From a00628646567886d666583b7df9a1b5b0398ecff Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 26 Oct 2023 19:31:44 +0800 Subject: [PATCH] FIX: organic support doesn't work with raft Fix organic support issues by syncing with Prusa's changes. Thanks to Prusa. Jira: none Change-Id: I96fa1a939767bb0b0d2e8a5fd72966bb10a2042e (cherry picked from commit 89607dc73313e1b5d389674ed2d8f4f358bcb8b9) --- .gitignore | 3 + src/libslic3r/CMakeLists.txt | 18 +- src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 3 + src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintObject.cpp | 4 +- src/libslic3r/Support/SupportLayer.hpp | 151 ++ .../{ => Support}/SupportMaterial.cpp | 205 +- .../{ => Support}/SupportMaterial.hpp | 164 +- src/libslic3r/Support/SupportParameters.hpp | 165 ++ .../{ => Support}/TreeModelVolumes.cpp | 242 ++- .../{ => Support}/TreeModelVolumes.hpp | 210 +-- src/libslic3r/{ => Support}/TreeSupport.cpp | 0 src/libslic3r/{ => Support}/TreeSupport.hpp | 14 +- src/libslic3r/{ => Support}/TreeSupport3D.cpp | 1655 ++++++++++++----- src/libslic3r/Support/TreeSupport3D.hpp | 332 ++++ src/libslic3r/Support/TreeSupportCommon.hpp | 730 ++++++++ src/libslic3r/TreeSupport3D.hpp | 605 ------ 18 files changed, 2816 insertions(+), 1689 deletions(-) create mode 100644 src/libslic3r/Support/SupportLayer.hpp rename src/libslic3r/{ => Support}/SupportMaterial.cpp (96%) rename src/libslic3r/{ => Support}/SupportMaterial.hpp (57%) create mode 100644 src/libslic3r/Support/SupportParameters.hpp rename src/libslic3r/{ => Support}/TreeModelVolumes.cpp (82%) rename src/libslic3r/{ => Support}/TreeModelVolumes.hpp (64%) rename src/libslic3r/{ => Support}/TreeSupport.cpp (100%) rename src/libslic3r/{ => Support}/TreeSupport.hpp (98%) rename src/libslic3r/{ => Support}/TreeSupport3D.cpp (73%) create mode 100644 src/libslic3r/Support/TreeSupport3D.hpp create mode 100644 src/libslic3r/Support/TreeSupportCommon.hpp delete mode 100644 src/libslic3r/TreeSupport3D.hpp diff --git a/.gitignore b/.gitignore index 758650f5ab..1077562909 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ deps/build-linux/* install_* build_*/ SVG +**/process_full/ +**/machine_full/ +**/filament_full/ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5fb314e9c0..75b981e99f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -265,14 +265,15 @@ set(lisbslic3r_sources SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp - SupportMaterial.cpp - SupportMaterial.hpp - TreeSupport.hpp - TreeSupport.cpp - TreeSupport3D.hpp - TreeSupport3D.cpp - TreeModelVolumes.hpp - TreeModelVolumes.cpp + Support/SupportMaterial.cpp + Support/SupportMaterial.hpp + Support/TreeSupport.hpp + Support/TreeSupport.cpp + Support/TreeSupport3D.hpp + Support/TreeSupport3D.cpp + Support/TreeModelVolumes.hpp + Support/TreeModelVolumes.cpp + Support/TreeSupportCommon.hpp MinimumSpanningTree.hpp MinimumSpanningTree.cpp Surface.cpp @@ -500,6 +501,7 @@ target_link_libraries(libslic3r qhull semver TBB::tbb + TBB::tbbmalloc libslic3r_cgal ${CMAKE_DL_LIBS} PNG::PNG diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 077c9dac01..a444d5e251 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -677,6 +677,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_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), 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) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 812d85d45d..24d8c81968 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -491,6 +491,9 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon 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); +// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). +// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. +Slic3r::Polygons intersection_clipped(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); Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8de984edd9..b7e19702b3 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -8,7 +8,7 @@ #include "Geometry/ConvexHull.hpp" #include "I18N.hpp" #include "ShortestPath.hpp" -#include "SupportMaterial.hpp" +#include "Support/SupportMaterial.hpp" #include "Thread.hpp" #include "Time.hpp" #include "GCode.hpp" diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 6f596cc7a6..dfcaabf317 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -7,7 +7,8 @@ #include "I18N.hpp" #include "Layer.hpp" #include "MutablePolygon.hpp" -#include "SupportMaterial.hpp" +#include "Support/SupportMaterial.hpp" +#include "Support/TreeSupport.hpp" #include "Surface.hpp" #include "Slicing.hpp" #include "Tesselate.hpp" @@ -17,7 +18,6 @@ #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" #include "InternalBridgeDetector.hpp" -#include "TreeSupport.hpp" #include #include diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp new file mode 100644 index 0000000000..ac820f7545 --- /dev/null +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include "../PrintConfig.hpp" +#include "../Slicing.hpp" +#include "../Fill/FillBase.hpp" +#include "../ClipperUtils.hpp" +#include "../Polygon.hpp" + +namespace Slic3r { + + class PrintObject; + class PrintConfig; + class PrintObjectConfig; + + // 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 SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support. + // This is from the old "MyLayer". + 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::sltTopContact) ? this->bottom_z : this->print_z; } + + SupporLayerType layer_type{ SupporLayerType::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; + }; + +// 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. +class SupportGeneratorLayerStorage { +public: + SupportGeneratorLayer& allocate_unguarded(SupporLayerType layer_type) { + m_storage.emplace_back(); + m_storage.back().layer_type = layer_type; + return m_storage.back(); + } + + SupportGeneratorLayer& allocate(SupporLayerType layer_type) + { + m_mutex.lock(); + m_storage.emplace_back(); + SupportGeneratorLayer *layer_new = &m_storage.back(); + m_mutex.unlock(); + layer_new->layer_type = layer_type; + return *layer_new; + } + +private: + template + using Allocator = tbb::scalable_allocator; + Slic3r::deque> m_storage; + tbb::spin_mutex m_mutex; +}; + using SupportGeneratorLayersPtr = std::vector; +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp similarity index 96% rename from src/libslic3r/SupportMaterial.cpp rename to src/libslic3r/Support/SupportMaterial.cpp index 613e7daaeb..297bc31e90 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -343,86 +343,6 @@ static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; } -SupportParameters::SupportParameters(const PrintObject &object) -{ - 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. - 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 (object_config.support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - 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())); - bridge_flow_ratio += region.config().bridge_flow; - } - this->gap_xy = object_config.support_object_xy_distance.value; - bridge_flow_ratio /= object.num_printing_regions(); - - 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()); - - this->can_merge_support_regions = object_config.support_filament.value == object_config.support_interface_filament.value; - if (!this->can_merge_support_regions && (object_config.support_filament.value == 0 || object_config.support_interface_filament.value == 0)) { - // One of the support extruders is of "don't care" type. - auto object_extruders = object.object_extruders(); - if (object_extruders.size() == 1 && - // object_extruders are 0-based but object_config.support_filament's are 1-based - object_extruders[0]+1 == std::max(object_config.support_filament.value, object_config.support_interface_filament.value)) - // Object is printed with the same extruder as the support. - this->can_merge_support_regions = true; - } - - - this->base_angle = Geometry::deg2rad(float(object_config.support_threshold_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_threshold_angle.value + 90.)); - this->interface_spacing = object_config.support_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_base_pattern_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_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - this->interface_spacing = this->support_spacing; - this->interface_density = this->support_density; - } - - SupportMaterialPattern support_pattern = object_config.support_base_pattern; - this->with_sheath = object_config.tree_support_wall_count>0; - this->base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; - this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (object_config.support_interface_pattern == smipGrid) - this->contact_fill_pattern = ipGrid; - else if (object_config.support_interface_pattern == smipRectilinearInterlaced) - this->contact_fill_pattern = ipRectilinear; - else - this->contact_fill_pattern = - (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || - object_config.support_interface_pattern == smipConcentric ? - ipConcentric : - (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); -} - PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_print_config (&object->print()->config()), m_object_config (&object->config()), @@ -570,7 +490,8 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Propagate top / bottom contact layers to generate interface layers // and base interface layers (for soluble interface / non souble base only) - auto [interface_layers, base_interface_layers] = this->generate_interface_layers(bottom_contacts, top_contacts, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr empty_layers; + auto [interface_layers, base_interface_layers] = generate_interface_layers(*m_object_config, m_support_params, bottom_contacts, top_contacts, empty_layers, empty_layers, intermediate_layers, layer_storage); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; @@ -1818,8 +1739,7 @@ static inline std::pair new_cont const SlicingParameters &slicing_params, const coordf_t support_layer_height_min, const Layer &layer, - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex) + SupportGeneratorLayerStorage &layer_storage) { double print_z, bottom_z, height; SupportGeneratorLayer* bridging_layer = nullptr; @@ -1891,7 +1811,7 @@ static inline std::pair new_cont } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::sltTopContact); + bridging_layer = &layer_storage.allocate(SupporLayerType::sltTopContact); bridging_layer->idx_object_layer_above = layer_id; bridging_layer->print_z = bridging_print_z; if (bridging_print_z == slicing_params.first_print_layer_height) { @@ -1909,7 +1829,7 @@ static inline std::pair new_cont } } - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::sltTopContact); + SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::sltTopContact); new_layer.idx_object_layer_above = layer_id; new_layer.print_z = print_z; new_layer.bottom_z = bottom_z; @@ -2216,7 +2136,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, // and the other for the overhangs extruded with a normal flow. contact_out.assign(num_layers * 2, nullptr); - tbb::spin_mutex layer_storage_mutex; std::vector overhangs_per_layers(num_layers); size_t layer_id_start = this->has_raft() ? 0 : 1; @@ -2421,7 +2340,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // Now apply the contact areas to the layer where they need to be made. if (!contact_polygons.empty() || !overhang_polygons.empty()) { // Allocate the two empty layers. - auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); + auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage); if (new_layer) { // Fill the non-bridging layer with polygons. fill_contact_layer(*new_layer, layer_id, m_slicing_params, @@ -2470,7 +2389,7 @@ static inline SupportGeneratorLayer* detect_bottom_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, + SupportGeneratorLayerStorage &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. @@ -2505,7 +2424,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( size_t layer_id = layer.id() - slicing_params.raft_layers(); // Allocate a new bottom contact layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::sltBottomContact); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::sltBottomContact); // 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 @@ -2960,7 +2879,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp assert(std::abs(extr2->bottom_z - m_slicing_params.first_print_layer_height) < EPSILON); 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) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); 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; @@ -2982,7 +2901,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // 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. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; @@ -3002,7 +2921,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); 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; @@ -3010,7 +2929,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp } // 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) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); 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); @@ -3028,7 +2947,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // 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. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; @@ -3052,7 +2971,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp 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) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate(sltIntermediate); 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; @@ -3456,7 +3375,7 @@ SupportGeneratorLayersPtr 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, (slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); + SupportGeneratorLayer &new_layer = layer_storage.allocate((slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = slicing_params.first_print_layer_height; new_layer.height = slicing_params.first_print_layer_height; @@ -3466,7 +3385,7 @@ SupportGeneratorLayersPtr generate_raft_base( // Insert the base layers. 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::sltRaftBase); + SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::sltRaftBase); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + slicing_params.base_raft_layer_height; new_layer.height = slicing_params.base_raft_layer_height; @@ -3476,7 +3395,7 @@ SupportGeneratorLayersPtr generate_raft_base( // Insert the interface layers. 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::sltRaftInterface); + SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::sltRaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; new_layer.height = slicing_params.interface_raft_layer_height; @@ -3522,28 +3441,24 @@ SupportGeneratorLayersPtr generate_raft_base( } // Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair PrintObjectSupportMaterial::generate_interface_layers( +std::pair generate_interface_layers( + const PrintObjectConfig& config, + const SupportParameters& m_support_params, const SupportGeneratorLayersPtr &bottom_contacts, const SupportGeneratorLayersPtr &top_contacts, + // Input / output, will be merged with output. Only provided for Organic supports. + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayersPtr &top_base_interface_layers, + // Input, will be trimmed with the newly created interface layers. SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) const + SupportGeneratorLayerStorage &layer_storage) { // my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - + const PrintObjectConfig* m_object_config = &config; 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_interface_top_layers > 1. - // Contact layer needs a base_interface layer, therefore run the following block if support_interface_top_layers > 0, has soluble support and extruders are different. - bool soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - m_slicing_params.soluble_interface && - // Interface extruder soluble. - m_object_config->support_interface_filament.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_interface_filament.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (m_object_config->support_filament.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_filament.value - 1)); bool snug_supports = m_object_config->support_style.value == smsSnug; // BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion bool differnt_support_interface_filament = m_object_config->support_filament != 0 && m_object_config->support_interface_filament != 0 && m_object_config->support_interface_filament.value != m_object_config->support_filament.value; @@ -3568,11 +3483,11 @@ std::pair PrintObjectSuppo auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; auto closing_distance = smoothing_distance; // scaled(m_object_config->support_closing_radius.value); - tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( - SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { - assert(! bottom.empty() || ! top.empty()); + auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { + bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); + assert(! bottom.empty() || ! top.empty() || has_top_interface); // Merge top into bottom, unite them with a safety offset. append(bottom, std::move(top)); // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). @@ -3581,11 +3496,17 @@ std::pair PrintObjectSuppo smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : union_safety_offset(std::move(bottom)), intermediate_layer.polygons); + if (has_top_interface) { + // Don't trim the precomputed Organic supports top interface with base layer + // as the precomputed top interface likely expands over multiple tree tips. + bottom = union_(std::move(top_interface_layer->polygons), bottom); + top_interface_layer->polygons.clear(); + } if (! bottom.empty()) { //FIXME Remove non-printable tiny islands, let them be printed using the base support. //bottom = opening(std::move(bottom), minimum_island_radius); if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + SupportGeneratorLayer &layer_new = top_interface_layer ? *top_interface_layer : layer_storage.allocate(type); layer_new.polygons = std::move(bottom); layer_new.print_z = intermediate_layer.print_z; layer_new.bottom_z = intermediate_layer.bottom_z; @@ -3604,7 +3525,7 @@ std::pair PrintObjectSuppo return nullptr; }; tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, + [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below @@ -3613,6 +3534,10 @@ std::pair PrintObjectSuppo auto idx_top_contact_first = -1; // Index of the first bottom contact layer intersecting the current intermediate layer. auto idx_bottom_contact_first = -1; + // Index of the first top interface layer intersecting the current intermediate layer. + auto idx_top_interface_first = -1; + // Index of the first top contact interface layer intersecting the current intermediate layer. + auto idx_top_base_interface_first = -1; auto num_intermediate = int(intermediate_layers.size()); for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; @@ -3664,23 +3589,55 @@ std::pair PrintObjectSuppo 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); } } - SupportGeneratorLayer *interface_layer = nullptr; - if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { + auto resolve_same_layer = [](SupportGeneratorLayersPtr &layers, int &idx, coordf_t print_z) -> SupportGeneratorLayer* { + if (! layers.empty()) { + idx = idx_higher_or_equal(layers, idx, [print_z](const SupportGeneratorLayer *layer) { return layer->print_z > print_z - EPSILON; }); + if (idx < int(layers.size()) && layers[idx]->print_z < print_z + EPSILON) + return layers[idx]; + } + return nullptr; + }; + SupportGeneratorLayer *top_interface_layer = resolve_same_layer(top_interface_layers, idx_top_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *top_base_interface_layer = resolve_same_layer(top_base_interface_layers, idx_top_base_interface_first, intermediate_layer.print_z); + SupportGeneratorLayer *interface_layer = nullptr; + if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty() || + (top_interface_layer && ! top_interface_layer->polygons.empty())) { interface_layer = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, + intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), top_interface_layer, nullptr, polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); interface_layers[idx_intermediate_layer] = interface_layer; } - if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) + if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty() || + (top_base_interface_layer && ! top_base_interface_layer->polygons.empty())) base_interface_layers[idx_intermediate_layer] = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), + intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), top_base_interface_layer, interface_layer ? &interface_layer->polygons : nullptr, sltBase); } }); // Compress contact_out, remove the nullptr items. - remove_nulls(interface_layers); - remove_nulls(base_interface_layers); + // The parallel_for above may not have merged all the interface and base_interface layers + // generated by the Organic supports code, do it here. + auto merge_remove_empty = [](SupportGeneratorLayersPtr& in1, SupportGeneratorLayersPtr& in2) { + auto remove_empty = [](SupportGeneratorLayersPtr& vec) { + vec.erase( + std::remove_if(vec.begin(), vec.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr || ptr->polygons.empty(); }), + vec.end()); + }; + remove_empty(in1); + remove_empty(in2); + if (in2.empty()) + return std::move(in1); + else if (in1.empty()) + return std::move(in2); + else { + SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr); + std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; }); + return out; + } + }; + interface_layers = merge_remove_empty(interface_layers, top_interface_layers); + base_interface_layers = merge_remove_empty(base_interface_layers, top_base_interface_layers); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; } diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp similarity index 57% rename from src/libslic3r/SupportMaterial.hpp rename to src/libslic3r/Support/SupportMaterial.hpp index 870aaa57b1..7d8699b10f 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -5,150 +5,14 @@ #include "PrintConfig.hpp" #include "Slicing.hpp" #include "Fill/FillBase.hpp" - +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" namespace Slic3r { class PrintObject; class PrintConfig; class PrintObjectConfig; -// 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 SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support. -// This is from the old "MyLayer". -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::sltTopContact) ? this->bottom_z : this->print_z; } - - SupporLayerType layer_type { SupporLayerType::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; -}; - -// 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 support_expansion; - 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; -}; - using LayerIndex = int; inline double layer_z(const SlicingParameters& slicing_params, const size_t layer_idx) @@ -211,6 +75,20 @@ SupportGeneratorLayersPtr generate_support_layers( const SupportGeneratorLayersPtr &interface_layers, const SupportGeneratorLayersPtr &base_interface_layers); +// 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 PrintObjectConfig& config, + const SupportParameters& support_params, + const SupportGeneratorLayersPtr& bottom_contacts, + const SupportGeneratorLayersPtr& top_contacts, + // Input / output, will be merged with output. Only provided for Organic supports. + SupportGeneratorLayersPtr& top_interface_layers, + SupportGeneratorLayersPtr& top_base_interface_layers, + SupportGeneratorLayersPtr& intermediate_layers, + SupportGeneratorLayerStorage& layer_storage); + // Produce the support G-code. // Used by both classic and tree supports. void generate_support_toolpaths( @@ -295,15 +173,7 @@ class PrintObjectSupportMaterial SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) 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 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. diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp new file mode 100644 index 0000000000..7fcc12ec5b --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -0,0 +1,165 @@ +#pragma once +#include "../libslic3r.h" +#include "../Flow.hpp" +#include "../PrintConfig.hpp" +#include "../Slicing.hpp" +#include "../Fill/FillBase.hpp" +#include "../Print.hpp" +#include "../Layer.hpp" +#include "SupportLayer.hpp" + +namespace Slic3r { +struct SupportParameters { + SupportParameters(const PrintObject& object) + { + const PrintConfig& print_config = object.print()->config(); + const PrintObjectConfig& object_config = object.config(); + const SlicingParameters& slicing_params = object.slicing_parameters(); + + this->soluble_interface = slicing_params.soluble_interface; + this->soluble_interface_non_soluble_base = + // Zero z-gap between the overhangs and the support interface. + slicing_params.soluble_interface && + // Interface extruder soluble. + object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) && + // Base extruder: Either "print with active extruder" not soluble. + (object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1)); + + { + this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); + this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? + num_top_interface_layers : object_config.support_interface_bottom_layers; + this->has_top_contacts = num_top_interface_layers > 0; + this->has_bottom_contacts = num_bottom_interface_layers > 0; + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2)); + this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); + } else { + this->num_top_base_interface_layers = 0; + this->num_bottom_base_interface_layers = 0; + } + } + 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. + 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 (object_config.support_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + 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& region = 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; + } + this->gap_xy = object_config.support_object_xy_distance.value; + bridge_flow_ratio /= object.num_printing_regions(); + + 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()); + + this->can_merge_support_regions = object_config.support_filament.value == object_config.support_interface_filament.value; + if (!this->can_merge_support_regions && (object_config.support_filament.value == 0 || object_config.support_interface_filament.value == 0)) { + // One of the support extruders is of "don't care" type. + auto object_extruders = object.object_extruders(); + if (object_extruders.size() == 1 && + // object_extruders are 0-based but object_config.support_filament's are 1-based + object_extruders[0] + 1 == std::max(object_config.support_filament.value, object_config.support_interface_filament.value)) + // Object is printed with the same extruder as the support. + this->can_merge_support_regions = true; + } + + + this->base_angle = Geometry::deg2rad(float(object_config.support_threshold_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_threshold_angle.value + 90.)); + this->interface_spacing = object_config.support_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_base_pattern_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_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + this->interface_spacing = this->support_spacing; + this->interface_density = this->support_density; + } + + SupportMaterialPattern support_pattern = object_config.support_base_pattern; + this->with_sheath = object_config.tree_support_wall_count > 0; + this->base_fill_pattern = + support_pattern == smpHoneycomb ? ipHoneycomb : + this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; + this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + if (object_config.support_interface_pattern == smipGrid) + this->contact_fill_pattern = ipGrid; + else if (object_config.support_interface_pattern == smipRectilinearInterlaced) + this->contact_fill_pattern = ipRectilinear; + else + this->contact_fill_pattern = + (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || + object_config.support_interface_pattern == smipConcentric ? + ipConcentric : + (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + } + // Both top / bottom contacts and interfaces are soluble. + bool soluble_interface; + // Support contact & interface are soluble, but support base is non-soluble. + bool soluble_interface_non_soluble_base; + + // Is there at least a top contact layer extruded above support base? + bool has_top_contacts; + // Is there at least a bottom contact layer extruded below support base? + bool has_bottom_contacts; + + // Number of top interface layers without counting the contact layer. + size_t num_top_interface_layers; + // Number of bottom interface layers without counting the contact layer. + size_t num_bottom_interface_layers; + // Number of top base interface layers. Zero if not soluble_interface_non_soluble_base. + size_t num_top_base_interface_layers; + // Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base. + size_t num_bottom_base_interface_layers; + + bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; } + bool has_interfaces() const { return this->num_top_interface_layers + this->num_bottom_interface_layers > 0; } + bool has_base_interfaces() const { return this->num_top_base_interface_layers + this->num_bottom_base_interface_layers > 0; } + size_t num_top_interface_layers_only() const { return this->num_top_interface_layers - this->num_top_base_interface_layers; } + size_t num_bottom_interface_layers_only() const { return this->num_bottom_interface_layers - this->num_bottom_base_interface_layers; } + 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 support_expansion; + 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; +}; +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp similarity index 82% rename from src/libslic3r/TreeModelVolumes.cpp rename to src/libslic3r/Support/TreeModelVolumes.cpp index c7f7bb063a..81ea970215 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -7,16 +7,17 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeModelVolumes.hpp" -#include "TreeSupport3D.hpp" - -#include "BuildVolume.hpp" -#include "ClipperUtils.hpp" -#include "Flow.hpp" -#include "Layer.hpp" -#include "Point.hpp" -#include "Print.hpp" -#include "PrintConfig.hpp" -#include "Utils.hpp" +#include "TreeSupportCommon.hpp" + +#include "../BuildVolume.hpp" +#include "../ClipperUtils.hpp" +#include "../Flow.hpp" +#include "../Layer.hpp" +#include "../Point.hpp" +#include "../Print.hpp" +#include "../PrintConfig.hpp" +#include "../Utils.hpp" +#include "../format.hpp" #include @@ -34,74 +35,6 @@ using namespace std::literals; // 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(); - 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 || config.support_material_style == smsOrganic); - - // 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, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); - } - - this->layer_height = scaled(config.layer_height.value); - this->resolution = scaled(print_config.resolution.value); - // Arache feature - this->min_feature_size = scaled(config.min_feature_size.value); - // +1 makes the threshold inclusive - this->support_angle = 0.5 * M_PI - std::clamp((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI); - 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_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0; - this->support_bottom_height = this->support_bottom_enable ? - (config.support_interface_bottom_layers.value > 0 ? - config.support_interface_bottom_layers.value : - config.support_interface_top_layers.value) * this->layer_height : - 0; - this->support_material_buildplate_only = config.support_on_build_plate_only; - this->support_xy_distance = scaled(config.support_object_xy_distance.value); - // 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); -// this->support_interface_skip_height = -// this->support_infill_angles = - this->support_roof_enable = config.support_interface_top_layers.value > 0; - this->support_roof_height = config.support_interface_top_layers.value * this->layer_height; -// this->minimum_roof_area = -// this->support_roof_angles = - this->support_roof_pattern = config.support_interface_pattern; - this->support_pattern = config.support_base_pattern; - this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); -// this->support_bottom_offset = -// this->support_wall_count = config.support_material_with_sheath ? 1 : 0; - this->support_wall_count = 1; - this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; -// this->minimum_support_area = -// this->minimum_bottom_area = -// this->support_offset = -// this->support_tree_branch_distance = 2.5 * line_width ?? - double support_tree_angle_slow = 25;// TODO add a setting? - double support_tree_branch_diameter_angle = 5; // TODO add a setting? - double tree_support_tip_diameter = 0.8; - this->support_tree_angle = std::clamp(config.tree_support_branch_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); - this->support_tree_angle_slow = std::clamp(support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); - this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter.value); - this->support_tree_branch_diameter_angle = std::clamp(support_tree_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); - this->support_tree_top_rate = 30; // percent -// this->support_tree_tip_diameter = this->support_line_width; - this->support_tree_tip_diameter = std::clamp(scaled(tree_support_tip_diameter), 0, this->support_tree_branch_diameter); -} - //FIXME Machine border is currently ignored. static Polygons calculateMachineBorderCollision(Polygon machine_border) { @@ -162,14 +95,23 @@ TreeModelVolumes::TreeModelVolumes( { m_anti_overhang = print_object.slice_support_blockers(); TreeSupportMeshGroupSettings mesh_settings(print_object); - m_layer_outlines.emplace_back(mesh_settings, std::vector{}); + const TreeSupportSettings config{ mesh_settings, print_object.slicing_parameters() }; + m_current_min_xy_dist = config.xy_min_distance; + m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist; + assert(m_current_min_xy_dist_delta >= 0); + m_increase_until_radius = config.increase_radius_until_radius; + m_radius_0 = config.getRadius(0); + m_raft_layers = config.raft_layers; m_current_outline_idx = 0; + m_layer_outlines.emplace_back(mesh_settings, std::vector{}); std::vector &outlines = m_layer_outlines.front().second; - outlines.assign(print_object.layer_count(), Polygons{}); - tbb::parallel_for(tbb::blocked_range(0, print_object.layer_count()), + size_t num_raft_layers = m_raft_layers.size(); + size_t num_layers = print_object.layer_count() + num_raft_layers; + outlines.assign(num_layers, Polygons{}); + tbb::parallel_for(tbb::blocked_range(num_raft_layers, num_layers, std::min(1, std::max(16, num_layers / (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)); + outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution); }); } #endif @@ -181,12 +123,6 @@ TreeModelVolumes::TreeModelVolumes( m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); } - const TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; - m_current_min_xy_dist = config.xy_min_distance; - m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist; - assert(m_current_min_xy_dist_delta >= 0); - 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++) { @@ -218,7 +154,7 @@ TreeModelVolumes::TreeModelVolumes( #endif } -void TreeModelVolumes::precalculate(const coord_t max_layer, std::function throw_on_cancel) +void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord_t max_layer, std::function throw_on_cancel) { auto t_start = std::chrono::high_resolution_clock::now(); m_precalculated = true; @@ -226,7 +162,7 @@ void TreeModelVolumes::precalculate(const coord_t max_layer, std::function= int(i); -- k) { std::string legend = format("radius-%1%", unscaled(sorted[k].first.first)); expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) }); + SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d-%s.svg", name.data(), sorted[i].first.second, legend.c_str()), { expolygons_with_attributes.back() }); } // Render the range of per radius collision polygons into a common SVG. SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes); @@ -417,8 +354,6 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx, std::function throw_on_cancel) const { - if (orig_radius == 0) - return this->getCollision(0, layer_idx, true); const coord_t radius = ceilRadius(orig_radius); if (std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result) @@ -426,7 +361,10 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; tree_supports_show_error("Not precalculated Placeable areas requested."sv, false); - } + } + if (orig_radius == 0) + // Placable areas for radius 0 are calculated in the general collision code. + return this->getCollision(0, layer_idx, true); const_cast(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel); return getPlaceableAreas(orig_radius, layer_idx, throw_on_cancel); } @@ -481,22 +419,20 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex 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{}); + LayerPolygonCache data; + data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1); + const bool calculate_placable = m_support_rests_on_model && radius == 0; - std::vector data_placeable; + LayerPolygonCache data_placeable; if (calculate_placable) - data_placeable = std::vector(max_layer_idx + 1 - min_layer_last, Polygons{}); + data_placeable.allocate(data.begin(), data.end()); 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 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)); + const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height))); + const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height))); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : // 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. @@ -507,41 +443,84 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex 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, &throw_on_cancel] + LayerPolygonCache collision_areas_offsetted; + collision_areas_offsetted.allocate( + std::max(0, data.begin() - z_distance_bottom_layers), + std::min(outlines.size(), data.end() + z_distance_top_layers)); + tbb::parallel_for(tbb::blocked_range(collision_areas_offsetted.begin(), collision_areas_offsetted.end()), + [&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, &collision_areas_offsetted, &throw_on_cancel] (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); - if(throw_on_cancel) + collision_areas_offsetted[layer_idx] = offset_value == 0 ? + union_(collision_areas) : + offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2); throw_on_cancel(); } }); // 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, &throw_on_cancel] + const bool processing_last_mesh = outline_idx == layer_outline_indices.size(); + tbb::parallel_for(tbb::blocked_range(data.begin(), data.end()), + [&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius, + xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel] (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 < int(collision_areas_offsetted.size())) + for (int i = - z_distance_bottom_layers; i <= 0; ++ i) + if (int j = layer_idx + i; collision_areas_offsetted.has(j)) append(collisions, collision_areas_offsetted[j]); + for (int i = 1; i <= z_distance_top_layers; ++ i) + if (int j = layer_idx + i; j < int(outlines.size())) { + Polygons collision_areas_original = machine_border; + append(collision_areas_original, outlines[j]); + + // If just the collision (including the xy distance) of the layers above is accumulated, it leads to the + // following issue: + // Example: assuming the z distance is 2 layer + // + = xy_distance + // - = model + // o = overhang of the area two layers above that should result in tips on this layer + // + // +-----+ + // +-----+ + // +-----+ + // o +-----+ + // If just the collision above is accumulated the overhang will get overwritten by the xy_distance of the + // layer below the overhang... + // + // This only causes issues if the overhang area is thinner than xy_distance + // Just accumulating areas of the model above without the xy distance is also problematic, as then support + // may get closer to the model (on the diagonal downwards) than the user intended. Example (s = support): + // +-----+ + // +-----+ + // +-----+ + // s+-----+ + + // technically the calculation below is off by one layer, as the actual distance between plastic one layer + // down is 0 not layer height, as this layer is filled with said plastic. But otherwise a part of the + // overhang that is expected to be supported is overwritten by the remaining part of the xy distance of the + // layer below the to be supported area. + coord_t required_range_x = + (xy_distance - ((i - (z_distance_top_layers == 1 ? 0.5 : 0)) * xy_distance / z_distance_top_layers)); + // the conditional -0.5 ensures that plastic can never touch on the diagonal + // downward when the z_distance_top_layers = 1. It is assumed to be better to + // not support an overhang<90 degree than to risk fusing to it. + append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2)); } - collisions = last && layer_idx < int(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) { + collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ? + union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : + union_(collisions); + auto &dst = data[layer_idx]; + if (processing_last_mesh) { if (! dst.empty()) collisions = union_(collisions, dst); dst = polygons_simplify(collisions, min_resolution); } else - append(dst, collisions); + append(dst, std::move(collisions)); if (throw_on_cancel) throw_on_cancel(); } @@ -550,17 +529,21 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex // 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, z_distance_bottom_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last, &throw_on_cancel] + tbb::parallel_for(tbb::blocked_range(std::max(z_distance_bottom_layers + 1, data.begin()), data.end()), + [&collision_areas_offsetted, &outlines, &anti_overhang = m_anti_overhang, processing_last_mesh, + min_resolution = m_min_resolution, z_distance_bottom_layers, xy_distance, &data_placeable, &throw_on_cancel] (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; + LayerIndex layer_idx_below = layer_idx - z_distance_bottom_layers - 1; 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 < int(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) { + const Polygons ¤t = collision_areas_offsetted[layer_idx]; + const Polygons &below = outlines[layer_idx_below]; + Polygons placable = diff( + // Inflate the surface to sit on by the separation distance to increase chance of a support being placed on a sloped surface. + offset(below, xy_distance), + layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current); + auto &dst = data_placeable[layer_idx]; + if (processing_last_mesh) { if (! dst.empty()) placable = union_(placable, dst); dst = polygons_simplify(placable, min_resolution); @@ -585,9 +568,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex #endif if (throw_on_cancel) throw_on_cancel(); - m_collision_cache.insert(std::move(data), min_layer_last + 1, radius); + m_collision_cache.insert(std::move(data), radius); if (calculate_placable) - m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius); + m_placeable_areas_cache.insert(std::move(data_placeable), radius); } void TreeModelVolumes::calculateCollisionHolefree(const std::vector &keys, std::function throw_on_cancel) @@ -653,7 +636,8 @@ void TreeModelVolumes::calculateAvoidance(const std::vector &ke 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) + if ((task.to_model ? to_model : to_build_plate) && + (! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta)) avoidance_tasks.emplace_back(task); } @@ -679,7 +663,7 @@ void TreeModelVolumes::calculateAvoidance(const std::vector &ke assert(move_steps > 0); float last_move_step = max_move - (move_steps - 1) * move_step; if (last_move_step < scaled(0.05)) { - assert(move_steps > 1); + //assert(move_steps > 1); if (move_steps > 1) { // Avoid taking a very short last step, stretch the other steps a bit instead. move_step = max_move / (-- move_steps); diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/Support/TreeModelVolumes.hpp similarity index 64% rename from src/libslic3r/TreeModelVolumes.hpp rename to src/libslic3r/Support/TreeModelVolumes.hpp index 5f9db2a1d8..fc91c4ed81 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/Support/TreeModelVolumes.hpp @@ -14,9 +14,11 @@ #include -#include "Point.hpp" -#include "Polygon.hpp" -#include "PrintConfig.hpp" +#include "TreeSupportCommon.hpp" + +#include "../Point.hpp" +#include "../Polygon.hpp" +#include "../PrintConfig.hpp" namespace Slic3r { @@ -27,169 +29,10 @@ class PrintObject; namespace TreeSupport3D { -using LayerIndex = int; - -struct TreeSupportMeshGroupSettings { - TreeSupportMeshGroupSettings() = default; - explicit 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) - Arachne specific - // 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. - 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) }; - // 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 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 - 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 - 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 - 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. - 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 - 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 - 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 - // 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 }; -}; +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); +static constexpr const bool SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; class TreeModelVolumes { @@ -240,7 +83,7 @@ class TreeModelVolumes * 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(const coord_t max_layer, std::function throw_on_cancel); + void precalculate(const PrintObject& print_object, const coord_t max_layer, std::function throw_on_cancel); /*! * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -326,6 +169,29 @@ class TreeModelVolumes Polygon m_bed_area; private: + // Caching polygons for a range of layers. + class LayerPolygonCache { + public: + void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) { + m_idx_begin = aidx_begin; + m_idx_end = aidx_end; + m_polygons.assign(aidx_end - aidx_begin, {}); + } + + LayerIndex begin() const { return m_idx_begin; } + LayerIndex end() const { return m_idx_end; } + size_t size() const { return m_polygons.size(); } + + bool has(LayerIndex idx) const { return idx >= m_idx_begin && idx < m_idx_end; } + Polygons& operator[](LayerIndex idx) { assert(idx >= m_idx_begin && idx < m_idx_end); return m_polygons[idx - m_idx_begin]; } + std::vector& polygons_mutable() { return m_polygons; } + + private: + std::vector m_polygons; + LayerIndex m_idx_begin; + LayerIndex m_idx_end; + }; + /*! * \brief Convenience typedef for the keys to the caches */ @@ -361,6 +227,13 @@ class TreeModelVolumes for (auto &d : in) m_data[first_layer_idx ++].emplace(radius, std::move(d)); } + void insert(LayerPolygonCache &&in, coord_t radius) { + std::lock_guard guard(m_mutex); + LayerIndex i = in.begin(); + allocate_layers(i + LayerIndex(in.size())); + for (auto &d : in.polygons_mutable()) + m_data[i ++].emplace(radius, 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. @@ -616,6 +489,9 @@ class TreeModelVolumes */ coord_t m_radius_0; + // Z heights of the raft layers (additional layers below the object, last raft layer aligned with the bottom of the first object layer). + std::vector m_raft_layers; + /*! * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp similarity index 100% rename from src/libslic3r/TreeSupport.cpp rename to src/libslic3r/Support/TreeSupport.cpp diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp similarity index 98% rename from src/libslic3r/TreeSupport.hpp rename to src/libslic3r/Support/TreeSupport.hpp index 50fb6919a6..52550e3688 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -3,14 +3,14 @@ #include #include -#include "ExPolygon.hpp" -#include "Point.hpp" -#include "Slicing.hpp" -#include "MinimumSpanningTree.hpp" #include "tbb/concurrent_unordered_map.h" -#include "Flow.hpp" -#include "PrintConfig.hpp" -#include "Fill/Lightning/Generator.hpp" +#include "../ExPolygon.hpp" +#include "../Point.hpp" +#include "../Slicing.hpp" +#include "../MinimumSpanningTree.hpp" +#include "../Flow.hpp" +#include "../PrintConfig.hpp" +#include "../Fill/Lightning/Generator.hpp" #include "TreeModelVolumes.hpp" #include "TreeSupport3D.hpp" diff --git a/src/libslic3r/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp similarity index 73% rename from src/libslic3r/TreeSupport3D.cpp rename to src/libslic3r/Support/TreeSupport3D.cpp index c0bcfac32f..c145c9cfe2 100644 --- a/src/libslic3r/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -162,7 +162,7 @@ static std::vector>> group_me //assert(object_config.support_material_style == smsTree || object_config.support_material_style == smsOrganic); bool found_existing_group = false; - TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object } }; + TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object }, print_object.slicing_parameters() }; //FIXME for now only a single object per group is enabled. #if 0 for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) @@ -209,27 +209,14 @@ static std::vector>> group_me } #endif -static bool inline g_showed_critical_error = false; -static bool inline g_showed_performance_warning = false; -void tree_supports_show_error(std::string_view message, bool critical) -{ // todo Remove! ONLY FOR PUBLIC BETA!! - -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - static bool showed_critical = false; - static bool showed_performance = false; - auto 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 && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); - (critical ? g_showed_critical_error : g_showed_performance_warning) = true; - if (show) - 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" + std::string(message) + "\n" + bugtype).c_str(), - "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 -} - -[[nodiscard]] static const std::vector generate_overhangs(const PrintObject &print_object, std::function throw_on_cancel) +[[nodiscard]] static const std::vector generate_overhangs(const TreeSupportSettings &settings, const PrintObject &print_object, std::function throw_on_cancel) { - std::vector out(print_object.layer_count(), Polygons{}); + const size_t num_raft_layers = settings.raft_layers.size(); + const size_t num_object_layers = print_object.layer_count(); + const size_t num_layers = num_object_layers + num_raft_layers; + std::vector out(num_layers, Polygons{}); + const PrintConfig &print_config = print_object.print()->config(); const PrintObjectConfig &config = print_object.config(); const bool support_auto = is_auto(config.support_type.value); const int support_enforce_layers = config.enforce_support_layers.value; @@ -245,8 +232,10 @@ void tree_supports_show_error(std::string_view message, bool critical) double support_tree_tip_diameter = 0.8; auto enforcer_overhang_offset = scaled(support_tree_tip_diameter); - tbb::parallel_for(tbb::blocked_range(1, out.size()), - [&print_object, &enforcers_layers, &blockers_layers, support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, &throw_on_cancel, &out] + size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size())); + tbb::parallel_for(tbb::blocked_range(1, num_overhang_layers), + [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, + support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out] (const tbb::blocked_range &range) { for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer ¤t_layer = *print_object.get_layer(layer_id); @@ -279,6 +268,11 @@ void tree_supports_show_error(std::string_view message, bool critical) } if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) overhangs = diff(overhangs, blockers_layers[layer_id], ApplySafetyOffset::Yes); + //if (config.bridge_no_support) { + // for (const LayerRegion *layerm : current_layer.regions()) + // remove_bridges_from_contacts(print_config, lower_layer, *layerm, + // float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); + //} } //check_self_intersections(overhangs, "generate_overhangs1"); if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) { @@ -311,7 +305,7 @@ void tree_supports_show_error(std::string_view message, bool critical) //check_self_intersections(overhangs, "generate_overhangs - enforcers"); } } - out[layer_id] = std::move(overhangs); + out[layer_id + num_raft_layers] = std::move(overhangs); throw_on_cancel(); } }); @@ -328,15 +322,17 @@ void tree_supports_show_error(std::string_view message, bool critical) LayerIndex max_layer = 0; for (size_t object_id : object_ids) { const PrintObject &print_object = *print.get_object(object_id); + const int num_raft_layers = int(config.raft_layers.size()); + const int num_layers = int(print_object.layer_count()) + num_raft_layers; int max_support_layer_id = 0; - for (int layer_id = 1; layer_id < int(print_object.layer_count()); ++ layer_id) + for (int layer_id = std::max(num_raft_layers, 1); layer_id < num_layers; ++ 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); } if (max_layer > 0) // The actual precalculation happens in TreeModelVolumes. - volumes.precalculate(max_layer, throw_on_cancel); + volumes.precalculate(*print.get_object(object_ids.front()), max_layer, throw_on_cancel); return max_layer; } @@ -384,6 +380,7 @@ void tree_supports_show_error(std::string_view message, bool critical) return result; } +#if 0 /*! * \brief Converts lines in internal format into a Polygons object representing these lines. * @@ -402,6 +399,7 @@ void tree_supports_show_error(std::string_view message, bool critical) validate_range(result); return result; } +#endif /*! * \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas(). @@ -490,15 +488,15 @@ static std::optional> polyline_sample_next_point_at_dis 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) { + // Squared distance of an intersection point of a circle with center at the foot point. + if (double l2_intersection = dist2 - l2_from_line; + l2_intersection > - 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) { + l2_intersection = std::max(l2_intersection, 0.); + if ((v - foot_pt).cast().squaredNorm() >= l2_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(); + Point p = p0 + (foot_pt + v * sqrt(l2_intersection / l2v)).cast(); validate_range(p); return std::pair{ p, i - 1 }; } @@ -621,7 +619,8 @@ static std::optional> polyline_sample_next_point_at_dis * \return A Polygons object that represents the resulting infill lines. */ [[nodiscard]] static Polylines generate_support_infill_lines( - const Polygons &polygon, const SupportParameters &support_params, + const Polygons &polygon, + const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { #if 0 @@ -780,7 +779,7 @@ static std::optional> polyline_sample_next_point_at_dis else do_final_difference = true; } - if (steps + (distance < last_step_offset_without_check || distance % step_size != 0) < min_amount_offset && min_amount_offset > 1) { + if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(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; @@ -809,16 +808,393 @@ static std::optional> polyline_sample_next_point_at_dis return union_(ret); } +class RichInterfacePlacer : public InterfacePlacer { +public: + RichInterfacePlacer( + const InterfacePlacer &interface_placer, + const TreeModelVolumes &volumes, + bool force_tip_to_roof, + size_t num_support_layers, + std::vector &move_bounds) + : + InterfacePlacer(interface_placer), + volumes(volumes), force_tip_to_roof(force_tip_to_roof), move_bounds(move_bounds) + { + m_already_inserted.assign(num_support_layers, {}); + this->min_xy_dist = this->config.xy_distance > this->config.xy_min_distance; + } + const TreeModelVolumes &volumes; + // Radius of the tree tip is large enough to be covered by an interface. + const bool force_tip_to_roof; + bool min_xy_dist; + +public: + // called by sample_overhang_area() + void add_points_along_lines( + // Insert points (tree tips or top contact interfaces) along these lines. + LineInformations lines, + // Start at this layer. + LayerIndex insert_layer_idx, + // Insert this number of interface layers. + size_t roof_tip_layers, + // True if an interface is already generated above these lines. + size_t supports_roof_layers, + // The element tries to not move until this dtt is reached. + 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) { + size_t this_layer_idx = insert_layer_idx - dtt_roof_tip; + auto evaluateRoofWillGenerate = [&](const 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.points.emplace_back(p.first + corner * config.min_radius); + return !generate_support_infill_lines({ roof_circle }, config, true, insert_layer_idx - dtt_roof_tip, config.support_roof_line_distance).empty(); + #else + return true; + #endif + }; + + { + std::pair split = + // keep all lines that are still valid on the next layer + split_lines(lines, [this, this_layer_idx](const std::pair &p) + { return evaluate_point_for_next_layer_function(volumes, config, this_layer_idx, p); }); + LineInformations points = std::move(split.second); + // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. + split = split_lines(split.first, evaluateRoofWillGenerate); + lines = std::move(split.first); + append(points, split.second); + // add all points that would not be valid + for (const LineInformation &line : points) + for (const std::pair &point_data : line) + add_point_as_influence_area(point_data, this_layer_idx, + // don't move until + roof_tip_layers - dtt_roof_tip, + // supports roof + dtt_roof_tip + supports_roof_layers > 0, + // disable ovalization + false); + } + + // add all tips as roof to the roof storage + Polygons new_roofs; + for (const LineInformation &line : lines) + //FIXME sweep the tip radius along the line? + for (const std::pair &p : line) { + Polygon roof_circle{ m_base_circle }; + roof_circle.scale(config.min_radius / m_base_radius); + roof_circle.translate(p.first); + new_roofs.emplace_back(std::move(roof_circle)); + } + this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers); + } + + for (const LineInformation &line : lines) { + // 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 + bool disable_ovalistation = config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; + for (const std::pair &point_data : line) + add_point_as_influence_area(point_data, insert_layer_idx - dtt_roof_tip, + // don't move until + dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, + // supports roof + dtt_roof_tip + supports_roof_layers > 0, + disable_ovalistation); + } + } + +private: + // called by this->add_points_along_lines() + void add_point_as_influence_area(std::pair p, 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 (! config.support_rests_on_model && ! to_bp) { + BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); + return; + } + Polygons circle{ m_base_circle }; + circle.front().translate(p.first); + { + Point hash_pos = p.first / ((config.min_radius + 1) / 10); + std::lock_guard critical_section_movebounds(m_mutex_movebounds); + if (!m_already_inserted[insert_layer].count(hash_pos)) { + // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing + m_already_inserted[insert_layer].emplace(hash_pos); + static constexpr const size_t dtt = 0; + SupportElementState state; + state.target_height = insert_layer; + state.target_position = p.first; + state.next_position = p.first; + state.layer_idx = insert_layer; + state.effective_radius_height = dtt; + state.to_buildplate = to_bp; + state.distance_to_top = dtt; + state.result_on_layer = p.first; + assert(state.result_on_layer_is_set()); + state.increased_to_model_radius = 0; + state.to_model_gracious = gracious; + state.elephant_foot_increases = 0; + state.use_min_xy_dist = min_xy_dist; + state.supports_roof = roof; + state.dont_move_until = dont_move_until; + state.can_use_safe_radius = safe_radius; + state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; + state.skip_ovalisation = skip_ovalisation; + move_bounds[insert_layer].emplace_back(state, std::move(circle)); + } + } + } + + // Outputs + std::vector &move_bounds; + + // Temps + static constexpr const auto m_base_radius = scaled(0.01); + const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + + // Mutexes, guards + std::mutex m_mutex_movebounds; + std::vector> m_already_inserted; +}; + + +int generate_raft_contact( + const PrintObject &print_object, + const TreeSupportSettings &config, + InterfacePlacer &interface_placer) +{ + int raft_contact_layer_idx = -1; + if (print_object.has_raft() && print_object.layer_count() > 0) { + // Produce raft contact layer outside of the tree support loop, so that no trees will be generated for the raft contact layer. + // Raft layers supporting raft contact interface will be produced by the classic raft generator. + // Find the raft contact layer. + raft_contact_layer_idx = int(config.raft_layers.size()) - 1; + while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON) + -- raft_contact_layer_idx; + // Create the raft contact layer. + const ExPolygons &lslices = print_object.get_layer(0)->lslices; + double expansion = print_object.config().raft_expansion.value; + interface_placer.add_roof_unguarded(expansion > 0 ? offset(lslices, scaled(expansion)) : to_polygons(lslices), raft_contact_layer_idx, 0); + } + return raft_contact_layer_idx; +} + +void finalize_raft_contact( + const PrintObject &print_object, + const int raft_contact_layer_idx, + SupportGeneratorLayersPtr &top_contacts, + std::vector &move_bounds) +{ + if (raft_contact_layer_idx >= 0) { + const size_t first_tree_layer = print_object.slicing_parameters().raft_layers() - 1; + // Remove tree tips that start below the raft contact, + // remove interface layers below the raft contact. + for (size_t i = 0; i < first_tree_layer; ++i) { + top_contacts[i] = nullptr; + move_bounds[i].clear(); + } + if (raft_contact_layer_idx >= 0 && print_object.config().raft_expansion.value > 0) { + // If any tips at first_tree_layer now are completely inside the expanded raft layer, remove them as well before they are propagated to the ground. + Polygons &raft_polygons = top_contacts[raft_contact_layer_idx]->polygons; + EdgeGrid::Grid grid(get_extents(raft_polygons).inflated(SCALED_EPSILON)); + grid.create(raft_polygons, Polylines{}, coord_t(scale_(10.))); + SupportElements &first_layer_move_bounds = move_bounds[first_tree_layer]; + double threshold = scaled(print_object.config().raft_expansion.value) * 2.; + first_layer_move_bounds.erase(std::remove_if(first_layer_move_bounds.begin(), first_layer_move_bounds.end(), + [&grid, threshold](const SupportElement &el) { + coordf_t dist; + if (grid.signed_distance_edges(el.state.result_on_layer, threshold, dist)) { + assert(std::abs(dist) < threshold + SCALED_EPSILON); + // Support point is inside the expanded raft, remove it. + return dist < - 0.; + } + return false; + }), first_layer_move_bounds.end()); + #if 0 + // Remove the remaining tips from the raft: Closing operation on tip circles. + if (! first_layer_move_bounds.empty()) { + const double eps = 0.1; + // All tips supporting this layer are expected to have the same radius. + double radius = support_element_radius(config, first_layer_move_bounds.front()); + // Connect the tips with the following closing radius. + double closing_distance = radius; + Polygon circle = make_circle(radius + closing_distance, eps); + Polygons circles; + circles.reserve(first_layer_move_bounds.size()); + for (const SupportElement &el : first_layer_move_bounds) { + circles.emplace_back(circle); + circles.back().translate(el.state.result_on_layer); + } + raft_polygons = diff(raft_polygons, offset(union_(circles), - closing_distance)); + } + #endif + } + } +} + +// Called by generate_initial_areas(), used in parallel by multiple layers. +// Produce +// 1) Maximum num_support_roof_layers roof (top interface & contact) layers. +// 2) Tree tips supporting either the roof layers or the object itself. +// num_support_roof_layers should always be respected: +// If num_support_roof_layers contact layers could not be produced, then the tree tip +// is augmented with SupportElementState::missing_roof_layers +// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to +// roofs aka interface layers by the tool path generator. +void sample_overhang_area( + // Area to support + Polygons&& overhang_area, + // If true, then the overhang_area is likely large and wide, thus it is worth to try + // to cover it with continuous interfaces supported by zig-zag patterned tree tips. + const bool large_horizontal_roof, + // Index of the top suport layer generated by this function. + const size_t layer_idx, + // Maximum number of roof (contact, interface) layers between the overhang and tree tips to be generated. + const size_t num_support_roof_layers, + // + const coord_t connect_length, + // Configuration classes + const TreeSupportMeshGroupSettings& mesh_group_settings, + // Configuration & Output + RichInterfacePlacer& interface_placer) +{ + // 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 + auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons& area, LayerIndex layer_idx) -> Polylines { + return generate_support_infill_lines(area, interface_placer.support_parameters, true, layer_idx, mesh_group_settings.support_roof_line_distance); + }; + + LineInformations overhang_lines; + // Track how many top contact / interface layers were already generated. + size_t dtt_roof = 0; + size_t layer_generation_dtt = 0; + + if (large_horizontal_roof) { + assert(num_support_roof_layers > 0); + // 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. + std::vector added_roofs(num_support_roof_layers); + Polygons last_overhang = overhang_area; + for (dtt_roof = 0; dtt_roof < num_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; + { + const bool min_xy_dist = interface_placer.config.xy_distance > interface_placer.config.xy_min_distance; + const Polygons& forbidden_next_raw = interface_placer.config.support_rests_on_model ? + interface_placer.volumes.getCollision(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : + interface_placer.volumes.getAvoidance(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist); + // prevent rounding errors down the line + //FIXME maybe use SafetyOffset::Yes at the following diff() instead? + forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); + } + Polygons overhang_area_next = diff(overhang_area, forbidden_next); + if (area(overhang_area_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. + if (dtt_roof > 0) { + size_t dtt_before = dtt_roof - 1; + // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. + overhang_lines = split_lines( + convert_lines_to_internal(interface_placer.volumes, interface_placer.config, + ensure_maximum_distance_polyline(generate_roof_lines(last_overhang, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before), + [&interface_placer, layer_idx, dtt_before](const std::pair& p) + { return evaluate_point_for_next_layer_function(interface_placer.volumes, interface_placer.config, layer_idx - dtt_before, p); }) + .first; + } + break; + } + added_roofs[dtt_roof] = overhang_area; + last_overhang = std::move(overhang_area); + overhang_area = std::move(overhang_area_next); + } + + 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 the roof should be valid, check that the area does generate lines. This is NOT guaranteed. + if (overhang_lines.empty() && dtt_roof != 0 && generate_roof_lines(overhang_area, 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 (generate_roof_lines(added_roofs[idx], layer_idx - idx).empty()) { + dtt_roof = idx; + layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; + break; + } + } + added_roofs.erase(added_roofs.begin() + dtt_roof, added_roofs.end()); + interface_placer.add_roofs(std::move(added_roofs), layer_idx); + } + + 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 + bool supports_roof = dtt_roof > 0; + bool continuous_tips = !supports_roof && large_horizontal_roof; + Polylines polylines = ensure_maximum_distance_polyline( + generate_support_infill_lines(overhang_area, interface_placer.support_parameters, supports_roof, layer_idx - layer_generation_dtt, + supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), + continuous_tips ? interface_placer.config.min_radius / 2 : connect_length, 1); + size_t point_count = 0; + for (const Polyline& poly : polylines) + point_count += poly.size(); + const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length))); + 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. + // 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 degrees so there is a fallback, + // as some support is better than none. + Polygons reduced_overhang_area = offset(union_ex(overhang_area), -interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); + polylines = ensure_maximum_distance_polyline( + to_polylines( + !reduced_overhang_area.empty() && + area(offset(diff_ex(overhang_area, reduced_overhang_area), std::max(interface_placer.config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? + reduced_overhang_area : + overhang_area), + connect_length, min_support_points); + } + overhang_lines = convert_lines_to_internal(interface_placer.volumes, interface_placer.config, polylines, layer_idx - dtt_roof); + } + + assert(dtt_roof <= layer_idx); + if (dtt_roof >= layer_idx && large_horizontal_roof) + // Reached buildplate when generating contact, interface and base interface layers. + interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof); + else { + // normal trees have to be generated + const bool roof_enabled = num_support_roof_layers > 0; + interface_placer.add_points_along_lines( + // Sample along these lines + overhang_lines, + // First layer index to insert the tree tips or interfaces. + layer_idx - dtt_roof, + // Remaining roof tip layers. + interface_placer.force_tip_to_roof ? num_support_roof_layers - dtt_roof : 0, + // Supports roof already? How many roof layers were already produced above these tips? + dtt_roof, + // Don't move until the following distance to top is reached. + roof_enabled ? num_support_roof_layers - dtt_roof : 0); + } +} + inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex& layer_storage_mutex, + SupportGeneratorLayerStorage& layer_storage, SupporLayerType layer_type, const SlicingParameters &slicing_params, size_t layer_idx) { - tbb::spin_mutex::scoped_lock lock(layer_storage_mutex); - layer_storage.push_back(SupportGeneratorLayer()); - return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); + auto& layer = layer_storage.allocate(layer_type); + return layer_initialize(layer, layer_type, slicing_params, layer_idx); } /*! @@ -836,205 +1212,102 @@ void generate_initial_areas( const TreeSupportSettings &config, const std::vector &overhangs, std::vector &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayerStorage &layer_storage, + InterfacePlacer &interface_placer, std::function throw_on_cancel) { using AvoidanceType = TreeModelVolumes::AvoidanceType; - static constexpr const auto base_radius = scaled(0.01); - const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); TreeSupportMeshGroupSettings mesh_group_settings(print_object); - TreeSupportSettings mesh_config{ mesh_group_settings }; - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; - 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 size_t z_distance_delta = config.z_distance_top_layers + 1; - const bool min_xy_dist = mesh_config.xy_distance > mesh_config.xy_min_distance; + const bool min_xy_dist = config.xy_distance > config.xy_min_distance; #if 0 if (mesh.overhang_areas.size() <= z_distance_delta) 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 = (config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * config.min_radius - 1.0 * config.support_line_width, 0.0); // 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 degrees angle between both remains. - const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? - mesh_config.min_radius / 2 : - sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_config.support_line_width / 2)); + const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? + config.min_radius / 2 : + scale_(sqrt(sqr(unscale(config.min_radius)) - sqr(unscale(config.min_radius - config.support_line_width / 2)))); // Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. //FIXME Vojtech: This is not sufficient for support enforcers to work. //FIXME There is no account for the support overhang angle. //FIXME There is no account for the width of the collision regions. - const coord_t extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width / 2) + (min_xy_dist ? mesh_config.support_line_width / 2 : 0) + const coord_t extra_outset = std::max(coord_t(0), config.min_radius - config.support_line_width / 2) + (min_xy_dist ? config.support_line_width / 2 : 0) //FIXME this is a heuristic value for support enforcers to work. // + 10 * mesh_config.support_line_width; ; - const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; - const bool roof_enabled = support_roof_layers != 0; - const bool force_tip_to_roof = sqr(mesh_config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; - //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). - //used by max_overhang_insert_lag, only if not min_xy_dist. - const coord_t max_overhang_speed = mesh_group_settings.support_angle < 0.5 * M_PI ? coord_t(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits::max(); + const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; + const bool roof_enabled = num_support_roof_layers > 0; + const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. // The 2*z_distance_delta is only a catch for when the support angle is very high. // Used only if not min_xy_dist. - const coord_t max_overhang_insert_lag = mesh_config.z_distance_top_layers > 0 ? - std::max(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers) : - 0; + coord_t max_overhang_insert_lag = 0; + if (config.z_distance_top_layers > 0) { + max_overhang_insert_lag = 2 * config.z_distance_top_layers; //FIXME - size_t num_support_layers = print_object.layer_count(); - std::vector> already_inserted(num_support_layers - z_distance_delta); - - std::mutex mutex_layer_storage, mutex_movebounds; - tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), - [&print_object, &volumes, &config, &overhangs, &mesh_config, &mesh_group_settings, &support_params, - z_distance_delta, min_xy_dist, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, - &base_circle, &mutex_layer_storage, &mutex_movebounds, &top_contacts, &layer_storage, &already_inserted, - &move_bounds, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - if (overhangs[layer_idx + z_distance_delta].empty()) - continue; + if (mesh_group_settings.support_angle > EPSILON && mesh_group_settings.support_angle < 0.5 * M_PI - EPSILON) { + //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). // take the least restrictive avoidance possible - Polygons relevant_forbidden; - { - const Polygons &relevant_forbidden_raw = mesh_config.support_rests_on_model ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx, min_xy_dist) : - volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); + const auto max_overhang_speed = coord_t(tan(mesh_group_settings.support_angle) * config.layer_height); + max_overhang_insert_lag = std::max(max_overhang_insert_lag, round_up_divide(config.xy_distance, max_overhang_speed / 2)); } + } - auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { - const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; - return generate_support_infill_lines(area, support_params, roof, layer_idx, support_infill_distance); - }; - - // roof_tip_layers = force_tip_to_roof ? support_roof_layers - dtt_roof : 0 - // insert_layer_idx = layer_idx - dtt_roof - // supports_roof = dtt_roof > 0 - // dont_move_until = roof_enabled ? support_roof_layers - dtt_roof : 0 - auto addLinesAsInfluenceAreas = [&](LineInformations lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) - { - auto addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) - { - bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; - bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (!mesh_config.support_rests_on_model && !to_bp) { - BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); - return; - } - Polygons circle{ base_circle }; - circle.front().translate(p.first); - { - std::lock_guard critical_section_movebounds(mutex_movebounds); - Point hash_pos = p.first / ((mesh_config.min_radius + 1) / 10); - if (! already_inserted[insert_layer].count(hash_pos)) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - already_inserted[insert_layer].emplace(hash_pos); - SupportElementState state; - state.target_height = insert_layer; - state.target_position = p.first; - state.next_position = p.first; - state.layer_idx = insert_layer; - state.effective_radius_height = dtt; - state.to_buildplate = to_bp; - state.distance_to_top = dtt; - state.result_on_layer = p.first; - assert(state.result_on_layer_is_set()); - state.increased_to_model_radius = 0; - state.to_model_gracious = gracious; - state.elephant_foot_increases = 0; - state.use_min_xy_dist = min_xy_dist; - state.supports_roof = roof; - state.dont_move_until = dont_move_until; - state.can_use_safe_radius = safe_radius; - state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; - state.skip_ovalisation = skip_ovalisation; - move_bounds[insert_layer].emplace_back(state, std::move(circle)); - } - } - }; + size_t num_support_layers; + int raft_contact_layer_idx; + // Layers with their overhang regions. + std::vector> raw_overhangs; - 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++) - { - 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; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius); - return !generate_support_infill_lines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); -#else - return true; -#endif - }; + { + const size_t num_raft_layers = config.raft_layers.size(); + const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); + num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); + raft_contact_layer_idx = generate_raft_contact(print_object, config, interface_placer); + // Enumerate layers for which the support tips may be generated from overhangs above. + raw_overhangs.reserve(num_support_layers - first_support_layer); + for (size_t layer_idx = first_support_layer; layer_idx < num_support_layers; ++ layer_idx) + if (const size_t overhang_idx = layer_idx + z_distance_delta; ! overhangs[overhang_idx].empty()) + raw_overhangs.push_back({ layer_idx, &overhangs[overhang_idx] }); + } - { - std::pair split = - // keep all lines that are still valid on the next layer - split_lines(lines, [&volumes, &config, insert_layer_idx, dtt_roof_tip](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, insert_layer_idx - dtt_roof_tip, p); }); - LineInformations points = std::move(split.second); - // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. - split = split_lines(split.first, evaluateRoofWillGenerate); - lines = std::move(split.first); - append(points, split.second); - // add all points that would not be valid - for (const LineInformation &line : points) - for (const std::pair &point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - } + RichInterfacePlacer rich_interface_placer{ interface_placer, volumes, force_tip_to_roof, num_support_layers, move_bounds }; - // add all tips as roof to the roof storage - Polygons added_roofs; - for (const LineInformation &line : lines) - //FIXME sweep the tip radius along the line? - for (const std::pair &p : line) { - Polygon roof_circle{ base_circle }; - roof_circle.scale(mesh_config.min_radius / base_radius); - roof_circle.translate(p.first); - added_roofs.emplace_back(std::move(roof_circle)); - } - if (! added_roofs.empty()) { - added_roofs = union_(added_roofs); - { - std::lock_guard lock(mutex_layer_storage); - SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::sltTopContact, print_object.slicing_parameters(), insert_layer_idx - dtt_roof_tip); - append(l->polygons, std::move(added_roofs)); - } - } - } + tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), + [&volumes, &config, &raw_overhangs, &mesh_group_settings, + min_xy_dist, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, + &rich_interface_placer, &throw_on_cancel](const tbb::blocked_range &range) { + for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) { + size_t layer_idx = raw_overhangs[raw_overhang_idx].first; + const Polygons &overhang_raw = *raw_overhangs[raw_overhang_idx].second; - 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); - } - }; + // take the least restrictive avoidance possible + Polygons relevant_forbidden; + { + const Polygons &relevant_forbidden_raw = config.support_rests_on_model ? + volumes.getCollision(config.getRadius(0), layer_idx, min_xy_dist) : + volumes.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); + } // 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; { - const Polygons &overhang_raw = overhangs[layer_idx + z_distance_delta]; // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. - overhang_regular = safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); + overhang_regular = safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, config.min_radius * 1.75 + config.xy_min_distance, 0, 1); //check_self_intersections(overhang_regular, "overhang_regular1"); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang @@ -1042,22 +1315,22 @@ void generate_initial_areas( diff(mesh_group_settings.support_offset == 0 ? overhang_raw : 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)), + offset(union_ex(overhang_regular), config.support_line_width * 0.5, jtMiter, 1.2)), relevant_forbidden); // 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. - //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. + //+config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. //FIXME likely a better approach would be to find correspondences between the full overhang and the trimmed overhang // and if there is no correspondence, project the missing points to the clipping curve. - for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset; ) { + for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + config.support_line_width / 8 < extra_outset; ) { const coord_t offset_current_step = std::min( - extra_total_offset_acc + 2 * mesh_config.support_line_width > mesh_config.min_radius ? - mesh_config.support_line_width / 8 : + extra_total_offset_acc + 2 * config.support_line_width > config.min_radius ? + config.support_line_width / 8 : circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; const Polygons &raw_collision = volumes.getCollision(0, layer_idx, true); - const coord_t offset_step = mesh_config.xy_min_distance + mesh_config.support_line_width; + const coord_t offset_step = config.xy_min_distance + config.support_line_width; // Reducing the remaining overhang by the areas already supported. //FIXME 1.5 * extra_total_offset_acc seems to be too much, it may remove some remaining overhang without being supported at all. remaining_overhang = diff(remaining_overhang, safe_offset_inc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); @@ -1065,6 +1338,7 @@ void generate_initial_areas( overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); //check_self_intersections(overhang_regular, "overhang_regular2"); } +#if 0 // 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 (! min_xy_dist) { @@ -1076,7 +1350,9 @@ void generate_initial_areas( // 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 = ensure_maximum_distance_polyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); + Polylines polylines = ensure_maximum_distance_polyline( + generate_support_infill_lines(remaining_overhang, support_params, false, layer_idx, mesh_group_settings.support_tree_branch_distance), + config.min_radius, 1); if (polylines.size() <= 3) // add the outer wall to ensure it is correct supported instead polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); @@ -1090,9 +1366,9 @@ void generate_initial_areas( } 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 ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : - volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); + const Polygons &relevant_forbidden_below = config.support_rests_on_model ? + volumes.getCollision(config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : + volumes.getAvoidance(config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; @@ -1102,146 +1378,42 @@ void generate_initial_areas( LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); validate_range(fresh_valid_points); - addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? support_roof_layers : 0); + rich_interface_placer.add_points_along_lines(fresh_valid_points, (force_tip_to_roof && lag_ctr <= num_support_roof_layers) ? num_support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? num_support_roof_layers : 0); } } +#endif } throw_on_cancel(); - Polygons overhang_roofs; - std::vector> overhang_processing; if (roof_enabled) { static constexpr const coord_t support_roof_offset = 0; - overhang_roofs = safe_offset_inc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); + Polygons overhang_roofs = safe_offset_inc(overhang_raw, support_roof_offset, relevant_forbidden, config.min_radius * 2 + config.xy_min_distance, 0, 1); if (mesh_group_settings.minimum_support_area > 0) remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); overhang_regular = diff(overhang_regular, overhang_roofs, ApplySafetyOffset::Yes); //check_self_intersections(overhang_regular, "overhang_regular3"); - for (ExPolygon &roof_part : union_ex(overhang_roofs)) - overhang_processing.emplace_back(std::move(roof_part), true); + for (ExPolygon &roof_part : union_ex(overhang_roofs)) { + sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, rich_interface_placer); + throw_on_cancel(); + } } + // Either the roof is not enabled, then these are all the overhangs to be supported, + // or roof is enabled and these are the thin overhangs at object slopes (not horizontal overhangs). if (mesh_group_settings.minimum_support_area > 0) remove_small(overhang_regular, mesh_group_settings.minimum_support_area); - for (ExPolygon &support_part : union_ex(overhang_regular)) - overhang_processing.emplace_back(std::move(support_part), false); - - for (const std::pair &overhang_pair : overhang_processing) { - const bool roof_allowed_for_this_part = overhang_pair.second; - 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; - // 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. - std::vector added_roofs(support_roof_layers); - - // 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; - { - const Polygons &forbidden_next_raw = mesh_config.support_rests_on_model ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : - volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line - //FIXME maybe use SafetyOffset::Yes at the following diff() instead? - forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); - } - 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 - if (dtt_roof != 0) { - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; - // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. - overhang_lines = convert_lines_to_internal(volumes, config, - ensure_maximum_distance_polyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = split_lines(overhang_lines, - [&volumes, &config, layer_idx, dtt_before](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, layer_idx - dtt_before, p); }) - .first; - } - break; - } - 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 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()) { - dtt_roof = idx; - layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; - break; - } - } - - { - std::lock_guard lock(mutex_layer_storage); - 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::sltTopContact, print_object.slicing_parameters(), layer_idx - idx); - // will be unioned in finalize_interface_and_support_areas() - append(l->polygons, std::move(added_roofs[idx])); - } - } - - 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 = ensure_maximum_distance_polyline( - generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); - size_t point_count = 0; - for (const Polyline &poly : polylines) - point_count += poly.size(); - 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. - // 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, jtMiter, 1.2); - polylines = ensure_maximum_distance_polyline( - to_polylines( - ! reduced_overhang_outset.empty() && - area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? - reduced_overhang_outset : - overhang_outset), - connect_length, min_support_points); - } - LayerIndex last_insert_layer = layer_idx - dtt_roof; - overhang_lines = convert_lines_to_internal(volumes, config, polylines, last_insert_layer); - } - - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { - // reached buildplate - std::lock_guard lock(mutex_layer_storage); - SupportGeneratorLayer*& l = top_contacts[0]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::sltTopContact, 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); + for (ExPolygon &support_part : union_ex(overhang_regular)) { + sample_overhang_area(to_polygons(std::move(support_part)), + false, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, rich_interface_placer); throw_on_cancel(); } } }); + + finalize_raft_contact(print_object, raft_contact_layer_idx, interface_placer.top_contacts_mutable(), move_bounds); } static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) @@ -1391,7 +1563,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di Polygons check_layer_data; if (settings.increase_radius) current_elem.effective_radius_height += 1; - coord_t radius = config.getCollisionRadius(current_elem); + coord_t radius = support_element_collision_radius(config, current_elem); if (settings.move) { increased = relevant_offset; @@ -1401,7 +1573,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(config.getCollisionRadius(parent.state), layer_idx, parent.state.use_min_xy_dist), + increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) @@ -1456,8 +1628,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di }; 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); + if (support_element_collision_radius(config, current_elem) < config.increase_radius_until_radius && support_element_collision_radius(config, current_elem) < support_element_radius(config, current_elem)) { + coord_t target_radius = std::min(support_element_radius(config, 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))) @@ -1465,24 +1637,24 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di 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)) + config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= support_element_radius(config, current_elem)) ++ resulting_eff_dtt; current_elem.effective_radius_height = resulting_eff_dtt; } - radius = config.getCollisionRadius(current_elem); + radius = support_element_collision_radius(config, 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)); + const coord_t foot_radius_increase = std::max(config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer, 0.0); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, // which could cause the radius to become bigger than precalculated. - double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); + double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - support_element_radius(config, 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 && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) + if (increase_bp_foot && support_element_radius(config, current_elem) >= config.branch_radius && support_element_radius(config, 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); + radius = support_element_collision_radius(config, current_elem); } if (ceil_radius_before != volumes.ceilRadius(radius, settings.use_min_distance)) { @@ -1497,7 +1669,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (area(check_layer_data) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << - volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); + volumes.ceilRadius(support_element_collision_radius(config, current_elem), settings.use_min_distance); tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); } } @@ -1575,7 +1747,7 @@ static void increase_areas_one_layer( { using AvoidanceType = TreeModelVolumes::AvoidanceType; - tbb::parallel_for(tbb::blocked_range(0, merging_areas.size()), + tbb::parallel_for(tbb::blocked_range(0, merging_areas.size(), 1), [&](const tbb::blocked_range &range) { for (size_t merging_area_idx = range.begin(); merging_area_idx < range.end(); ++ merging_area_idx) { SupportElementMerging &merging_area = merging_areas[merging_area_idx]; @@ -1584,7 +1756,7 @@ static void increase_areas_one_layer( SupportElementState elem = SupportElementState::propagate_down(parent.state); const Polygons &wall_restriction = // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. - volumes.getWallRestriction(config.getCollisionRadius(parent.state), layer_idx, parent.state.use_min_xy_dist); + volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist); #ifdef TREESUPPORT_DEBUG_SVG SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)), @@ -1593,7 +1765,7 @@ static void increase_areas_one_layer( #endif // TREESUPPORT_DEBUG_SVG Polygons to_bp_data, to_model_data; - coord_t radius = config.getCollisionRadius(elem); + coord_t radius = support_element_collision_radius(config, 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. @@ -1602,9 +1774,9 @@ static void increase_areas_one_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.state), parent.state.use_min_xy_dist); + const coord_t ceiled_parent_radius = volumes.ceilRadius(support_element_collision_radius(config, parent.state), parent.state.use_min_xy_dist); coord_t projected_radius_increased = config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases); - coord_t projected_radius_delta = projected_radius_increased - config.getCollisionRadius(parent.state); + coord_t projected_radius_delta = projected_radius_increased - support_element_collision_radius(config, parent.state); // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): /* @@ -1629,9 +1801,9 @@ static void increase_areas_one_layer( 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.state.effective_radius_height + 1, parent.state.elephant_foot_increases + 1), parent.state.use_min_xy_dist)) - extra_speed += config.branch_radius * config.diameter_scale_bp_radius; + extra_speed += config.bp_radius_increase_per_layer; else - extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), + extra_slow_speed += std::min(coord_t(config.bp_radius_increase_per_layer), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); } @@ -1760,7 +1932,7 @@ static void increase_areas_one_layer( "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.layer_idx << " 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: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << + "Parent " << &parent << ": Radius: " << support_element_collision_radius(config, parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; tree_supports_show_error("Potentially lost branch!"sv, true); @@ -1770,7 +1942,7 @@ static void increase_areas_one_layer( if (result) { elem = *result; - radius = config.getCollisionRadius(elem); + radius = support_element_collision_radius(config, elem); elem.last_area_increase = settings; add = true; // do not merge if the branch should not move or the priority has to be to get farther away from the model. @@ -1817,10 +1989,11 @@ static void increase_areas_one_layer( // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); + parent.state.to_model_gracious = false; } throw_on_cancel(); } - }); + }, tbb::simple_partitioner()); } [[nodiscard]] static SupportElementState merge_support_element_states( @@ -1850,11 +2023,11 @@ static void increase_areas_one_layer( out.to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious out.elephant_foot_increases = 0; - if (config.diameter_scale_bp_radius > 0) { - coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(out)); + if (config.bp_radius_increase_per_layer > 0) { + coord_t foot_increase_radius = std::abs(std::max(support_element_collision_radius(config, second), support_element_collision_radius(config, first)) - support_element_collision_radius(config, out)); // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. - out.elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); + out.elephant_foot_increases = foot_increase_radius / (config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer); } // 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. @@ -1883,10 +2056,10 @@ static bool merge_influence_areas_two_elements( if (merging_gracious_and_non_gracious || merging_min_and_regular_xy) return false; - const bool dst_radius_bigger = config.getCollisionRadius(dst.state) > config.getCollisionRadius(src.state); + const bool dst_radius_bigger = support_element_collision_radius(config, dst.state) > support_element_collision_radius(config, src.state); const SupportElementMerging &smaller_rad = dst_radius_bigger ? src : dst; const SupportElementMerging &bigger_rad = dst_radius_bigger ? dst : src; - const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.state) - config.getRadius(smaller_rad.state)); + const coord_t real_radius_delta = std::abs(support_element_radius(config, bigger_rad.state) - support_element_radius(config, smaller_rad.state)); { // Testing intersection of bounding boxes. // Expand the smaller radius branch bounding box to match the lambda intersect_small_with_bigger() below. @@ -1908,8 +2081,8 @@ static bool merge_influence_areas_two_elements( if (dst.state.to_buildplate != src.state.to_buildplate) { // Merging a "to build plate" branch with a "to model" branch. // Don't allow merging a thick "to build plate" branch into a thinner "to model" branch. - const coord_t rdst = config.getRadius(dst.state); - const coord_t rsrc = config.getRadius(src.state); + const coord_t rdst = support_element_radius(config, dst.state); + const coord_t rsrc = support_element_radius(config, src.state); if (dst.state.to_buildplate) { if (rsrc < rdst) increased_to_model_radius = src.state.increased_to_model_radius + rdst - rsrc; @@ -1943,7 +2116,7 @@ static bool merge_influence_areas_two_elements( // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. // Remember that collision radius <= real radius as otherwise this assumption would be false. - const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.state); + const coord_t smaller_collision_radius = support_element_collision_radius(config, smaller_rad.state); const Polygons &collision = volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius); auto intersect_small_with_bigger = [real_radius_delta, smaller_collision_radius, &collision, &config](const Polygons &small, const Polygons &bigger) { return intersection( @@ -2175,7 +2348,6 @@ static void merge_influence_areas( tbb::parallel_for(tbb::blocked_range(0, num_buckets_initial), [&](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const size_t bucket_pair_idx = idx * 2; // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second); throw_on_cancel(); @@ -2369,7 +2541,7 @@ static void set_to_model_contact_to_model_gracious( { SupportElement *elem = &first_elem; for (LayerIndex layer_check = elem->state.layer_idx; - ! intersection(elem->influence_area, volumes.getPlaceableAreas(config.getCollisionRadius(elem->state), layer_check, throw_on_cancel)).empty(); + ! intersection(elem->influence_area, volumes.getPlaceableAreas(support_element_collision_radius(config, elem->state), layer_check, throw_on_cancel)).empty(); elem = &move_bounds[++ layer_check][elem->parents.front()]) { assert(elem->state.layer_idx == layer_check); assert(! elem->state.deleted); @@ -2420,13 +2592,13 @@ static void remove_deleted_elements(std::vector &move_bounds) std::iota(map_current.begin(), map_current.end(), 0); } // Delete all "deleted" elements from the end of the layer vector. - while (i < layer.size() && layer.back().state.deleted) { + while (i < int32_t(layer.size()) && layer.back().state.deleted) { layer.pop_back(); // Mark as deleted in the map. map_current[layer.size()] = -1; } assert(i == layer.size() || i + 1 < layer.size()); - if (i + 1 < layer.size()) { + if (i + 1 < int32_t(layer.size())) { element = std::move(layer.back()); layer.pop_back(); // Mark the current element as deleted. @@ -2461,8 +2633,10 @@ void create_nodes_from_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. { SupportElements *layer_above = move_bounds.size() > 1 ? &move_bounds[1] : nullptr; - for (SupportElement &elem : *layer_above) - elem.state.marked = false; + if (layer_above) { + for (SupportElement &elem : *layer_above) + elem.state.marked = false; + } for (SupportElement &init : move_bounds.front()) { init.state.result_on_layer = move_inside_if_outside(init.influence_area, init.state.next_position); // Also set the parent nodes, as these will be required for the first iteration of the loop below and mark the parent nodes. @@ -2474,7 +2648,7 @@ void create_nodes_from_area( for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { auto &layer = move_bounds[layer_idx]; - auto *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + auto *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; if (layer_above) for (SupportElement &elem : *layer_above) elem.state.marked = false; @@ -2528,7 +2702,7 @@ void create_nodes_from_area( assert(! parent.state.deleted); assert(elem.state.result_on_layer_is_set() == parent.state.result_on_layer_is_set()); if (elem.state.result_on_layer_is_set()) { - double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state); + double radius_increase = support_element_radius(config, elem) - support_element_radius(config, parent); assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); //FIXME this assert fails a lot. Is it correct? @@ -2553,7 +2727,7 @@ void create_nodes_from_area( assert(! parent.state.deleted); assert(elem.state.result_on_layer_is_set() == parent.state.result_on_layer_is_set()); if (elem.state.result_on_layer_is_set()) { - double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state); + double radius_increase = support_element_radius(config, elem) - support_element_radius(config, parent); assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); //FIXME this assert fails a lot. Is it correct? @@ -2606,7 +2780,7 @@ static void generate_branch_areas( for (size_t idx = range.begin(); idx < range.end(); ++ idx) { DrawArea &draw_area = linear_data[idx]; const LayerIndex layer_idx = draw_area.element->state.layer_idx; - const coord_t radius = config.getRadius(*draw_area.element); + const coord_t radius = support_element_radius(config, *draw_area.element); 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. @@ -2616,12 +2790,12 @@ static void generate_branch_areas( const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer; movement_directions.emplace_back(movement, radius); } - const SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + const SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; for (int32_t parent_idx : draw_area.element->parents) { const SupportElement &parent = (*layer_above)[parent_idx]; const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer; //FIXME why max(..., config.support_line_width)? - movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); + movement_directions.emplace_back(movement, std::max(support_element_radius(config, parent), config.support_line_width)); parent_uses_min |= parent.state.use_min_xy_dist; } } @@ -2665,7 +2839,7 @@ static void generate_branch_areas( Polygons polygons = generateArea(0, max_speed); const bool fast_relative_movement = max_speed > radius * 0.75; - if (fast_relative_movement || config.getRadius(*draw_area.element) - config.getCollisionRadius(draw_area.element->state) > config.support_line_width) { + if (fast_relative_movement || support_element_radius(config, *draw_area.element) - support_element_collision_radius(config, draw_area.element->state) > config.support_line_width) { // Simulate the path the nozzle will take on the outermost wall. // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. ExPolygons nozzle_path = offset_ex(polygons, - config.support_line_width / 2); @@ -2743,9 +2917,10 @@ static void smooth_branch_areas( for (int32_t parent_idx : draw_area.element->parents) { const SupportElement &parent = layer_above[parent_idx]; assert(parent.state.layer_idx == layer_idx + 1); - if (config.getRadius(parent.state) != config.getCollisionRadius(parent.state)) { + if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (config.getRadius(*draw_area.element) - config.getRadius(parent))); + max_outer_wall_distance = std::max(max_outer_wall_distance, + (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (support_element_radius(config, *draw_area.element) - support_element_radius(config, 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. @@ -2757,12 +2932,12 @@ static void smooth_branch_areas( #ifndef NDEBUG assert(parent.state.layer_idx == layer_idx + 1); assert(contains(linear_data[processing_base_above + parent_idx].polygons, parent.state.result_on_layer)); - double radius_increase = config.getRadius(draw_area.element->state) - config.getRadius(parent.state); + double radius_increase = support_element_radius(config, *draw_area.element) - support_element_radius(config, parent); assert(radius_increase >= 0); double shift = (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm(); assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); #endif // NDEBUG - if (config.getRadius(parent.state) != config.getCollisionRadius(parent.state)) { + if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { // No other element on this layer than the current one may be connected to &parent, // thus it is safe to update parent's DrawArea directly. Polygons &dst = linear_data[processing_base_above + parent_idx].polygons; @@ -2815,7 +2990,7 @@ static void smooth_branch_areas( for (Point& p : outer) p += direction; append(max_allowed_area, std::move(result)); - do_something = do_something || parent.state.marked || config.getCollisionRadius(parent.state) != config.getRadius(parent.state); + do_something = do_something || parent.state.marked || support_element_collision_radius(config, parent) != support_element_radius(config, parent); } if (do_something) { // Trim the current drawing areas with max_allowed_area. @@ -2895,6 +3070,8 @@ static void finalize_interface_and_support_areas( std::function throw_on_cancel) { + assert(std::all_of(bottom_contacts.begin(), bottom_contacts.end(), [](auto *p) { return p == nullptr; })); + assert(std::all_of(intermediate_layers.begin(), intermediate_layers.end(), [](auto* p) { return p == nullptr; })); InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SupportLinesOverwriteInterface; #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -2902,33 +3079,42 @@ static void finalize_interface_and_support_areas( #endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. - 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) { - // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), 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(config.resolution))); // Subtract support lines of the branches from the roof - SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; - if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { - if (support_roof == nullptr) { - support_roof = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::sltTopContact, print_object.slicing_parameters(), layer_idx); - support_roof->polygons = union_(support_roof_storage[layer_idx]); + SupportGeneratorLayer *support_roof = top_contacts[layer_idx]; + Polygons support_roof_polygons; + if (Polygons &src = support_roof_storage[layer_idx]; ! src.empty()) { + if (support_roof != nullptr && ! support_roof->polygons.empty()) { + support_roof_polygons = union_(src, support_roof->polygons); + support_roof->polygons.clear(); } else - support_roof->polygons = union_(support_roof->polygons, support_roof_storage[layer_idx]); + support_roof_polygons = std::move(src); + } else if (support_roof != nullptr) { + support_roof_polygons = std::move(support_roof->polygons); + support_roof->polygons.clear(); + } - if (! support_roof->polygons.empty() && - area(intersection(support_layer_storage[layer_idx], support_roof->polygons)) > tiny_area_threshold) { + //assert(intermediate_layers[layer_idx] == nullptr); + Polygons base_layer_polygons = std::move(support_layer_storage[layer_idx]); + + if (! base_layer_polygons.empty()) { + // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. + base_layer_polygons = smooth_outward(union_(base_layer_polygons), 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. + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution))); + } + if (! support_roof_polygons.empty() && ! base_layer_polygons.empty()) { +// if (area(intersection(base_layer_polygons, support_roof_polygons)) > tiny_area_threshold) + { switch (interface_pref) { case InterfacePreference::InterfaceAreaOverwritesSupport: - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], support_roof->polygons); + base_layer_polygons = diff(base_layer_polygons, support_roof_polygons); break; case InterfacePreference::SupportAreaOverwritesInterface: - support_roof->polygons = diff(support_roof->polygons, support_layer_storage[layer_idx]); + support_roof_polygons = diff(support_roof_polygons, base_layer_polygons); break; //FIXME #if 1 @@ -2943,14 +3129,14 @@ static void finalize_interface_and_support_areas( Polygons interface_lines = offset(to_polylines( generate_support_infill_lines(support_roof->polygons, true, layer_idx, config.support_roof_line_distance)), config.support_roof_line_width / 2); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); + base_layer_polygons = diff(base_layer_polygons, interface_lines); break; } case InterfacePreference::SupportLinesOverwriteInterface: { // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. Polygons tree_lines = union_(offset(to_polylines( - generate_support_infill_lines(support_layer_storage[layer_idx], false, layer_idx, config.support_line_distance, true)), + generate_support_infill_lines(base_layer_polygons, false, layer_idx, config.support_line_distance, true)), config.support_line_width / 2)); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. support_roof->polygons = diff(support_roof->polygons, tree_lines); @@ -2964,10 +3150,10 @@ static void finalize_interface_and_support_areas( } // 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 (config.support_bottom_layers > 0 && ! base_layer_polygons.empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; Polygons layer_outset = diff_clipped( - config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], + config.support_bottom_offset > 0 ? offset(base_layer_polygons, config.support_bottom_offset, jtMiter, 1.2) : base_layer_polygons, volumes.getCollision(0, layer_idx, false)); Polygons floor_layer; size_t layers_below = 0; @@ -2977,23 +3163,26 @@ static void finalize_interface_and_support_areas( //FIXME subtract the wipe tower append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); if (layers_below < config.support_bottom_layers) - layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); + layers_below = std::min(layers_below + 1, config.support_bottom_layers); else break; } if (! floor_layer.empty()) { if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::sltBottomContact, print_object.slicing_parameters(), layer_idx); + support_bottom = &layer_allocate(layer_storage, SupporLayerType::sltBottomContact, print_object.slicing_parameters(), config, layer_idx); support_bottom->polygons = union_(floor_layer, support_bottom->polygons); - 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. + base_layer_polygons = diff_clipped(base_layer_polygons, 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, layer_storage_mutex, SupporLayerType::sltBase, print_object.slicing_parameters(), layer_idx); - append(l->polygons, union_(support_layer_storage[layer_idx])); + if (! support_roof_polygons.empty()) { + if (support_roof == nullptr) + support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::sltTopContact, print_object.slicing_parameters(), config, layer_idx); + support_roof->polygons = union_(support_roof_polygons); + } + if (! base_layer_polygons.empty()) { + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::sltBase, print_object.slicing_parameters(), config, layer_idx); + base_layer->polygons = union_(base_layer_polygons); } #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -3043,7 +3232,7 @@ static void draw_areas( std::vector> map_downwards_old; std::vector> map_downwards_new; for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; map_downwards_new.clear(); linear_data_layers.emplace_back(linear_data.size()); std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; }); @@ -3317,8 +3506,9 @@ static std::pair discretize_polygon(const Vec3f& center, const Polygon return { begin, int(pts.size()) }; } -static void extrude_branch( - const std::vector &path, +// Returns Z span of the generated mesh. +static std::pair extrude_branch( + const std::vector&path, const TreeSupportSettings &config, const SlicingParameters &slicing_params, const std::vector &move_bounds, @@ -3331,26 +3521,26 @@ static void extrude_branch( assert(path.size() >= 2); static constexpr const float eps = 0.015f; std::pair prev_strip; - -// char fname[2048]; -// static int irun = 0; + float zmin = 0; + float zmax = 0; for (size_t ipath = 1; ipath < path.size(); ++ ipath) { const SupportElement &prev = *path[ipath - 1]; const SupportElement ¤t = *path[ipath]; assert(prev.state.layer_idx + 1 == current.state.layer_idx); - p1 = to_3d(unscaled(prev .state.result_on_layer), layer_z(slicing_params, prev .state.layer_idx)); - p2 = to_3d(unscaled(current.state.result_on_layer), layer_z(slicing_params, current.state.layer_idx)); + p1 = to_3d(unscaled(prev .state.result_on_layer), layer_z(slicing_params, config, prev .state.layer_idx)); + p2 = to_3d(unscaled(current.state.result_on_layer), layer_z(slicing_params, config, current.state.layer_idx)); v1 = (p2 - p1).normalized(); if (ipath == 1) { nprev = v1; // Extrude the bottom half sphere. - float radius = unscaled(config.getRadius(prev.state)); + float radius = unscaled(support_element_radius(config, prev)); float angle_step = 2. * acos(1. - eps / radius); auto nsteps = int(ceil(M_PI / (2. * angle_step))); angle_step = M_PI / (2. * nsteps); int ifan = int(result.vertices.size()); result.vertices.emplace_back((p1 - nprev * radius).cast()); + zmin = result.vertices.back().z(); float angle = angle_step; std::pair strip; if (current.state.type == TreeSupport::NodeType::ePolygon) { @@ -3360,9 +3550,7 @@ static void extrude_branch( } else { for (int i = 1; i < nsteps; ++i, angle += angle_step) { - if (current.state.type != TreeSupport::NodeType::ePolygon) { - strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast(), nprev.cast(), radius * sin(angle), eps, result.vertices); - } + strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast(), nprev.cast(), radius * sin(angle), eps, result.vertices); if (i == 1) triangulate_fan(result, ifan, strip.first, strip.second); else @@ -3377,7 +3565,7 @@ static void extrude_branch( // End of the tube. ncurrent = v1; // Extrude the top half sphere. - float radius = unscaled(config.getRadius(current.state)); + float radius = unscaled(support_element_radius(config, current)); float angle_step = 2. * acos(1. - eps / radius); auto nsteps = int(ceil(M_PI / (2. * angle_step))); angle_step = M_PI / (2. * nsteps); @@ -3396,6 +3584,7 @@ static void extrude_branch( } int ifan = int(result.vertices.size()); result.vertices.emplace_back((p2 + ncurrent * radius).cast()); + zmax = result.vertices.back().z(); triangulate_fan(result, ifan, prev_strip.first, prev_strip.second); // sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); // its_write_obj(result, fname); @@ -3403,10 +3592,10 @@ static void extrude_branch( } else { const SupportElement &next = *path[ipath + 1]; assert(current.state.layer_idx + 1 == next.state.layer_idx); - p3 = to_3d(unscaled(next.state.result_on_layer), layer_z(slicing_params, next.state.layer_idx)); + p3 = to_3d(unscaled(next.state.result_on_layer), layer_z(slicing_params, config, next.state.layer_idx)); v2 = (p3 - p2).normalized(); ncurrent = (v1 + v2).normalized(); - float radius = unscaled(config.getRadius(current.state)); + float radius = unscaled(support_element_radius(config, current)); std::pair strip; if (current.state.type == TreeSupport::NodeType::ePolygon) { strip = discretize_polygon(p2.cast(), current.influence_area, result.vertices); @@ -3420,7 +3609,7 @@ static void extrude_branch( // its_write_obj(result, fname); } #if 0 - if (circles_intersect(p1, nprev, settings.getRadius(prev), p2, ncurrent, settings.getRadius(current))) { + if (circles_intersect(p1, nprev, support_element_radius(settings, prev), p2, ncurrent, support_element_radius(settings, current))) { // Cannot connect previous and current slice using a simple zig-zag triangulation, // because the two circles intersect. @@ -3430,6 +3619,8 @@ static void extrude_branch( } #endif } + + return std::make_pair(zmin, zmax); } #endif @@ -3462,7 +3653,7 @@ void organic_smooth_branches_avoid_collisions( layer_collision_cache.resize(num_layers, {}); } auto& l = layer_collision_cache[layer_idx]; - l.min_element_radius = std::min(l.min_element_radius, config.getRadius(element.first->state)); + l.min_element_radius = std::min(l.min_element_radius, support_element_radius(config, *element.first)); } throw_on_cancel(); @@ -3516,9 +3707,9 @@ void organic_smooth_branches_avoid_collisions( link_down, // locked element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0), - element.state.radius(config.getRadius(element.state)):float(element.state.radius), + unscaled(support_element_radius(config, element)), // 3D position - to_3d(unscaled(element.state.result_on_layer), element.state.print_z(element.state.result_on_layer), float(layer_z(slicing_params, config, element.state.layer_idx))) }); // Update min_z coordinate to min_z of the tree below. CollisionSphere &collision_sphere = collision_spheres.back(); @@ -3549,8 +3740,9 @@ void organic_smooth_branches_avoid_collisions( //FIXME limit the collision span by the tree slope. collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius); collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius); - collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, collision_sphere.min_z)); - collision_sphere.layer_end = std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, collision_sphere.max_z)) + 1; + collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, config, collision_sphere.min_z)); + assert(collision_sphere.layer_begin < layer_collision_cache.size()); + collision_sphere.layer_end = std::min(LayerIndex(layer_collision_cache.size()), std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, config, collision_sphere.max_z)) + 1); } throw_on_cancel(); @@ -3565,7 +3757,7 @@ void organic_smooth_branches_avoid_collisions( collision_sphere.prev_position = collision_sphere.position; std::atomic num_moved{ 0 }; tbb::parallel_for(tbb::blocked_range(0, collision_spheres.size()), - [&collision_spheres, &layer_collision_cache, &slicing_params, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range range) { + [&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range range) { for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id) if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) { // Calculate collision of multiple 2D layers against a collision sphere. @@ -3584,7 +3776,7 @@ void organic_smooth_branches_avoid_collisions( double collision_depth = sqrt(r2) - dist; if (collision_depth > collision_sphere.last_collision_depth) { collision_sphere.last_collision_depth = collision_depth; - collision_sphere.last_collision = to_3d(hit_point_out.cast(), float(layer_z(slicing_params, layer_id))); + collision_sphere.last_collision = to_3d(hit_point_out.cast(), float(layer_z(slicing_params, config, layer_id))); } } } @@ -3663,7 +3855,7 @@ static void organic_smooth_branches_avoid_collisions( std::vector pts, prev, projections; std::vector distances; for (const std::pair& element : elements_with_link_down) { - Vec3d pt = to_3d(unscaled(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), element.first->state.layer_idx)) * scale; + Vec3d pt = to_3d(unscaled(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), config, element.first->state.layer_idx)) * scale; pts.push_back({ pt.x(), pt.y(), pt.z() }); } @@ -3681,13 +3873,13 @@ static void organic_smooth_branches_avoid_collisions( for (size_t i = 0; i < projections.size(); ++ i) { const SupportElement &element = *elements_with_link_down[i].first; const int below = elements_with_link_down[i].second; - const bool locked = below == -1 && element.state.layer_idx > 0; + const bool locked = (below == -1 && element.state.layer_idx > 0) || element.state.locked(); if (! locked && pts[i] != projections[i]) { // Nudge the circle center away from the collision. Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; double depth = v.norm(); assert(std::abs(distances[i] - depth) < EPSILON); - double radius = unscaled(config.getRadius(element.state)) * scale; + double radius = unscaled(support_element_radius(config, element)) * scale; if (depth < radius) { // Collision detected to be removed. ++ num_moved; @@ -3709,7 +3901,7 @@ static void organic_smooth_branches_avoid_collisions( const size_t offset_above = linear_data_layers[element.state.layer_idx + 1]; double weight = 0.; for (auto iparent : element.parents) { - double w = config.getRadius(above[iparent].state); + double w = support_element_radius(config, above[iparent]); avg.x() += w * prev[offset_above + iparent].x(); avg.y() += w * prev[offset_above + iparent].y(); weight += w; @@ -3809,7 +4001,7 @@ indexed_triangle_set draw_branches( // Traverse all nodes, generate tubes. // Traversal stack with nodes and thier current parent const SlicingParameters &slicing_params = print_object.slicing_parameters(); - std::vector path; + std::vector path; indexed_triangle_set cummulative_mesh; indexed_triangle_set partial_mesh; indexed_triangle_set temp_mesh; @@ -3941,9 +4133,6 @@ void slice_branches( */ static void generate_support_areas(Print &print, TreeSupport* tree_support, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) { - g_showed_critical_error = false; - g_showed_performance_warning = false; - // Settings with the indexes of meshes that use these settings. std::vector>> grouped_meshes = group_meshes(print, print_object_ids); if (grouped_meshes.empty()) @@ -3958,7 +4147,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. //FIXME this is a copy // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - const TreeSupportSettings &config = processing.first; + TreeSupportSettings &config = processing.first; BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; auto t_start = std::chrono::high_resolution_clock::now(); @@ -3978,104 +4167,150 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons assert(processing.second.size() == 1); print.set_status(55, _L("Support: detect overhangs")); +#if 0 std::vector overhangs; tree_support->detect_overhangs(); - overhangs.resize(print_object.support_layer_count()); - for (size_t i = 0; i < overhangs.size(); i++) + const int num_raft_layers = int(config.raft_layers.size()); + const int num_layers = int(print_object.layer_count()) + num_raft_layers; + overhangs.resize(num_layers); + for (size_t i = 0; i < print_object.layer_count(); i++) { - overhangs[i] = to_polygons(print_object.get_support_layer(i)->overhang_areas); + overhangs[i + num_raft_layers] = to_polygons(print_object.get_support_layer(i)->overhang_areas); } print_object.clear_support_layers(); - +#else + std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); +#endif // ### Precalculate avoidances, collision etc. size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); if (num_support_layers == 0) continue; - auto t_precalc = std::chrono::high_resolution_clock::now(); + bool has_support = num_support_layers > 0; + bool has_raft = config.raft_layers.size() > 0; + num_support_layers = std::max(num_support_layers, config.raft_layers.size()); - // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas - std::vector move_bounds(num_support_layers); + SupportParameters support_params(print_object); + support_params.with_sheath = true; + // organic support default pattern is none. + if (config.support_pattern == smpDefault) { + config.support_pattern = smpNone; + support_params.support_density = 0; + } - // ### Place tips of the support tree - SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); SupportGeneratorLayerStorage layer_storage; + SupportGeneratorLayersPtr top_contacts; + SupportGeneratorLayersPtr bottom_contacts; + SupportGeneratorLayersPtr interface_layers; + SupportGeneratorLayersPtr base_interface_layers; + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + if (support_params.has_top_contacts || has_raft) + top_contacts.assign(num_support_layers, nullptr); + if (support_params.has_bottom_contacts) + bottom_contacts.assign(num_support_layers, nullptr); + if (support_params.has_interfaces() || has_raft) + interface_layers.assign(num_support_layers, nullptr); + if (support_params.has_base_interfaces() || has_raft) + base_interface_layers.assign(num_support_layers, nullptr); + + auto remove_undefined_layers = [&bottom_contacts, &top_contacts, &interface_layers, &base_interface_layers, &intermediate_layers]() { + auto doit = [](SupportGeneratorLayersPtr& layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + doit(bottom_contacts); + doit(top_contacts); + doit(interface_layers); + doit(base_interface_layers); + doit(intermediate_layers); + }; - for (size_t mesh_idx : processing.second) - generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, move_bounds, top_contacts, layer_storage, throw_on_cancel); - auto t_gen = std::chrono::high_resolution_clock::now(); + InterfacePlacer interface_placer{ + print_object.slicing_parameters(), support_params, config, + // Outputs + layer_storage, top_contacts, interface_layers, base_interface_layers }; - // save num of points to log - for (size_t i = 0; i < move_bounds.size(); i++) - BOOST_LOG_TRIVIAL(info) << "Number of points in move_bound: " << move_bounds[i].size() << " in layer " << i; + + if (has_support) { + auto t_precalc = std::chrono::high_resolution_clock::now(); + // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas + std::vector move_bounds(num_support_layers); + + + for (size_t mesh_idx : processing.second) + generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, + move_bounds, interface_placer, throw_on_cancel); + auto t_gen = std::chrono::high_resolution_clock::now(); + + // save num of points to log + for (size_t i = 0; i < move_bounds.size(); i++) + BOOST_LOG_TRIVIAL(info) << "Number of points in move_bound: " << move_bounds[i].size() << " in layer " << i; #ifdef TREESUPPORT_DEBUG_SVG - for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { - Polygons polys; - for (auto& area : move_bounds[layer_idx]) - append(polys, area.influence_area); - if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) - SVG::export_expolygons(debug_out_path("initial_areas-%d.svg", layer_idx), - { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius(begin->state), layer_idx, begin->state.use_min_xy_dist)) }, + for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { + Polygons polys; + for (auto& area : move_bounds[layer_idx]) + append(polys, area.influence_area); + if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) + SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), + { { { union_ex(volumes.getWallRestriction(support_element_collision_radius(config, begin->state), layer_idx, begin->state.use_min_xy_dist)) }, { "wall_restricrictions", "gray", 0.5f } }, { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); - } + } #endif // TREESUPPORT_DEBUG_SVG - // ### Propagate the influence areas downwards. This is an inherently serial operation. - print.set_status(60, _L("Support: propagate branches")); - create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); - auto t_path = std::chrono::high_resolution_clock::now(); + // ### Propagate the influence areas downwards. This is an inherently serial operation. + print.set_status(60, _L("Support: propagate branches")); + create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); + auto t_path = std::chrono::high_resolution_clock::now(); - // ### Set a point in each influence area - create_nodes_from_area(volumes, config, move_bounds, throw_on_cancel); - auto t_place = std::chrono::high_resolution_clock::now(); + // ### Set a point in each influence area + create_nodes_from_area(volumes, config, move_bounds, throw_on_cancel); + auto t_place = std::chrono::high_resolution_clock::now(); - // ### draw these points as circles - - if (0 && is_tree(print_object.config().support_type.value)) - draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - else { + // ### draw these points as circles indexed_triangle_set branches = draw_branches(*print.get_object(processing.second.front()), volumes, config, move_bounds, throw_on_cancel); - // Reduce memory footprint. After this point only slice_branches() will use volumes and from that only collisions with zero radius will be used. + // Reduce memory footprint. After this point only slice_branches() will use volumes and from that only collisions with zero radius will be used. volumes.clear_all_but_object_collision(); slice_branches(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, branches, bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - } - auto t_draw = std::chrono::high_resolution_clock::now(); - auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); - auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); - auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); - auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); - auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); - auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - BOOST_LOG_TRIVIAL(info) << - "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " - "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " - "Creating inital influence areas: " << dur_gen << " ms " - "Influence area creation: " << dur_path << "ms " - "Placement of Points in InfluenceAreas: " << dur_place << "ms " - "Drawing result as support " << dur_draw << " ms"; - - move_bounds.clear(); - - 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); + // this new function may cause bad_function_call exception + //organic_draw_branches( + // *print.get_object(processing.second.front()), volumes, config, move_bounds, + // bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, + // throw_on_cancel); + + + remove_undefined_layers(); + + std::tie(interface_layers, base_interface_layers) = generate_interface_layers(print_object.config(), support_params, + bottom_contacts, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + + auto t_draw = std::chrono::high_resolution_clock::now(); + auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); + auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); + auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); + auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); + auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); + auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); + BOOST_LOG_TRIVIAL(info) << + "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " + "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " + "Creating inital influence areas: " << dur_gen << " ms " + "Influence area creation: " << dur_path << "ms " + "Placement of Points in InfluenceAreas: " << dur_place << "ms " + "Drawing result as support " << dur_draw << " ms"; + + move_bounds.clear(); + } + else if (generate_raft_contact(print_object, config, interface_placer) >= 0) { + remove_undefined_layers(); + } + else + // No raft. + continue; // Produce the support G-code. - // Used by both classic and tree supports. - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; - SupportGeneratorLayersPtr interface_layers, base_interface_layers; SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); SupportGeneratorLayersPtr layers_sorted = generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); @@ -4120,6 +4355,428 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // storage.support.generated = true; } +// Organic specific: Smooth branches and produce one cummulative mesh to be sliced. +void organic_draw_branches( + PrintObject& print_object, + TreeModelVolumes& volumes, + const TreeSupportSettings& config, + std::vector& move_bounds, + + // I/O: + SupportGeneratorLayersPtr& bottom_contacts, + SupportGeneratorLayersPtr& top_contacts, + InterfacePlacer& interface_placer, + + // Output: + SupportGeneratorLayersPtr& intermediate_layers, + SupportGeneratorLayerStorage& layer_storage, + + std::function throw_on_cancel) +{ + // All SupportElements are put into a layer independent storage to improve parallelization. + std::vector> elements_with_link_down; + std::vector linear_data_layers; + { + std::vector> map_downwards_old; + std::vector> map_downwards_new; + linear_data_layers.emplace_back(0); + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++layer_idx) { + SupportElements* layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; + map_downwards_new.clear(); + std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); + SupportElements& layer = move_bounds[layer_idx]; + for (size_t elem_idx = 0; elem_idx < layer.size(); ++elem_idx) { + SupportElement& elem = layer[elem_idx]; + int child = -1; + if (layer_idx > 0) { + auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto& l, const SupportElement* r) { return l.first < r; }); + if (it != map_downwards_old.end() && it->first == &elem) { + child = it->second; + // Only one link points to a node above from below. + assert(!(++it != map_downwards_old.end() && it->first == &elem)); + } +#ifndef NDEBUG + { + const SupportElement* pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; + assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); + } +#endif // NDEBUG + } + for (int32_t parent_idx : elem.parents) { + SupportElement& parent = (*layer_above)[parent_idx]; + if (parent.state.result_on_layer_is_set()) + map_downwards_new.emplace_back(&parent, elem_idx); + } + + elements_with_link_down.push_back({ &elem, int(child) }); + } + std::swap(map_downwards_old, map_downwards_new); + linear_data_layers.emplace_back(elements_with_link_down.size()); + } + } + + throw_on_cancel(); + + organic_smooth_branches_avoid_collisions(print_object, volumes, config, elements_with_link_down, linear_data_layers, throw_on_cancel); + + // Reduce memory footprint. After this point only finalize_interface_and_support_areas() will use volumes and from that only collisions with zero radius will be used. + volumes.clear_all_but_object_collision(); + + // Unmark all nodes. + for (SupportElements& elements : move_bounds) + for (SupportElement& element : elements) + element.state.marked = false; + + // Traverse all nodes, generate tubes. + // Traversal stack with nodes and their current parent + + struct Branch { + std::vector path; + bool has_root{ false }; + bool has_tip{ false }; + }; + + struct Slice { + Polygons polygons; + Polygons bottom_contacts; + size_t num_branches{ 0 }; + }; + + struct Tree { + std::vector branches; + + std::vector slices; + LayerIndex first_layer_id{ -1 }; + }; + + std::vector trees; + + struct TreeVisitor { + static void visit_recursive(std::vector& move_bounds, SupportElement& start_element, Tree& out) { + assert(!start_element.state.marked && !start_element.parents.empty()); + // Collect elements up to a bifurcation above. + start_element.state.marked = true; + // For each branch bifurcating from this point: + //SupportElements &layer = move_bounds[start_element.state.layer_idx]; + SupportElements& layer_above = move_bounds[start_element.state.layer_idx + 1]; + bool root = out.branches.empty(); + for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++parent_idx) { + Branch branch; + branch.path.emplace_back(&start_element); + // Traverse each branch until it branches again. + SupportElement& first_parent = layer_above[start_element.parents[parent_idx]]; + assert(!first_parent.state.marked); + assert(branch.path.back()->state.layer_idx + 1 == first_parent.state.layer_idx); + branch.path.emplace_back(&first_parent); + if (first_parent.parents.size() < 2) + first_parent.state.marked = true; + SupportElement* next_branch = nullptr; + if (first_parent.parents.size() == 1) { + for (SupportElement* parent = &first_parent;;) { + assert(parent->state.marked); + SupportElement& next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; + assert(!next_parent.state.marked); + assert(branch.path.back()->state.layer_idx + 1 == next_parent.state.layer_idx); + branch.path.emplace_back(&next_parent); + if (next_parent.parents.size() > 1) { + // Branching point was reached. + next_branch = &next_parent; + break; + } + next_parent.state.marked = true; + if (next_parent.parents.size() == 0) + // Tip is reached. + break; + parent = &next_parent; + } + } + else if (first_parent.parents.size() > 1) + // Branching point was reached. + next_branch = &first_parent; + assert(branch.path.size() >= 2); + assert(next_branch == nullptr || !next_branch->state.marked); + branch.has_root = root; + branch.has_tip = !next_branch; + out.branches.emplace_back(std::move(branch)); + if (next_branch) + visit_recursive(move_bounds, *next_branch, out); + } + } + }; + + for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++layer_idx) { + // int ielement; + for (SupportElement& start_element : move_bounds[layer_idx]) { + if (!start_element.state.marked && !start_element.parents.empty()) { +#if 0 + int found = 0; + if (layer_idx > 0) { + for (auto& el : move_bounds[layer_idx - 1]) { + for (auto iparent : el.parents) + if (iparent == ielement) + ++found; + } + if (found != 0) + printf("Found: %d\n", found); + } +#endif + trees.push_back({}); + TreeVisitor::visit_recursive(move_bounds, start_element, trees.back()); + assert(!trees.back().branches.empty()); + //FIXME debugging +#if 0 + if (start_element.state.lost) { + } + else if (start_element.state.verylost) { + } + else + trees.pop_back(); +#endif + } + // ++ ielement; + } + } + + const SlicingParameters& slicing_params = print_object.slicing_parameters(); + MeshSlicingParams mesh_slicing_params; + mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; + + tbb::parallel_for(tbb::blocked_range(0, trees.size(), trees.size()), + [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range& range) { + indexed_triangle_set partial_mesh; + std::vector slice_z; + std::vector bottom_contacts; + for (size_t tree_id = range.begin(); tree_id < range.end(); ++tree_id) { + Tree& tree = trees[tree_id]; + for (const Branch& branch : tree.branches) { + // Triangulate the tube. + partial_mesh.clear(); + std::pair zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh); + LayerIndex layer_begin = branch.has_root ? + branch.path.front()->state.layer_idx : + std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first)); + LayerIndex layer_end = (branch.has_tip ? + branch.path.back()->state.layer_idx : + std::max(branch.path.back()->state.layer_idx, layer_idx_floor(slicing_params, config, zspan.second))) + 1; + slice_z.clear(); + for (LayerIndex layer_idx = layer_begin; layer_idx < layer_end; ++layer_idx) { + const double print_z = layer_z(slicing_params, config, layer_idx); + const double bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.; + slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); + } + std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); + bottom_contacts.clear(); + //FIXME parallelize? + for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) + slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); + + size_t num_empty = 0; + if (slices.front().empty()) { + // Some of the initial layers are empty. + num_empty = std::find_if(slices.begin(), slices.end(), [](auto& s) { return !s.empty(); }) - slices.begin(); + } + else { + if (branch.has_root) { + if (branch.path.front()->state.to_model_gracious) { + if (config.settings.support_floor_layers > 0) + //FIXME one may just take the whole tree slice as bottom interface. + bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {}))); + } + else if (layer_begin > 0) { + // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. + struct BottomExtraSlice { + Polygons polygons; + double area; + }; + std::vector bottom_extra_slices; + Polygons rest_support; + coord_t bottom_radius = support_element_radius(config, *branch.path.front()); + // Don't propagate further than 1.5 * bottom radius. + //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; + LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; + LayerIndex layer_bottommost = branch.path.front()->state.verylost ? + // If the tree bottom is hanging in the air, bring it down to some surface. + 0 : + //FIXME the "verylost" branches should stop when crossing another support. + std::max(0, layer_begin - layers_propagate_max); + double support_area_min_radius = M_PI * sqr(double(config.branch_radius)); + double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius); + // Only propagate until the rest area is smaller than this threshold. + //double support_area_min = 0.1 * support_area_min_radius; + for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; --layer_idx) { + rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); + double rest_support_area = area(rest_support); + if (rest_support_area < support_area_stop) + // Don't propagate a fraction of the tree contact surface. + break; + bottom_extra_slices.push_back({ rest_support, rest_support_area }); + } + // Now remove those bottom slices that are not supported at all. +#if 0 + while (!bottom_extra_slices.empty()) { + Polygons this_bottom_contacts = intersection_clipped( + bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); + if (area(this_bottom_contacts) < support_area_min) + bottom_extra_slices.pop_back(); + else { + // At least a fraction of the tree bottom is considered to be supported. + if (config.settings.support_floor_layers > 0) + // Turn this fraction of the tree bottom into a contact layer. + bottom_contacts.emplace_back(std::move(this_bottom_contacts)); + break; + } + } +#endif + if (config.settings.support_floor_layers > 0) + for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; --i) + bottom_contacts.emplace_back( + intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {}))); + layer_begin -= LayerIndex(bottom_extra_slices.size()); + slices.insert(slices.begin(), bottom_extra_slices.size(), {}); + auto it_dst = slices.begin(); + for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++it_src) + *it_dst++ = std::move(it_src->polygons); + } + } + +#if 0 + //FIXME branch.has_tip seems to not be reliable. + if (branch.has_tip && interface_placer.support_parameters.has_top_contacts) + // Add top slices to top contacts / interfaces / base interfaces. + for (int i = int(branch.path.size()) - 1; i >= 0; --i) { + const SupportElement& el = *branch.path[i]; + if (el.state.missing_roof_layers == 0) + break; + //FIXME Move or not? + interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx, + interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers); + } +#endif + } + + layer_begin += LayerIndex(num_empty); + while (!slices.empty() && slices.back().empty()) { + slices.pop_back(); + --layer_end; + } + if (layer_begin < layer_end) { + LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); + LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); + size_t new_size = size_t(new_end - new_begin); + if (tree.first_layer_id == -1) { + } + else if (tree.slices.capacity() < new_size) { + std::vector new_slices; + new_slices.reserve(new_size); + if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) + new_slices.insert(new_slices.end(), dif, {}); + append(new_slices, std::move(tree.slices)); + tree.slices.swap(new_slices); + } + else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) + tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); + tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); + layer_begin -= LayerIndex(num_empty); + for (LayerIndex i = layer_begin; i != layer_end; ++i) { + int j = i - layer_begin; + if (Polygons& src = slices[j]; !src.empty()) { + Slice& dst = tree.slices[i - new_begin]; + if (++dst.num_branches > 1) { + append(dst.polygons, std::move(src)); + if (j < int(bottom_contacts.size())) + append(dst.bottom_contacts, std::move(bottom_contacts[j])); + } + else { + dst.polygons = std::move(std::move(src)); + if (j < int(bottom_contacts.size())) + dst.bottom_contacts = std::move(bottom_contacts[j]); + } + } + } + tree.first_layer_id = new_begin; + } + } + } + }, tbb::simple_partitioner()); + + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), + [&trees, &throw_on_cancel](const tbb::blocked_range& range) { + for (size_t tree_id = range.begin(); tree_id < range.end(); ++tree_id) { + Tree& tree = trees[tree_id]; + for (Slice& slice : tree.slices) + if (slice.num_branches > 1) { + slice.polygons = union_(slice.polygons); + slice.bottom_contacts = union_(slice.bottom_contacts); + slice.num_branches = 1; + } + throw_on_cancel(); + } + }, tbb::simple_partitioner()); + + size_t num_layers = 0; + for (Tree& tree : trees) + if (tree.first_layer_id >= 0) + num_layers = std::max(num_layers, size_t(tree.first_layer_id + tree.slices.size())); + + std::vector slices(num_layers, Slice{}); + for (Tree& tree : trees) + if (tree.first_layer_id >= 0) { + for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++i) + if (Slice& src = tree.slices[i - tree.first_layer_id]; !src.polygons.empty()) { + Slice& dst = slices[i]; + if (++dst.num_branches > 1) { + append(dst.polygons, std::move(src.polygons)); + append(dst.bottom_contacts, std::move(src.bottom_contacts)); + } + else { + dst.polygons = std::move(src.polygons); + dst.bottom_contacts = std::move(src.bottom_contacts); + } + } + } + + tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), + [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + Slice& slice = slices[layer_idx]; + assert(intermediate_layers[layer_idx] == nullptr); + Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); + Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts); + + if (!base_layer_polygons.empty()) { + // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. + base_layer_polygons = smooth_outward(union_(base_layer_polygons), 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. + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution))); + } + + // Subtract top contact layer polygons from support base. + SupportGeneratorLayer* top_contact_layer = top_contacts.empty() ? nullptr : top_contacts[layer_idx]; + if (top_contact_layer && !top_contact_layer->polygons.empty() && !base_layer_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons); + if (!bottom_contact_polygons.empty()) + //FIXME it may be better to clip bottom contacts with top contacts first after they are propagated to produce interface layers. + bottom_contact_polygons = diff(bottom_contact_polygons, top_contact_layer->polygons); + } + if (!bottom_contact_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, bottom_contact_polygons); + SupportGeneratorLayer* bottom_contact_layer = bottom_contacts[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::sltBottomContact, print_object.slicing_parameters(), config, layer_idx); + bottom_contact_layer->polygons = std::move(bottom_contact_polygons); + } + if (!base_layer_polygons.empty()) { + SupportGeneratorLayer* base_layer = intermediate_layers[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::sltBase, print_object.slicing_parameters(), config, layer_idx); + base_layer->polygons = union_(base_layer_polygons); + } + + throw_on_cancel(); + } + }, tbb::simple_partitioner()); +} + + } // namespace TreeSupport3D void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_support, std::function throw_on_cancel) diff --git a/src/libslic3r/Support/TreeSupport3D.hpp b/src/libslic3r/Support/TreeSupport3D.hpp new file mode 100644 index 0000000000..7a6d342ff3 --- /dev/null +++ b/src/libslic3r/Support/TreeSupport3D.hpp @@ -0,0 +1,332 @@ +// 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 slic3r_TreeSupport_hpp +#define slic3r_TreeSupport_hpp + +#include +#include "../Point.hpp" +#include "../BoundingBox.hpp" +#include "../Utils.hpp" +#include "TreeModelVolumes.hpp" +#include "TreeSupportCommon.hpp" + +// #define TREE_SUPPORT_SHOW_ERRORS + +#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 + +namespace Slic3r +{ + +// Forward declarations +class TreeSupport; +class Print; +class PrintObject; +class SupportGeneratorLayer; +using SupportGeneratorLayersPtr = std::vector; + +namespace TreeSupport3D +{ + +// The number of vertices in each circle. +static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; + +struct AreaIncreaseSettings +{ + AreaIncreaseSettings( + TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0, + bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) : + increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {} + + coord_t increase_speed; + // Packing for smaller memory footprint of SupportElementState && SupportElementMerging + TreeModelVolumes::AvoidanceType type; + bool increase_radius : 1; + bool no_error : 1; + bool use_min_distance : 1; + bool move : 1; + bool operator==(const AreaIncreaseSettings& other) const + { + return type == other.type && + increase_speed == other.increase_speed && + increase_radius == other.increase_radius && + no_error == other.no_error && + use_min_distance == other.use_min_distance && + move == other.move; + } +}; + +#define TREE_SUPPORTS_TRACK_LOST + +// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. +struct SupportElementStateBits { + SupportElementStateBits() : + to_buildplate(false), + to_model_gracious(false), + use_min_xy_dist(false), + supports_roof(false), + can_use_safe_radius(false), + skip_ovalisation(false), +#ifdef TREE_SUPPORTS_TRACK_LOST + lost(false), + verylost(false), +#endif // TREE_SUPPORTS_TRACK_LOST + deleted(false), + marked(false) + {} + + /*! + * \brief The element trys to reach the buildplate + */ + bool to_buildplate : 1; + + /*! + * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? + */ + bool to_model_gracious : 1; + + /*! + * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. + */ + bool use_min_xy_dist : 1; + + /*! + * \brief True if this Element or any parent provides support to a support roof. + */ + bool supports_roof : 1; + + /*! + * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. + */ + bool can_use_safe_radius : 1; + + /*! + * \brief Skip the ovalisation to parent and children when generating the final circles. + */ + bool skip_ovalisation : 1; + +#ifdef TREE_SUPPORTS_TRACK_LOST + // Likely a lost branch, debugging information. + bool lost : 1; + bool verylost : 1; +#endif // TREE_SUPPORTS_TRACK_LOST + + // Not valid anymore, to be deleted. + bool deleted : 1; + + // General purpose flag marking a visited element. + bool marked : 1; +}; + +struct SupportElementState : public SupportElementStateBits +{ + int type = 0; + coordf_t radius = 0; + float print_z = 0; + + /*! + * \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 layer_idx; + + /*! + * \brief The Effective distance to top of this element regarding radius increases and collision calculations. + */ + uint32_t effective_radius_height; + + /*! + * \brief The amount of layers this element is below the topmost layer of this branch. + */ + uint32_t distance_to_top; + + /*! + * \brief The resulting center point around which a circle will be drawn later. + * Will be set by setPointsOnAreas + */ + Point result_on_layer{ std::numeric_limits::max(), std::numeric_limits::max() }; + bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + /*! + * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. + */ + coord_t increased_to_model_radius; // how much to model we increased only relevant for merging + + /*! + * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. + */ + double elephant_foot_increases; + + /*! + * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. + */ + uint32_t dont_move_until; + + /*! + * \brief Settings used to increase the influence area to its current state. + */ + AreaIncreaseSettings last_area_increase; + + /*! + * \brief Amount of roof layers that were not yet added, because the branch needed to move. + */ + uint32_t missing_roof_layers; + + // called by increase_single_area() and increaseAreas() + [[nodiscard]] static SupportElementState propagate_down(const SupportElementState& src) + { + SupportElementState dst{ src }; + ++dst.distance_to_top; + --dst.layer_idx; + // set to invalid as we are a new node on a new layer + dst.result_on_layer_reset(); + dst.skip_ovalisation = false; + return dst; + } +}; + + +/*! + * \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 TreeSupportSettings &settings, const SupportElementState &elem) +{ + return elem.effective_radius_height < settings.increase_radius_until_layer ? + (elem.distance_to_top < settings.increase_radius_until_layer ? elem.distance_to_top : settings.increase_radius_until_layer) : + elem.effective_radius_height; +} + +/*! + * \brief Get the Radius, that this element will have. + * \param elem[in] The Element. + * \return The radius the element has. + */ +[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElementState &elem) +{ + return settings.getRadius(getEffectiveDTT(settings, 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 support_element_collision_radius(const TreeSupportSettings &settings, const SupportElementState &elem) +{ + return settings.getRadius(elem.effective_radius_height, elem.elephant_foot_increases); +} + +struct SupportElement +{ + using ParentIndices = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + +// SupportElement(const SupportElementState &state) : SupportElementState(state) {} + SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {} + SupportElement(const SupportElementState &state, ParentIndices &&parents, Polygons &&influence_area) : + state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {} + + SupportElementState state; + + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + ParentIndices parents; + + /*! + * \brief The resulting influence area. + * Will only be set in the results of createLayerPathing, and will be nullptr inside! + */ + Polygons influence_area; +}; + +void tree_supports_show_error(std::string_view message, bool critical); + +using SupportElements = std::deque; + +[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElement &elem) +{ + return support_element_radius(settings, elem.state); +} + +[[nodiscard]] inline coord_t support_element_collision_radius(const TreeSupportSettings &settings, const SupportElement &elem) +{ + return support_element_collision_radius(settings, elem.state); +} + +void create_layer_pathing(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); + +void create_nodes_from_area(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); + +void organic_smooth_branches_avoid_collisions(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector>& elements_with_link_down, const std::vector& linear_data_layers, std::function throw_on_cancel); + +indexed_triangle_set draw_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); + +void slice_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, const indexed_triangle_set& cummulative_mesh, SupportGeneratorLayersPtr& bottom_contacts, SupportGeneratorLayersPtr& top_contacts, SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, std::function throw_on_cancel); + +void generate_initial_areas(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, InterfacePlacer& interface_placer, std::function throw_on_cancel); + +// Organic specific: Smooth branches and produce one cummulative mesh to be sliced. +void organic_draw_branches( + PrintObject& print_object, + TreeModelVolumes& volumes, + const TreeSupportSettings& config, + std::vector& move_bounds, + + // I/O: + SupportGeneratorLayersPtr& bottom_contacts, + SupportGeneratorLayersPtr& top_contacts, + InterfacePlacer& interface_placer, + + // Output: + SupportGeneratorLayersPtr& intermediate_layers, + SupportGeneratorLayerStorage& layer_storage, + + std::function throw_on_cancel); + +} // namespace TreeSupport3D + +void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_support, std::function throw_on_cancel = []{}); + +} // namespace Slic3r + +#endif /* slic3r_TreeSupport_hpp */ diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp new file mode 100644 index 0000000000..842b112034 --- /dev/null +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -0,0 +1,730 @@ +#pragma once +#include +#include "../Point.hpp" +#include "../libslic3r.h" +#include "../Print.hpp" +#include "../BoundingBox.hpp" +#include "../Utils.hpp" +#include "../Slicing.hpp" // SlicingParams +#include "TreeModelVolumes.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" +namespace Slic3r +{ +namespace TreeSupport3D +{ +using LayerIndex = int; + +enum class InterfacePreference +{ + InterfaceAreaOverwritesSupport, + SupportAreaOverwritesInterface, + InterfaceLinesOverwriteSupport, + SupportLinesOverwriteInterface, + Nothing +}; + +struct TreeSupportMeshGroupSettings { + TreeSupportMeshGroupSettings() = default; + explicit 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 || config.support_material_style == smsOrganic); + + // 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, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); + } + + this->layer_height = scaled(config.layer_height.value); + this->resolution = scaled(print_config.resolution.value); + // Arache feature + this->min_feature_size = scaled(config.min_feature_size.value); + // +1 makes the threshold inclusive + this->support_angle = 0.5 * M_PI - std::clamp((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI); + 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_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0; + this->support_bottom_height = this->support_bottom_enable ? + (config.support_interface_bottom_layers.value > 0 ? + config.support_interface_bottom_layers.value : + config.support_interface_top_layers.value) * this->layer_height : + 0; + this->support_material_buildplate_only = config.support_on_build_plate_only; + this->support_xy_distance = scaled(config.support_object_xy_distance.value); + // 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); + this->support_roof_enable = config.support_interface_top_layers.value > 0; + this->support_roof_layers = config.support_interface_top_layers.value; + this->support_floor_enable = config.support_interface_bottom_layers.value > 0; + this->support_floor_layers = config.support_interface_bottom_layers.value; + this->support_roof_pattern = config.support_interface_pattern; + this->support_pattern = config.support_base_pattern; + this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); + this->support_wall_count = std::max(1, config.tree_support_wall_count.value); // at least 1 wall for organic tree support + this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; + double support_tree_angle_slow = 25;// TODO add a setting? + double support_tree_branch_diameter_angle = 5; // TODO add a setting? + double tree_support_tip_diameter = 0.8; + this->support_tree_angle = std::clamp(config.tree_support_branch_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_angle_slow = std::clamp(support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); + this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter.value); + this->support_tree_branch_diameter_angle = std::clamp(support_tree_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_top_rate = 30; // percent + // this->support_tree_tip_diameter = this->support_line_width; + this->support_tree_tip_diameter = std::clamp(scaled(tree_support_tip_diameter), 0, this->support_tree_branch_diameter); + } + +/*********************************************************************/ +/* 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) - Arachne specific + // 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. + 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) }; + // 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 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 + 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_layers{ 2 }; + bool support_floor_enable{ false }; + coord_t support_floor_layers{ 2 }; + + // 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 + 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 + 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. + 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 + 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 + 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 + // 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 }; +}; + +/*! + * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. + */ +struct TreeSupportSettings +{ + TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. + + explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings, const SlicingParameters &slicing_params) + : 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 + branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height), + 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_increase_per_layer), + support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), + xy_distance(mesh_group_settings.support_xy_distance), + xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), + bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), + bp_radius_increase_per_layer(std::min(tan(0.7) * layer_height, 0.5 * support_line_width)), + z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))), + z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))), +// support_infill_angles(mesh_group_settings.support_infill_angles), + support_roof_angles(mesh_group_settings.support_roof_angles), + roof_pattern(mesh_group_settings.support_roof_pattern), + support_pattern(mesh_group_settings.support_pattern), + support_roof_line_width(mesh_group_settings.support_roof_line_width), + support_line_spacing(mesh_group_settings.support_line_spacing), + support_bottom_offset(mesh_group_settings.support_bottom_offset), + support_wall_count(mesh_group_settings.support_wall_count), + resolution(mesh_group_settings.resolution), + support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. + settings(mesh_group_settings), + min_feature_size(mesh_group_settings.min_feature_size) + { + // At least one tip layer must be defined. + assert(tip_layers > 0); + layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer; + + if (TreeSupportSettings::soluble) { + // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely + // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size + // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. + xy_min_distance = std::max(xy_min_distance, scaled(0.1)); + xy_distance = std::max(xy_distance, xy_min_distance); + } + + + // const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; + // interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); + //FIXME this was the default + // interface_preference = InterfacePreference::SupportLinesOverwriteInterface; + interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport; + + if (slicing_params.raft_layers() > 0) { + // Fill in raft_layers with the heights of the layers below the first object layer. + // First layer + double z = slicing_params.first_print_layer_height; + this->raft_layers.emplace_back(z); + // Raft base layers + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { + z += slicing_params.base_raft_layer_height; + this->raft_layers.emplace_back(z); + } + // Raft interface layers + for (size_t i = 0; i + 1 < slicing_params.interface_raft_layers; ++ i) { + z += slicing_params.interface_raft_layer_height; + this->raft_layers.emplace_back(z); + } + // Raft contact layer + if (slicing_params.raft_layers() > 1) { + z = slicing_params.raft_contact_top_z; + this->raft_layers.emplace_back(z); + } + if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) { + // Layers between the raft contacts and bottom of the object. + auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height)); + double step = dist_to_go / nsteps; + for (int i = 0; i < nsteps; ++ i) { + z += step; + this->raft_layers.emplace_back(z); + } + } + } + } + +private: + double angle; + double angle_slow; + std::vector known_z; + +public: + // some static variables dependent on other meshes that are not currently processed. + // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. + inline static bool soluble = false; + /*! + * \brief Width of a single line of support. + */ + coord_t support_line_width; + /*! + * \brief Height of a single layer + */ + coord_t layer_height; + /*! + * \brief Radius of a branch when it has left the tip. + */ + coord_t branch_radius; + /*! + * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed + */ + coord_t min_radius; + /*! + * \brief How far an influence area may move outward every layer at most. + */ + coord_t maximum_move_distance; + /*! + * \brief How far every influence area will move outward every layer if possible. + */ + coord_t maximum_move_distance_slow; + /*! + * \brief Amount of bottom layers. 0 if disabled. + */ + size_t support_bottom_layers; + /*! + * \brief Amount of effectiveDTT increases are required to reach branch radius. + */ + size_t tip_layers; + /*! + * \brief Factor by which to increase the branch radius. + */ + double branch_radius_increase_per_layer; + /*! + * \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. + */ + LayerIndex 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 bp_radius_increase_per_layer; + /*! + * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- + */ + coord_t xy_min_distance; + /*! + * \brief Amount of layers distance required the top of the support to the model + */ + size_t z_distance_top_layers; + /*! + * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. + */ + size_t z_distance_bottom_layers; + /*! + * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. + */ + size_t performance_interface_skip_layers; + /*! + * \brief User specified angles for the support infill. + */ +// std::vector support_infill_angles; + /*! + * \brief User specified angles for the support roof infill. + */ + std::vector support_roof_angles; + /*! + * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. + */ + SupportMaterialInterfacePattern roof_pattern; + /*! + * \brief Pattern used in the support infill. + */ + SupportMaterialPattern support_pattern; + /*! + * \brief Line width of the support roof. + */ + coord_t support_roof_line_width; + /*! + * \brief Distance between support infill lines. + */ + coord_t support_line_spacing; + /*! + * \brief Offset applied to the support floor area. + */ + coord_t support_bottom_offset; + /* + * \brief Amount of walls the support area will have. + */ + int support_wall_count; + /* + * \brief Maximum allowed deviation when simplifying. + */ + coord_t resolution; + /* + * \brief Distance between the lines of the roof. + */ + coord_t support_roof_line_distance; + /* + * \brief How overlaps of an interface area with a support area should be handled. + */ + InterfacePreference interface_preference; + + /* + * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. + */ + TreeSupportMeshGroupSettings settings; + + /* + * \brief Minimum thickness of any model features. + */ + coord_t min_feature_size; + + // Extra raft layers below the object. + std::vector raft_layers; + + public: + bool operator==(const TreeSupportSettings& other) const + { + return branch_radius == other.branch_radius && tip_layers == other.tip_layers && branch_radius_increase_per_layer == other.branch_radius_increase_per_layer && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && + bp_radius_increase_per_layer == other.bp_radius_increase_per_layer && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && + xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. + support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && + support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. + support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. + support_roof_angles == other.support_roof_angles && + //support_infill_angles == other.support_infill_angles && + increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. + support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference + && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. + // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry +#if 0 + && (interface_preference == InterfacePreference::InterfaceAreaOverwritesSupport || interface_preference == InterfacePreference::SupportAreaOverwritesInterface + // Perimeter generator parameters + || + (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && + settings.get("min_bead_width") == other.settings.get("min_bead_width") && + settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && + settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && + settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && + settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && + settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && + settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && + settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && + settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && + settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) + ) +#endif + && raft_layers == other.raft_layers + ; + } + + /*! + * \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 + (distance_to_top - tip_layers) * branch_radius_increase_per_layer) + + // gradual increase + elephant_foot_increases * (std::max(bp_radius_increase_per_layer - branch_radius_increase_per_layer, 0.0)); + } + + /*! + * \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 num_layers_widened = layer_start_bp_radius - layer_idx; + return num_layers_widened > 0 ? branch_radius + num_layers_widened * bp_radius_increase_per_layer : 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; + } +}; + +inline void tree_supports_show_error(std::string_view message, bool critical) +{ // todo Remove! ONLY FOR PUBLIC BETA!! + printf("Error: %s, critical: %d\n", message.data(), int(critical)); +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 + static bool g_showed_critical_error = false; + static bool g_showed_performance_warning = false; + auto 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 && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); + (critical ? g_showed_critical_error : g_showed_performance_warning) = true; + if (show) + 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" + std::string(message) + "\n" + bugtype).c_str(), + "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 +} + +inline double layer_z(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const size_t layer_idx) +{ + return layer_idx >= config.raft_layers.size() ? + slicing_params.object_print_z_min + slicing_params.first_object_layer_height + (layer_idx - config.raft_layers.size()) * slicing_params.layer_height : + config.raft_layers[layer_idx]; +} +// Lowest collision layer +inline LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z) +{ + return + LayerIndex(config.raft_layers.size()) + + std::max(0, ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); +} +// Highest collision layer +inline LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z) +{ + return + LayerIndex(config.raft_layers.size()) + + std::max(0, floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); +} + +inline SupportGeneratorLayer& layer_initialize( + SupportGeneratorLayer &layer_new, + const SlicingParameters &slicing_params, + const TreeSupportSettings &config, + const size_t layer_idx) +{ + layer_new.print_z = layer_z(slicing_params, config, layer_idx); + layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0; + layer_new.height = layer_new.print_z - layer_new.bottom_z; + return layer_new; +} + +// Using the std::deque as an allocator. +inline SupportGeneratorLayer& layer_allocate_unguarded( + SupportGeneratorLayerStorage &layer_storage, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + const TreeSupportSettings &config, + size_t layer_idx) +{ + SupportGeneratorLayer &layer = layer_storage.allocate_unguarded(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); +} + +inline SupportGeneratorLayer& layer_allocate( + SupportGeneratorLayerStorage &layer_storage, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + const TreeSupportSettings &config, + size_t layer_idx) +{ + SupportGeneratorLayer &layer = layer_storage.allocate(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); +} + +// Used by generate_initial_areas() in parallel by multiple layers. +class InterfacePlacer { +public: + InterfacePlacer( + const SlicingParameters &slicing_parameters, + const SupportParameters &support_parameters, + const TreeSupportSettings &config, + SupportGeneratorLayerStorage &layer_storage, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interfaces, + SupportGeneratorLayersPtr &top_base_interfaces) + : + slicing_parameters(slicing_parameters), support_parameters(support_parameters), config(config), + layer_storage(layer_storage), top_contacts(top_contacts), top_interfaces(top_interfaces), top_base_interfaces(top_base_interfaces) + {} + InterfacePlacer(const InterfacePlacer& rhs) : + slicing_parameters(rhs.slicing_parameters), support_parameters(rhs.support_parameters), config(rhs.config), + layer_storage(rhs.layer_storage), top_contacts(rhs.top_contacts), top_interfaces(rhs.top_interfaces), top_base_interfaces(rhs.top_base_interfaces) + {} + + const SlicingParameters &slicing_parameters; + const SupportParameters &support_parameters; + const TreeSupportSettings &config; + SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; } + +public: + // Insert the contact layer and some of the inteface and base interface layers below. + void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx) + { + if (! new_roofs.empty()) { + std::lock_guard lock(m_mutex_layer_storage); + for (size_t idx = 0; idx < new_roofs.size(); ++ idx) + if (! new_roofs[idx].empty()) + add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx, idx); + } + } + + void add_roof(Polygons &&new_roof, const size_t insert_layer_idx, const size_t dtt_tip) + { + std::lock_guard lock(m_mutex_layer_storage); + add_roof_unguarded(std::move(new_roof), insert_layer_idx, dtt_tip); + } + + // called by sample_overhang_area() + void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof) + { + std::lock_guard lock(m_mutex_layer_storage); + this->add_roof_unguarded(std::move(overhang_areas), 0, std::min(dtt_roof, this->support_parameters.num_top_interface_layers)); + } + + void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + { + assert(support_parameters.has_top_contacts); + assert(dtt_roof <= support_parameters.num_top_interface_layers); + SupportGeneratorLayersPtr &layers = + dtt_roof == 0 ? this->top_contacts : + dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces; + SupportGeneratorLayer*& l = layers[insert_layer_idx]; + if (l == nullptr) + l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::sltTopContact : SupporLayerType::sltTopInterface, + slicing_parameters, config, insert_layer_idx); + // will be unioned in finalize_interface_and_support_areas() + append(l->polygons, std::move(new_roofs)); + } + +private: + // Outputs + SupportGeneratorLayerStorage &layer_storage; + SupportGeneratorLayersPtr &top_contacts; + SupportGeneratorLayersPtr &top_interfaces; + SupportGeneratorLayersPtr &top_base_interfaces; + + // Mutexes, guards + std::mutex m_mutex_layer_storage; +}; + +} // namespace TreeSupport3D +} // namespace slic3r \ No newline at end of file diff --git a/src/libslic3r/TreeSupport3D.hpp b/src/libslic3r/TreeSupport3D.hpp deleted file mode 100644 index ef99dd40c1..0000000000 --- a/src/libslic3r/TreeSupport3D.hpp +++ /dev/null @@ -1,605 +0,0 @@ -// 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 slic3r_TreeSupport_hpp -#define slic3r_TreeSupport_hpp - -#include "TreeModelVolumes.hpp" -#include "Point.hpp" - -#include - -#include "BoundingBox.hpp" -#include "Utils.hpp" - -// #define TREE_SUPPORT_SHOW_ERRORS - -#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 - -namespace Slic3r -{ - -// Forward declarations -class TreeSupport; -class Print; -class PrintObject; -class SupportGeneratorLayer; -using SupportGeneratorLayerStorage = std::deque; -using SupportGeneratorLayersPtr = std::vector; - -namespace TreeSupport3D -{ - -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); - -// The number of vertices in each circle. -static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; -static constexpr const bool SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; - -enum class InterfacePreference -{ - InterfaceAreaOverwritesSupport, - SupportAreaOverwritesInterface, - InterfaceLinesOverwriteSupport, - SupportLinesOverwriteInterface, - Nothing -}; - -struct AreaIncreaseSettings -{ - AreaIncreaseSettings( - TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0, - bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) : - increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {} - - coord_t increase_speed; - // Packing for smaller memory footprint of SupportElementState && SupportElementMerging - TreeModelVolumes::AvoidanceType type; - bool increase_radius : 1; - bool no_error : 1; - bool use_min_distance : 1; - bool move : 1; - bool operator==(const AreaIncreaseSettings& other) const - { - return type == other.type && - increase_speed == other.increase_speed && - increase_radius == other.increase_radius && - no_error == other.no_error && - use_min_distance == other.use_min_distance && - move == other.move; - } -}; - -struct TreeSupportSettings; - -// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. -struct SupportElementStateBits { - SupportElementStateBits() : - to_buildplate(false), - to_model_gracious(false), - use_min_xy_dist(false), - supports_roof(false), - can_use_safe_radius(false), - skip_ovalisation(false), - deleted(false), - marked(false) - {} - - /*! - * \brief The element trys to reach the buildplate - */ - bool to_buildplate : 1; - - /*! - * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? - */ - bool to_model_gracious : 1; - - /*! - * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. - */ - bool use_min_xy_dist : 1; - - /*! - * \brief True if this Element or any parent provides support to a support roof. - */ - bool supports_roof : 1; - - /*! - * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. - */ - bool can_use_safe_radius : 1; - - /*! - * \brief Skip the ovalisation to parent and children when generating the final circles. - */ - bool skip_ovalisation : 1; - - // Not valid anymore, to be deleted. - bool deleted : 1; - - // General purpose flag marking a visited element. - bool marked : 1; -}; - -struct SupportElementState : public SupportElementStateBits -{ - int type=0; - coordf_t radius=0; - float print_z=0; - - /*! - * \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 layer_idx; - - /*! - * \brief The Effective distance to top of this element regarding radius increases and collision calculations. - */ - uint32_t effective_radius_height; - - /*! - * \brief The amount of layers this element is below the topmost layer of this branch. - */ - uint32_t distance_to_top; - - /*! - * \brief The resulting center point around which a circle will be drawn later. - * Will be set by setPointsOnAreas - */ - Point result_on_layer { std::numeric_limits::max(), std::numeric_limits::max() }; - bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } - void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } - /*! - * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. - */ - coord_t increased_to_model_radius; // how much to model we increased only relevant for merging - - /*! - * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. - */ - double elephant_foot_increases; - - /*! - * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. - */ - uint32_t dont_move_until; - - /*! - * \brief Settings used to increase the influence area to its current state. - */ - AreaIncreaseSettings last_area_increase; - - /*! - * \brief Amount of roof layers that were not yet added, because the branch needed to move. - */ - uint32_t missing_roof_layers; - - // called by increase_single_area() and increaseAreas() - [[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src) - { - SupportElementState dst{ src }; - ++ dst.distance_to_top; - -- dst.layer_idx; - // set to invalid as we are a new node on a new layer - dst.result_on_layer_reset(); - dst.skip_ovalisation = false; - return dst; - } -}; - -struct SupportElement -{ - using ParentIndices = -#ifdef NDEBUG - // To reduce memory allocation in release mode. - boost::container::small_vector; -#else // NDEBUG - // To ease debugging. - std::vector; -#endif // NDEBUG - -// SupportElement(const SupportElementState &state) : SupportElementState(state) {} - SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {} - SupportElement(const SupportElementState &state, ParentIndices &&parents, Polygons &&influence_area) : - state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {} - - SupportElementState state; - - /*! - * \brief All elements in the layer above the current one that are supported by this element - */ - ParentIndices parents; - - /*! - * \brief The resulting influence area. - * Will only be set in the results of createLayerPathing, and will be nullptr inside! - */ - Polygons influence_area; -}; - -/*! - * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. - */ -struct TreeSupportSettings -{ - TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. - - explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) - : angle(mesh_group_settings.support_tree_angle), - angle_slow(mesh_group_settings.support_tree_angle_slow), - support_line_width(mesh_group_settings.support_line_width), - layer_height(mesh_group_settings.layer_height), - branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), - min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance - maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), - maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), - support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), - tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large - diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), - max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), - min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), - increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), - increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), - support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), - xy_distance(mesh_group_settings.support_xy_distance), - xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), - bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), - diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. - z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), - z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), - performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), -// support_infill_angles(mesh_group_settings.support_infill_angles), - support_roof_angles(mesh_group_settings.support_roof_angles), - roof_pattern(mesh_group_settings.support_roof_pattern), - support_pattern(mesh_group_settings.support_pattern), - support_roof_line_width(mesh_group_settings.support_roof_line_width), - support_line_spacing(mesh_group_settings.support_line_spacing), - support_bottom_offset(mesh_group_settings.support_bottom_offset), - support_wall_count(mesh_group_settings.support_wall_count), - resolution(mesh_group_settings.resolution), - support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. - settings(mesh_group_settings), - min_feature_size(mesh_group_settings.min_feature_size) - - - { - layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); - - if (TreeSupportSettings::soluble) { - // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely - // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size - // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. - xy_min_distance = std::max(xy_min_distance, scaled(0.1)); - xy_distance = std::max(xy_distance, xy_min_distance); - } - - -// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; -// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); -//FIXME this was the default -// interface_preference = InterfacePreference::SupportLinesOverwriteInterface; - interface_preference = InterfacePreference::SupportAreaOverwritesInterface; - } - -private: - double angle; - double angle_slow; - std::vector known_z; - -public: - // some static variables dependent on other meshes that are not currently processed. - // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. - inline static bool soluble = false; - /*! - * \brief Width of a single line of support. - */ - coord_t support_line_width; - /*! - * \brief Height of a single layer - */ - coord_t layer_height; - /*! - * \brief Radius of a branch when it has left the tip. - */ - coord_t branch_radius; - /*! - * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed - */ - coord_t min_radius; - /*! - * \brief How far an influence area may move outward every layer at most. - */ - coord_t maximum_move_distance; - /*! - * \brief How far every influence area will move outward every layer if possible. - */ - coord_t maximum_move_distance_slow; - /*! - * \brief Amount of bottom layers. 0 if disabled. - */ - size_t support_bottom_layers; - /*! - * \brief Amount of effectiveDTT increases are required to reach branch radius. - */ - size_t tip_layers; - /*! - * \brief Factor by which to increase the branch radius. - */ - double diameter_angle_scale_factor; - /*! - * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. - */ - coord_t max_to_model_radius_increase; - /*! - * \brief If smaller (in layers) than that, all branches to model will be deleted - */ - size_t min_dtt_to_model; - /*! - * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. - */ - coord_t increase_radius_until_radius; - /*! - * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. - */ - size_t increase_radius_until_layer; - /*! - * \brief True if the branches may connect to the model. - */ - bool support_rests_on_model; - /*! - * \brief How far should support be from the model. - */ - coord_t xy_distance; - /*! - * \brief Radius a branch should have when reaching the buildplate. - */ - coord_t bp_radius; - /*! - * \brief The layer index at which an increase in radius may be required to reach the bp_radius. - */ - coord_t layer_start_bp_radius; - /*! - * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. - */ - double diameter_scale_bp_radius; - /*! - * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- - */ - coord_t xy_min_distance; - /*! - * \brief Amount of layers distance required the top of the support to the model - */ - size_t z_distance_top_layers; - /*! - * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. - */ - size_t z_distance_bottom_layers; - /*! - * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. - */ - size_t performance_interface_skip_layers; - /*! - * \brief User specified angles for the support infill. - */ -// std::vector support_infill_angles; - /*! - * \brief User specified angles for the support roof infill. - */ - std::vector support_roof_angles; - /*! - * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. - */ - SupportMaterialInterfacePattern roof_pattern; - /*! - * \brief Pattern used in the support infill. - */ - SupportMaterialPattern support_pattern; - /*! - * \brief Line width of the support roof. - */ - coord_t support_roof_line_width; - /*! - * \brief Distance between support infill lines. - */ - coord_t support_line_spacing; - /*! - * \brief Offset applied to the support floor area. - */ - coord_t support_bottom_offset; - /* - * \brief Amount of walls the support area will have. - */ - int support_wall_count; - /* - * \brief Maximum allowed deviation when simplifying. - */ - coord_t resolution; - /* - * \brief Distance between the lines of the roof. - */ - coord_t support_roof_line_distance; - /* - * \brief How overlaps of an interface area with a support area should be handled. - */ - InterfacePreference interface_preference; - - /* - * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. - */ - TreeSupportMeshGroupSettings settings; - - /* - * \brief Minimum thickness of any model features. - */ - coord_t min_feature_size; - - public: - bool operator==(const TreeSupportSettings& other) const - { - return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. - xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. - support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && - support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. - support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. - support_roof_angles == other.support_roof_angles && - //support_infill_angles == other.support_infill_angles && - increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. - support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference - && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. - // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry -#if 0 - && (interface_preference == InterfacePreference::InterfaceAreaOverwritesSupport || interface_preference == InterfacePreference::SupportAreaOverwritesInterface - // Perimeter generator parameters - || - (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && - settings.get("min_bead_width") == other.settings.get("min_bead_width") && - settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && - settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && - settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && - settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && - settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && - settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && - settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && - settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && - settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) - ) -#endif - ; - } - - /*! - * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. - * \param elem[in] The SupportElement one wants to know the effectiveDTT - * \return The Effective DTT. - */ - [[nodiscard]] inline size_t getEffectiveDTT(const SupportElementState &elem) const - { - return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; - } - - /*! - * \brief Get the Radius part will have based on numeric values. - * \param distance_to_top[in] The effective distance_to_top of the element - * \param elephant_foot_increases[in] The elephant_foot_increases of the element. - * \return The radius an element with these attributes would have. - */ - [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const - { - return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip - branch_radius + // base - branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) - + // gradual increase - branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); - } - - /*! - * \brief Get the Radius, that this element will have. - * \param elem[in] The Element. - * \return The radius the element has. - */ - [[nodiscard]] inline coord_t getRadius(const SupportElementState &elem) const - { return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); } - [[nodiscard]] inline coord_t getRadius(const SupportElement &elem) const - { return this->getRadius(elem.state); } - - /*! - * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. - * \param elem[in] The Element. - * \return The collision radius the element has. - */ - [[nodiscard]] inline coord_t getCollisionRadius(const SupportElementState &elem) const - { - return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); - } - - /*! - * \brief Get the Radius an element should at least have at a given layer. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const - { - double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; - return scale > 0 ? branch_radius + branch_radius * scale : 0; - } - - /*! - * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) - { - return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; - } - - /*! - * \brief Set the z every Layer is printed at. Required for getActualZ to work - * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex - * \return The radius every element should aim to achieve. - */ - void setActualZ(std::vector& z) - { - known_z = z; - } -}; - -void tree_supports_show_error(std::string_view message, bool critical); - -using SupportElements = std::deque; -void create_layer_pathing(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void create_nodes_from_area(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void organic_smooth_branches_avoid_collisions(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector>& elements_with_link_down, const std::vector& linear_data_layers, std::function throw_on_cancel); - -indexed_triangle_set draw_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void slice_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, const indexed_triangle_set& cummulative_mesh, SupportGeneratorLayersPtr& bottom_contacts, SupportGeneratorLayersPtr& top_contacts, SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, std::function throw_on_cancel); - -void generate_initial_areas(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, SupportGeneratorLayersPtr& top_contacts, SupportGeneratorLayerStorage& layer_storage, std::function throw_on_cancel); - -} // namespace TreeSupport3D - -void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_support, std::function throw_on_cancel = []{}); - -} // namespace Slic3r - -#endif /* slic3r_TreeSupport_hpp */