diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 214a38139..661ea766c 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/4fd2120c913c20c3bb66ee9d01d8ff5087a8b90a.zip - URL_HASH SHA256=5b59e8b4dccf73e109c6588f6a69bcfe4e02e930af53c43d5d1329c1f3d83ec9 + URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip + URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 134ec73a0..a179e4c94 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -871,7 +871,7 @@ template auto rcend(const P& p) -> decltype(_backward(cbegin(p))) template TPoint

front(const P& p) { return *shapelike::cbegin(p); } template TPoint

back (const P& p) { - return *backward(shapelike::cend(p)); + return *std::prev(shapelike::cend(p)); } // Optional, does nothing by default diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 5b5311d90..a17d54982 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -157,26 +157,34 @@ template class EdgeCache { void createCache(const RawShape& sh) { { // For the contour - auto first = shapelike::cbegin(sh); - auto next = std::next(first); - auto endit = shapelike::cend(sh); + auto first = sl::cbegin(sh); + auto endit = sl::cend(sh); + auto next = first == endit ? endit : std::next(first); - contour_.distances.reserve(shapelike::contourVertexCount(sh)); + contour_.distances.reserve(sl::contourVertexCount(sh)); while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); contour_.full_distance += length(contour_.emap.back()); contour_.distances.emplace_back(contour_.full_distance); } + + if constexpr (ClosureTypeV == Closure::OPEN) { + if (sl::contourVertexCount(sh) > 0) { + contour_.emap.emplace_back(sl::back(sh), sl::front(sh)); + contour_.full_distance += length(contour_.emap.back()); + contour_.distances.emplace_back(contour_.full_distance); + } + } } for(auto& h : shapelike::holes(sh)) { // For the holes - auto first = h.begin(); - auto next = std::next(first); - auto endit = h.end(); + auto first = sl::cbegin(h); + auto endit = sl::cend(h); + auto next = first == endit ? endit :std::next(first); ContourCache hc; - hc.distances.reserve(endit - first); + hc.distances.reserve(sl::contourVertexCount(h)); while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); @@ -184,6 +192,14 @@ template class EdgeCache { hc.distances.emplace_back(hc.full_distance); } + if constexpr (ClosureTypeV == Closure::OPEN) { + if (sl::contourVertexCount(h) > 0) { + hc.emap.emplace_back(sl::back(sh), sl::front(sh)); + hc.full_distance += length(hc.emap.back()); + hc.distances.emplace_back(hc.full_distance); + } + } + holes_.emplace_back(std::move(hc)); } } @@ -206,7 +222,6 @@ template class EdgeCache { contour_.corners.reserve(N / S + 1); contour_.corners.emplace_back(0.0); auto N_1 = N-1; - contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 02f613c15..a6e5e1fb4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -16,6 +16,7 @@ #include "FillLightning.hpp" #include "FillConcentric.hpp" #include "FillEnsuring.hpp" +#include "Polygon.hpp" namespace Slic3r { @@ -486,14 +487,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: size_t first_object_layer_id = this->object()->get_layer(0)->id(); for (SurfaceFill &surface_fill : surface_fills) { - //skip patterns for which additional input is nullptr - switch (surface_fill.params.pattern) { - case ipLightning: if (lightning_generator == nullptr) continue; break; - case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break; - case ipSupportCubic: if (support_fill_octree == nullptr) continue; break; - default: break; - } - // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); @@ -647,7 +640,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } -Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const +Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const { std::vector surface_fills = group_fills(*this); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); @@ -656,14 +649,17 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const Polylines sparse_infill_polylines{}; for (SurfaceFill &surface_fill : surface_fills) { - // skip patterns for which additional input is nullptr + if (surface_fill.surface.surface_type != stInternal) { + continue; + } + switch (surface_fill.params.pattern) { - case ipLightning: continue; break; - case ipAdaptiveCubic: continue; break; - case ipSupportCubic: continue; break; case ipCount: continue; break; case ipSupportBase: continue; break; case ipEnsuring: continue; break; + case ipLightning: + case ipAdaptiveCubic: + case ipSupportCubic: case ipRectilinear: case ipMonotonic: case ipMonotonicLines: @@ -688,10 +684,16 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - // f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); + if (surface_fill.params.pattern == ipLightning) { + auto *lf = dynamic_cast(f.get()); + lf->generator = lightning_generator; + lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); + } + // calculate flow spacing for infill pattern generation double link_max_length = 0.; if (!surface_fill.params.bridge) { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index a59c029b8..b3d071c9d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -368,8 +368,12 @@ public: void make_perimeters(); // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } - void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator); - Polylines generate_sparse_infill_polylines_for_anchoring() const; + void make_fills(FillAdaptive::Octree *adaptive_fill_octree, + FillAdaptive::Octree *support_fill_octree, + FillLightning::Generator *lightning_generator); + Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree, + FillAdaptive::Octree *support_fill_octree, + FillLightning::Generator* lightning_generator) const; void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 6017f393e..073907c8c 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -378,7 +378,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly const double custom_angle = this->region().config().bridge_angle.value; const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); bridges.surfaces = custom_angle > 0 ? - expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) : + expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, Geometry::deg2rad(custom_angle)) : expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c21f6bd2b..5e1574e60 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1500,7 +1500,7 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& // Perform cut TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); // add small Z offset to better preview upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 2dd5e8f8e..5235fd72e 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -234,6 +234,7 @@ namespace client delete m_data.s; m_type = TYPE_EMPTY; } + ~expr() { reset(); } enum Type { TYPE_EMPTY = 0, diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index d805473b5..5743e38bd 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -267,29 +267,34 @@ ThickLines ThickPolyline::thicklines() const // Removes the given distance from the end of the ThickPolyline void ThickPolyline::clip_end(double distance) { - while (distance > 0) { - Vec2d last_point = this->last_point().cast(); - coordf_t last_width = this->width.back(); - this->points.pop_back(); - this->width.pop_back(); - if (this->points.empty()) - break; - - Vec2d vec = this->last_point().cast() - last_point; - coordf_t width_diff = this->width.back() - last_width; - double vec_length_sqr = vec.squaredNorm(); - if (vec_length_sqr > distance * distance) { - double t = (distance / std::sqrt(vec_length_sqr)); - this->points.emplace_back((last_point + vec * t).cast()); - this->width.emplace_back(last_width + width_diff * t); - assert(this->width.size() == (this->points.size() - 1) * 2); - return; - } else + if (! this->empty()) { + assert(this->width.size() == (this->points.size() - 1) * 2); + while (distance > 0) { + Vec2d last_point = this->last_point().cast(); + this->points.pop_back(); + if (this->points.empty()) { + assert(this->width.empty()); + break; + } + coordf_t last_width = this->width.back(); this->width.pop_back(); - distance -= std::sqrt(vec_length_sqr); + Vec2d vec = this->last_point().cast() - last_point; + coordf_t width_diff = this->width.back() - last_width; + double vec_length_sqr = vec.squaredNorm(); + if (vec_length_sqr > distance * distance) { + double t = (distance / std::sqrt(vec_length_sqr)); + this->points.emplace_back((last_point + vec * t).cast()); + this->width.emplace_back(last_width + width_diff * t); + assert(this->width.size() == (this->points.size() - 1) * 2); + return; + } else + this->width.pop_back(); + + distance -= std::sqrt(vec_length_sqr); + } } - assert(this->width.size() == (this->points.size() - 1) * 2); + assert(this->points.empty() ? this->width.empty() : this->width.size() == (this->points.size() - 1) * 2); } void ThickPolyline::start_at_index(int index) diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8e362f037..1b23388e1 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -187,6 +187,7 @@ struct ThickPolyline { const Point& last_point() const { return this->points.back(); } size_t size() const { return this->points.size(); } bool is_valid() const { return this->points.size() >= 2; } + bool empty() const { return this->points.empty(); } double length() const { return Slic3r::length(this->points); } void clear() { this->points.clear(); this->width.clear(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0e760f3d3..09514200f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -452,7 +452,7 @@ static std::vector s_Preset_print_options { "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", - "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter", + "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a3eba0148..370fa3ba2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1185,7 +1185,7 @@ void Print::alert_when_supports_needed() case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: message = L("thin fragile section"); break; } - return (critical ? "!" : "") + message; + return message; }; // vector of pairs of object and its issues, where each issue is a pair of type and critical flag diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 3795c2449..9fbbe378a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ +#include "Fill/FillAdaptive.hpp" +#include "Fill/FillLightning.hpp" #include "PrintBase.hpp" #include "BoundingBox.hpp" @@ -385,7 +387,8 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data( + const std::vector>& surfaces_w_bottom_z) const; FillLightning::GeneratorPtr prepare_lightning_infill_data(); // XYZ in scaled coordinates @@ -410,6 +413,9 @@ private: // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + + std::pair m_adaptive_fill_octrees; + FillLightning::GeneratorPtr m_lightning_generator; }; struct WipeTowerData diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fb6a641cb..dbdb36c7d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2910,6 +2910,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + // 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. + def = this->add("support_tree_branch_distance", coFloat); + def->label = L("Branch Distance"); + def->category = L("Support material"); + def->tooltip = L("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."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + def = this->add("support_tree_top_rate", coPercent); def->label = L("Branch Density"); def->category = L("Support material"); @@ -2921,7 +2933,7 @@ void PrintConfigDef::init_fff_params() def->min = 5; def->max_literal = 35; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(30)); + def->set_default_value(new ConfigOptionPercent(15)); def = this->add("temperature", coInts); def->label = L("Other layers"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a2ccb546b..e5a9f4c41 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -555,6 +555,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_tree_branch_diameter)) ((ConfigOptionFloat, support_tree_branch_diameter_angle)) ((ConfigOptionPercent, support_tree_top_rate)) + ((ConfigOptionFloat, support_tree_branch_distance)) ((ConfigOptionFloat, support_tree_tip_diameter)) // The rest ((ConfigOptionBool, thick_bridges)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b9552a2ef..ccdc7a914 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -16,6 +16,7 @@ #include "Layer.hpp" #include "MutablePolygon.hpp" #include "PrintBase.hpp" +#include "PrintConfig.hpp" #include "SupportMaterial.hpp" #include "TreeSupport.hpp" #include "Surface.hpp" @@ -37,10 +38,13 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include #include @@ -400,16 +404,16 @@ void PrintObject::infill() if (this->set_started(posInfill)) { m_print->set_status(45, L("making infill")); - auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); - auto lightning_generator = this->prepare_lightning_infill_data(); + const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; + const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get()); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); } } ); @@ -509,7 +513,8 @@ void PrintObject::estimate_curled_extrusions() } } -std::pair PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data( + const std::vector> &surfaces_w_bottom_z) const { using namespace FillAdaptive; @@ -523,22 +528,18 @@ std::pair PrintObject::prepare its_transform(mesh, to_octree * this->trafo_centered(), true); // Triangulate internal bridging surfaces. - std::vector> overhangs(this->layers().size()); - tbb::parallel_for( - tbb::blocked_range(0, int(m_layers.size()) - 1), - [this, &to_octree, &overhangs](const tbb::blocked_range &range) { - std::vector &out = overhangs[range.begin()]; - for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - m_print->throw_if_canceled(); - const Layer *layer = this->layers()[idx_layer]; - for (const LayerRegion *layerm : layer->regions()) - for (const Surface &surface : layerm->fill_surfaces()) - if (surface.surface_type == stInternalBridge) - append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); - } - for (Vec3d &p : out) - p = (to_octree * p).eval(); - }); + std::vector> overhangs(surfaces_w_bottom_z.size()); + tbb::parallel_for(tbb::blocked_range(0, surfaces_w_bottom_z.size()), + [this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range &range) { + for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) { + std::vector &out = overhangs[surface_idx]; + m_print->throw_if_canceled(); + append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon, + surfaces_w_bottom_z[surface_idx].second)); + for (Vec3d &p : out) + p = (to_octree * p).eval(); + } + }); // and gather them. for (size_t i = 1; i < overhangs.size(); ++ i) append(overhangs.front(), std::move(overhangs[i])); @@ -693,6 +694,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_tree_branch_diameter" || opt_key == "support_tree_branch_diameter_angle" || opt_key == "support_tree_top_rate" + || opt_key == "support_tree_branch_distance" || opt_key == "support_tree_tip_diameter" || opt_key == "raft_expansion" || opt_key == "raft_first_layer_density" @@ -1581,586 +1583,626 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - struct ModifiedSurface + struct CandidateSurface { - ModifiedSurface(const Surface *original_surface, Polygons new_polys, const LayerRegion *region, double bridge_angle) - : original_surface(original_surface), new_polys(new_polys), region(region), bridge_angle(bridge_angle) + CandidateSurface(const Surface *original_surface, + int layer_index, + Polygons new_polys, + const LayerRegion *region, + double bridge_angle, + bool supported_by_lightning) + : original_surface(original_surface) + , layer_index(layer_index) + , new_polys(new_polys) + , region(region) + , bridge_angle(bridge_angle) + , supported_by_lightning(supported_by_lightning) {} const Surface *original_surface; + int layer_index; Polygons new_polys; const LayerRegion *region; double bridge_angle; + bool supported_by_lightning; }; - std::unordered_map> bridging_surfaces; + std::map> surfaces_by_layer; - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), - &bridging_surfaces](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); - - // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring - // gather potential internal bridging surfaces for the current layer - // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it - std::unordered_map bridging_surface_candidates; - std::unordered_map expansion_space; - std::unordered_map max_bridge_flow_height; - std::unordered_map surface_to_region; - for (const LayerSlice &slice : layer->lslices_ex) { - AABBTreeLines::LinesDistancer slice_island_tree{to_lines(layer->lslices[int(&slice - layer->lslices_ex.data())])}; - std::unordered_set regions_to_check; - - // If there is composite island we have to check all regions on the layer. otherwise, only some regions are needed to be checked - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(layer->regions()[island.perimeters.region()]); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(layer->regions()[island.fill_region_id]); - } else { - for (const auto& r : layer->regions()) { - regions_to_check.insert(r); + // SECTION to gather and filter surfaces for expanding, and then cluster them by layer + { + tbb::concurrent_vector candidate_surfaces; + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), + &candidate_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); + if (layer->lower_layer == nullptr) { + continue; + } + auto spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); + Polygons unsupported_area; + Polygons lower_layer_solids; + bool contains_only_lightning = true; + for (const LayerRegion *region : layer->lower_layer->regions()) { + if (region->region().config().fill_pattern.value != ipLightning) { + contains_only_lightning = false; + } + Polygons fill_polys = to_polygons(region->fill_expolygons()); + unsupported_area = union_(unsupported_area, expand(fill_polys, spacing)); + for (const Surface &surface : region->fill_surfaces()) { + if (surface.surface_type != stInternal || region->region().config().fill_density.value == 100) { + Polygons p = to_polygons(surface.expolygon); + lower_layer_solids.insert(lower_layer_solids.end(), p.begin(), p.end()); } - break; } } - for ( const LayerRegion *region : regions_to_check) { - SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + lower_layer_solids = expand(lower_layer_solids, 4 * spacing); + unsupported_area = shrink(unsupported_area, 5 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); - // filter out surfaces not from this island... TODO sotre this info in the Z-Graph, so that this filtering is not needed - // NOTE: we are keeping even very small internal ensuring overhangs here. The aim is to later differentiate between expanding wall ensuring regions - // where briding them would be conterproductive, and small ensuring islands that expand into large ones, where bridging is quite necessary - region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [slice_island_tree](const Surface *s) { - if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { - return true; - } - return false; - }), - region_internal_solids.end()); - if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill, true).height()); - } + for (const LayerRegion *region : layer->regions()) { + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); for (const Surface *s : region_internal_solids) { - surface_to_region[s] = region; + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON; + if (!unsupported.empty() && (!partially_supported || area(unsupported) > 5 * 5 * spacing * spacing)) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing)); + for (Polygon p : diff(to_polygons(s->expolygon), worth_bridging)) { + if (p.area() < region->flow(frSolidInfill, true).scaled_spacing() * scale_(12.0)) { + worth_bridging.push_back(p); + } + } + worth_bridging = intersection(closing(worth_bridging, 3 * region->flow(frSolidInfill, true).scaled_spacing()), s->expolygon); + candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0, contains_only_lightning)); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)), + to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging), + to_lines(unsupported_area)); +#endif + } } - bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), - region_internal_solids.end()); - auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); - expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); } } + }); - // if there are none briding candidates, exit now, before making infill for the previous layer - if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), - [](const std::pair &candidates) { return candidates.second.empty(); })) { - continue; + for (const CandidateSurface &c : candidate_surfaces) { + surfaces_by_layer[c.layer_index].push_back(c); + } + } + + std::map infill_lines; + // SECTION to generate infill polylines + { + std::vector> surfaces_w_bottom_z; + for (const auto &pair : surfaces_by_layer) { + for (const CandidateSurface &c : pair.second) { + surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z()); } + } - // generate sparse infill polylines from lower layers to get anchorable polylines - Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring(); + this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + this->m_lightning_generator = this->prepare_lightning_infill_data(); - for (std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; + std::vector layers_to_generate_infill; + for (const auto &pair : surfaces_by_layer) { + assert(pair.first > 0); + infill_lines[pair.first - 1] = {}; + layers_to_generate_infill.push_back(pair.first - 1); + } - auto region_has_special_infill = [](const LayerRegion *layer_region) { - switch (layer_region->region().config().fill_pattern.value) { - case ipAdaptiveCubic: return true; - case ipSupportCubic: return true; - case ipLightning: return true; - default: return false; - } - }; + tbb::parallel_for(tbb::blocked_range(0, layers_to_generate_infill.size()), [po = static_cast(this), + &layers_to_generate_infill, + &infill_lines](tbb::blocked_range r) { + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + size_t lidx = layers_to_generate_infill[job_idx]; + infill_lines.at( + lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->m_adaptive_fill_octrees.first.get(), + po->m_adaptive_fill_octrees.second.get(), + po->m_lightning_generator.get()); + } + }); +#ifdef DEBUG_BRIDGE_OVER_INFILL + for (const auto &il : infill_lines) { + debug_draw(std::to_string(il.first) + "_infill_lines", to_lines(get_layer(il.first)->lslices), to_lines(il.second), {}, {}); + } +#endif + } - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill{}; - Polygons special_infill{}; - Polygons not_sparse_infill{}; - { - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - std::vector current_links{}; - current_links.insert(current_links.end(), candidates.first->overlaps_below.begin(), - candidates.first->overlaps_below.end()); - std::vector next_links{}; - for (int i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(po->get_layer(i)->regions()[island.perimeters.region()]); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(po->get_layer(i)->regions()[island.fill_region_id]); - } else { - for (const auto &r : po->get_layer(i)->regions()) { - regions_under_to_check.insert(r); - } - break; - } - } + // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another + std::vector> clustered_layers_for_threads; + float target_flow_height_factor = 0.5; + { + std::vector layers_with_candidates; + std::map layer_area_covered_by_candidates; + for (const auto& pair : surfaces_by_layer) { + layers_with_candidates.push_back(pair.first); + layer_area_covered_by_candidates[pair.first] = {}; + } - for (const LayerRegion *region : regions_under_to_check) { - bool has_low_density = region->region().config().fill_density.value < 100; - bool has_special_infill = region_has_special_infill(region); - for (const Surface &surface : region->fill_surfaces()) { - if (surface.surface_type == stInternal && has_low_density && !has_special_infill) { - Polygons p = to_polygons(surface.expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } else if (surface.surface_type == stInternal && has_low_density && has_special_infill) { - Polygons p = to_polygons(surface.expolygon); - special_infill.insert(special_infill.end(), p.begin(), p.end()); - } else { - Polygons p = to_polygons(surface.expolygon); - not_sparse_infill.insert(not_sparse_infill.end(), p.begin(), p.end()); - } - } - } - } - current_links = next_links; - next_links.clear(); - } + tbb::parallel_for(tbb::blocked_range(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer, + &layer_area_covered_by_candidates]( + tbb::blocked_range r) { + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + size_t lidx = layers_with_candidates[job_idx]; + for (const auto &candidate : surfaces_by_layer.at(lidx)) { + Polygon candiate_inflated_aabb = get_extents(candidate.new_polys) + .inflated(candidate.region->flow(frSolidInfill, true).scaled_spacing() * 5) + .polygon(); + layer_area_covered_by_candidates.at(lidx) = union_(layer_area_covered_by_candidates.at(lidx), + Polygons{candiate_inflated_aabb}); + } + } + }); - lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, - layer->lslices[int(candidates.first - layer->lslices_ex.data())]); - lower_layers_sparse_infill = diff(lower_layers_sparse_infill, not_sparse_infill); - special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); - special_infill = diff(special_infill, not_sparse_infill); + // note: surfaces_by_layer is ordered map + for (auto pair : surfaces_by_layer) { + if (clustered_layers_for_threads.empty() || + this->get_layer(clustered_layers_for_threads.back().back())->print_z < + this->get_layer(pair.first)->print_z - + this->get_layer(pair.first)->regions()[0]->flow(frSolidInfill, true).height() * target_flow_height_factor - + EPSILON || + intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()], + layer_area_covered_by_candidates[pair.first]) + .empty()) { + clustered_layers_for_threads.push_back({pair.first}); + } else { + clustered_layers_for_threads.back().push_back(pair.first); + } + } - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); +#ifdef DEBUG_BRIDGE_OVER_INFILL + std::cout << "BRIDGE OVER INFILL CLUSTERED LAYERS FOR SINGLE THREAD" << std::endl; + for (auto cluster : clustered_layers_for_threads) { + std::cout << "CLUSTER: "; + for (auto l : cluster) { + std::cout << l << " "; + } + std::cout << std::endl; + } +#endif + } - if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { - continue; + // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. + auto gather_areas_w_depth = [target_flow_height_factor](const PrintObject *po, int lidx, float target_flow_height) { + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill{}; + Polygons not_sparse_infill{}; + double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + const Layer *layer = po->get_layer(i); + if (layer->print_z < bottom_z) + break; + + for (const LayerRegion *region : layer->regions()) { + bool has_low_density = region->region().config().fill_density.value < 100; + for (const Surface &surface : region->fill_surfaces()) { + if (surface.surface_type == stInternal && has_low_density) { + Polygons p = to_polygons(surface.expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } else { + Polygons p = to_polygons(surface.expolygon); + not_sparse_infill.insert(not_sparse_infill.end(), p.begin(), p.end()); } } + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + } - if (expansion_space[candidates.first].empty() && special_infill.empty()) { - // there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island - for (const Surface *candidate : candidates.second) { - bridging_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), - surface_to_region[candidate], 0); + return diff(lower_layers_sparse_infill, not_sparse_infill); + }; + + // LAMBDA do determine optimal bridging angle + auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern) { + AABBTreeLines::LinesDistancer lines_tree(anchors); + + std::map counted_directions; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; } - continue; - } - - Polygons expand_area; - for (const Surface *sparse_infill : expansion_space[candidates.first]) { - assert(sparse_infill->surface_type == stInternal); - Polygons a = to_polygons(sparse_infill->expolygon); - expand_area.insert(expand_area.end(), a.begin(), a.end()); - } - - // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that would otherwise - // compete over anchoring sparse infill lines, leaving one area unachored - std::sort(candidates.second.begin(), candidates.second.end(), [](const Surface* left, const Surface* right){ - auto a = get_extents(left->expolygon); - auto b = get_extents(right->expolygon); - - if (a.min.x() == b.min.x()) { - return a.min.y() < b.min.y(); - }; - return a.min.x() < b.min.x(); - }); - - std::unordered_map> infill_and_deep_infill_polygons_per_region; - for (const auto &surface_region : surface_to_region) { - const LayerRegion *r = surface_region.second; - if (infill_and_deep_infill_polygons_per_region.find(r) == infill_and_deep_infill_polygons_per_region.end()) { - const Flow &flow = r->bridging_flow(frSolidInfill, true); - Polygons infill_region = to_polygons(r->fill_expolygons()); - Polygons deep_infill_area = closing(infill_region, scale_(0.01), scale_(0.01) + 4.0 * flow.scaled_spacing()); - Polygons solid_supported_area = expand(not_sparse_infill, 4.0 * flow.scaled_spacing()); - infill_and_deep_infill_polygons_per_region[r] = {closing(infill_region, float(scale_(0.1))), - intersection(lower_layers_sparse_infill, - diff(deep_infill_area, solid_supported_area))}; - } - } - - // Lower layers sparse infill sections gathered - // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate - // bridging. These areas we then expand (within the surrounding sparse infill only!) - // to touch the infill polylines on previous layer. - for (const Surface *candidate : candidates.second) { - const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); - assert(candidate->surface_type == stInternalSolid); - - Polygons bridged_area = intersection(expand(to_polygons(candidate->expolygon), flow.scaled_spacing()), - infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].first); - // cut off parts which are not over sparse infill - material overflow - Polygons worth_bridging = intersection(bridged_area, - infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].second); - if (worth_bridging.empty()) { - continue; - } - bridged_area = intersection(bridged_area, expand(worth_bridging, 5.0 * flow.scaled_spacing())); - - Polygons max_area = expand_area; - max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - max_area = closing(max_area, flow.scaled_spacing()); - - Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - if (!special_infill.empty()) { - auto part_over_special_infill = intersection(special_infill, bridged_area); - auto artificial_boundary = to_polylines(expand(part_over_special_infill, 0.5 * flow.scaled_width())); - anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end()); - -#ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(lidx) + "special", to_lines(part_over_special_infill), to_lines(artificial_boundary), - to_lines(anchors), to_lines(expand_area)); -#endif - } - anchors = diff_pl(anchors, bridged_area); - - Lines anchors_and_walls = to_lines(anchors); - Lines tmp = to_lines(max_area); - anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); - -#ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(lidx) + "candidate", to_lines(candidate->expolygon), to_lines(bridged_area), - to_lines(max_area), (anchors_and_walls)); -#endif - - double bridging_angle = 0; - Polygons tmp_expanded_area = expand(bridged_area, 3.0 * flow.scaled_spacing()); - for (const ModifiedSurface& s : bridging_surfaces[candidates.first]) { - if (!intersection(s.new_polys, tmp_expanded_area).empty()) { - bridging_angle = s.bridge_angle; - break; - } - } - if (bridging_angle == 0) { - AABBTreeLines::LinesDistancer lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)}; - - std::map counted_directions; - for (const Polygon &p : bridged_area) { - for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { - Vec2d start = p.points[point_idx].cast(); - Vec2d next = p.points[point_idx + 1].cast(); - Vec2d v = next - start; // vector from next to current - double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a = (start + v * (i * step_size)).cast(); - auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - double angle = lines_tree.get_line(index).orientation(); - if (angle > PI) { - angle -= PI; - } - angle += PI * 0.5; - counted_directions[angle]++; - } - } - } - - std::pair best_dir{0, 0}; - // sliding window accumulation - for (const auto &dir : counted_directions) { - int score_acc = 0; - double dir_acc = 0; - double window_start_angle = dir.first - PI * 0.1; - double window_end_angle = dir.first + PI * 0.1; - for (auto dirs_window = counted_directions.lower_bound(window_start_angle); - dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { - dir_acc += dirs_window->first * dirs_window->second; - score_acc += dirs_window->second; - } - // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the - // opposite direction. - if (window_start_angle < 0.5 * PI) { - for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); - dirs_window != counted_directions.end(); dirs_window++) { - dir_acc += dirs_window->first * dirs_window->second; - score_acc += dirs_window->second; - } - } - if (window_start_angle > 1.5 * PI) { - for (auto dirs_window = counted_directions.begin(); - dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { - dir_acc += dirs_window->first * dirs_window->second; - score_acc += dirs_window->second; - } - } - - if (score_acc > best_dir.second) { - best_dir = {dir_acc / score_acc, score_acc}; - } - } - bridging_angle = best_dir.first; - if (bridging_angle == 0) { - bridging_angle = 0.001; - } - switch (surface_to_region[candidate]->region().config().fill_pattern.value) { - case ipHilbertCurve: bridging_angle += 0.25 * PI; break; - case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; - default: break; - } - } - - auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { - for (Line &l : lines) { - double ax = double(l.a.x()); - double ay = double(l.a.y()); - l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); - l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); - double bx = double(l.b.x()); - double by = double(l.b.y()); - l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); - l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); - } - }; - - auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { - return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || - (bhigh >= alow && bhigh <= ahigh); - }; - - Polygons expanded_bridged_area{}; - double aligning_angle = -bridging_angle + PI * 0.5; - { - polygons_rotate(bridged_area, aligning_angle); - lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle)); - BoundingBox bb_x = get_extents(bridged_area); - BoundingBox bb_y = get_extents(anchors_and_walls); - - const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); - std::vector vertical_lines(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); - coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); - coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); - vertical_lines[i].a = Point{x, y_min}; - vertical_lines[i].b = Point{x, y_max}; - } - - auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; - auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; - -#ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(lidx) + "sliced", to_lines(bridged_area), anchors_and_walls, - vertical_lines, {}); -#endif - - std::vector> polygon_sections(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); - for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { - if (bridged_area_tree.outside( - (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { - polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, - area_intersections[intersection_idx + 1].first); - } - } - auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - - for (Line §ion : polygon_sections[i]) { - auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), - section.a, - [](const Point &a, const std::pair &b) { - return a.y() > b.first.y(); - }); - if (maybe_below_anchor != anchors_intersections.rend()) { - section.a = maybe_below_anchor->first; - section.a.y() -= flow.scaled_width() * (0.5 + 1.0); - } - - auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), - section.b, - [](const Point &a, const std::pair &b) { - return a.y() < b.first.y(); - }); - if (maybe_upper_anchor != anchors_intersections.end()) { - section.b = maybe_upper_anchor->first; - section.b.y() += flow.scaled_width() * (0.5 + 1.0); - } - } - - for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { - Line §ion_a = polygon_sections[i][section_idx]; - Line §ion_b = polygon_sections[i][section_idx + 1]; - if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { - section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; - section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; - section_a.a = section_a.b; - } - } - - polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const Line &s) { return s.a == s.b; }), - polygon_sections[i].end()); - } - - // reconstruct polygon from polygon sections - struct TracedPoly - { - std::vector lows; - std::vector highs; - }; - - std::vector current_traced_polys; - for (const auto &polygon_slice : polygon_sections) { - std::unordered_set used_segments; - for (TracedPoly &traced_poly : current_traced_polys) { - auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), - traced_poly.lows.back(), [](const Point &low, const Line &seg) { - return seg.b.y() > low.y(); - }); - - if (maybe_first_overlap != polygon_slice.end() && // segment exists - segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), - maybe_first_overlap->b.y())) // segment is overlapping - { - // Overlapping segment. In that case, add it - // to the traced polygon and add segment to used segments - if ((traced_poly.lows.back() - maybe_first_overlap->a).cast().squaredNorm() < - 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { - traced_poly.lows.push_back(maybe_first_overlap->a); - } else { - traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a); - } - - if ((traced_poly.highs.back() - maybe_first_overlap->b).cast().squaredNorm() < - 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { - traced_poly.highs.push_back(maybe_first_overlap->b); - } else { - traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b); - } - used_segments.insert(&(*maybe_first_overlap)); - } else { - // Zero or multiple overlapping segments. Resolving this is nontrivial, - // so we just close this polygon and maybe open several new. This will hopefully happen much less often - traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); - Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); - new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); - traced_poly.lows.clear(); - traced_poly.highs.clear(); - } - } - - current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), - [](const TracedPoly &tp) { return tp.lows.empty(); }), - current_traced_polys.end()); - - for (const auto &segment : polygon_slice) { - if (used_segments.find(&segment) == used_segments.end()) { - TracedPoly &new_tp = current_traced_polys.emplace_back(); - new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0}); - new_tp.lows.push_back(segment.a); - new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.b); - } - } - } - - // add not closed polys - for (TracedPoly &traced_poly : current_traced_polys) { - Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); - new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); - } - -#ifdef DEBUG_BRIDGE_OVER_INFILL - Lines l{}; - for (const auto &s : polygon_sections) { - l.insert(l.end(), s.begin(), s.end()); - } - debug_draw(std::to_string(lidx) + "reconstructed", l, anchors_and_walls_tree.get_lines(), - to_lines(expanded_bridged_area), bridged_area_tree.get_lines()); -#endif - } - - polygons_rotate(expanded_bridged_area, -aligning_angle); - expanded_bridged_area = intersection(expanded_bridged_area, max_area); - expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); - expand_area = diff(expand_area, expanded_bridged_area); - - bridging_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], - bridging_angle); -#ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), - to_lines(max_area), to_lines(expand_area)); -#endif + angle += PI * 0.5; + counted_directions[angle]++; } } } - }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + std::pair best_dir{0, 0}; + // sliding window accumulation + for (const auto &dir : counted_directions) { + int score_acc = 0; + double dir_acc = 0; + double window_start_angle = dir.first - PI * 0.1; + double window_end_angle = dir.first + PI * 0.1; + for (auto dirs_window = counted_directions.lower_bound(window_start_angle); + dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the + // opposite direction. + if (window_start_angle < 0.5 * PI) { + for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); + dirs_window != counted_directions.end(); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + if (window_start_angle > 1.5 * PI) { + for (auto dirs_window = counted_directions.begin(); + dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &bridging_surfaces](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - Layer *layer = po->get_layer(lidx); - std::unordered_map new_surfaces; + if (score_acc > best_dir.second) { + best_dir = {dir_acc / score_acc, score_acc}; + } + } + double bridging_angle = best_dir.first; + if (bridging_angle == 0) { + bridging_angle = 0.001; + } + switch (dominant_pattern) { + case ipHilbertCurve: bridging_angle += 0.25 * PI; break; + case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; + default: break; + } - for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = bridging_surfaces.find(&slice); - modified_surfaces != bridging_surfaces.end()) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(layer->regions()[island.perimeters.region()]); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(layer->regions()[island.fill_region_id]); - } else { - for (LayerRegion *r : layer->regions()) { - regions_to_check.insert(r); - } - break; - } - } + return bridging_angle; + }; - Polygons cut_from_infill{}; - for (const auto &surface : modified_surfaces->second) { - cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); - } + // LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly + // generated lines + auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) { + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } + }; - for (const LayerRegion *region : regions_to_check) { - for (const ModifiedSurface &s : modified_surfaces->second) { - for (const Surface &surface : region->m_fill_surfaces.surfaces) { - if (s.original_surface == &surface) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { - if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) { - new_surfaces[region].emplace_back(tmp, expoly); - } - } - tmp.surface_type = stInternalBridge; - tmp.bridge_angle = s.bridge_angle; - for (const ExPolygon &expoly : union_ex(s.new_polys)) { - new_surfaces[region].emplace_back(tmp, expoly); - } - } else if (surface.surface_type == stInternal) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { - new_surfaces[region].emplace_back(tmp, expoly); - } - } else { - new_surfaces[region].push_back(surface); - } - } - } - } + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + + Polygons expanded_bridged_area{}; + double aligning_angle = -bridging_angle + PI * 0.5; + { + polygons_rotate(bridged_area, aligning_angle); + lines_rotate(anchors, cos(aligning_angle), sin(aligning_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors); + + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, + area_intersections[intersection_idx + 1].first); + } + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + + for (Line §ion : polygon_sections[i]) { + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, + [](const Point &a, const std::pair &b) { + return a.y() > b.first.y(); + }); + if (maybe_below_anchor != anchors_intersections.rend()) { + section.a = maybe_below_anchor->first; + section.a.y() -= bridging_flow.scaled_width() * (0.5 + 1.0); + } + + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.b = maybe_upper_anchor->first; + section.b.y() += bridging_flow.scaled_width() * (0.5 + 1.0); } } - for (LayerRegion *region : layer->regions()) { - if (new_surfaces.find(region) != new_surfaces.end()) { - region->m_fill_surfaces = new_surfaces[region]; + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; + } + } + + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); + } + + // reconstruct polygon from polygon sections + struct TracedPoly + { + std::vector lows; + std::vector highs; + }; + + std::vector current_traced_polys; + for (const auto &polygon_slice : polygon_sections) { + std::unordered_set used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + + if (maybe_first_overlap != polygon_slice.end() && // segment exists + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), + maybe_first_overlap->b.y())) // segment is overlapping + { + // Overlapping segment. In that case, add it + // to the traced polygon and add segment to used segments + if ((traced_poly.lows.back() - maybe_first_overlap->a).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.lows.push_back(maybe_first_overlap->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a); + } + + if ((traced_poly.highs.back() - maybe_first_overlap->b).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.highs.push_back(maybe_first_overlap->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b); + } + used_segments.insert(&(*maybe_first_overlap)); + } else { + // Zero or multiple overlapping segments. Resolving this is nontrivial, + // so we just close this polygon and maybe open several new. This will hopefully happen much less often + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); + } + } + + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); + + for (const auto &segment : polygon_slice) { + if (used_segments.find(&segment) == used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a); + new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); } } } - }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + } + } + + polygons_rotate(expanded_bridged_area, -aligning_angle); + return expanded_bridged_area; + }; + + tbb::parallel_for(tbb::blocked_range(0, clustered_layers_for_threads.size()), [po = static_cast(this), + &surfaces_by_layer, &clustered_layers_for_threads, + gather_areas_w_depth, &infill_lines, + determine_bridging_angle, + construct_anchored_polygon]( + tbb::blocked_range r) { + for (size_t cluster_idx = r.begin(); cluster_idx < r.end(); cluster_idx++) { + for (size_t job_idx = 0; job_idx < clustered_layers_for_threads[cluster_idx].size(); job_idx++) { + size_t lidx = clustered_layers_for_threads[cluster_idx][job_idx]; + const Layer *layer = po->get_layer(lidx); + // this thread has exclusive access to all surfaces in layers enumerated in + // clustered_layers_for_threads[cluster_idx] + + // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that + // would otherwise compete over anchoring sparse infill lines, leaving one area unachored + std::sort(surfaces_by_layer[lidx].begin(), surfaces_by_layer[lidx].end(), + [](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + if (a.min.x() == b.min.x()) { + return a.min.y() < b.min.y(); + }; + return a.min.x() < b.min.x(); + }); + + // Gather deep infill areas, where thick bridges fit + coordf_t thick_bridges_depth = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).height(); + Polygons deep_infill_area = gather_areas_w_depth(po, lidx, thick_bridges_depth * 0.5); + + // Now also remove area that has been already filled on lower layers by bridging expansion - For this + // reason we did the clustering of layers per thread. + double bottom_z = layer->print_z - thick_bridges_depth - EPSILON; + if (job_idx > 0) { + for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) { + size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx]; + const Layer *lower_layer = po->get_layer(lower_layer_idx); + if (lower_layer->print_z >= bottom_z) { + for (const auto &c : surfaces_by_layer[lower_layer_idx]) { + deep_infill_area = diff(deep_infill_area, c.new_polys); + } + } else { + break; + } + } + } + + // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors + Polygons expansion_area; + for (const LayerRegion *region : layer->regions()) { + auto polys = to_polygons(region->fill_surfaces().filter_by_type(stInternal)); + expansion_area.insert(expansion_area.end(), polys.begin(), polys.end()); + } + expansion_area = closing(expansion_area, SCALED_EPSILON); + expansion_area = intersection(expansion_area, deep_infill_area); + Polylines anchors = intersection_pl(infill_lines[lidx - 1], expansion_area); + + std::vector expanded_surfaces; + expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); + for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { + const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); + Polygons area_to_be_bridge = intersection(candidate.new_polys, deep_infill_area); + + if (area_to_be_bridge.empty()) + continue; + + area_to_be_bridge = expand(area_to_be_bridge, flow.scaled_spacing()); + Polygons boundary_area = union_(expansion_area, area_to_be_bridge); + Polylines boundary_plines = to_polylines(boundary_area); + + double bridging_angle = 0; + if (!anchors.empty()) { + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors), + candidate.region->region().config().fill_pattern.value); + } else { + // use expansion boundaries as anchors. + // Also, use Infill pattern that is neutral for angle determination, since there are no infill lines. + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine); + } + + boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end()); + if (candidate.supported_by_lightning) { + boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10))); + } + Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + + // Check collision with other expanded surfaces + { + bool reconstruct = false; + Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); + for (const CandidateSurface &s : expanded_surfaces) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { + bridging_angle = s.bridge_angle; + reconstruct = true; + break; + } + } + if (reconstruct) { + bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + } + } + + bridging_area = intersection(bridging_area, boundary_area); + bridging_area = opening(bridging_area, flow.scaled_spacing()); + expansion_area = diff(expansion_area, bridging_area); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + + "_expanded_bridging", + to_lines(layer->lslices), to_lines(boundary_plines), to_lines(candidate.new_polys), + to_lines(bridging_area)); +#endif + + expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, candidate.layer_index, bridging_area, + candidate.region, bridging_angle, candidate.supported_by_lightning)); + } + surfaces_by_layer[lidx].swap(expanded_surfaces); + expanded_surfaces.clear(); + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end()) + continue; + Layer *layer = po->get_layer(lidx); + + Polygons cut_from_infill{}; + for (const auto &surface : surfaces_by_layer.at(lidx)) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + + for (LayerRegion *region : layer->regions()) { + Surfaces new_surfaces; + + for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { + for (Surface &surface : region->m_fill_surfaces.surfaces) { + if (cs.original_surface == &surface) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cs.new_polys)) { + if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) { + new_surfaces.emplace_back(tmp, expoly); + } + } + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = cs.bridge_angle; + for (const ExPolygon &expoly : union_ex(cs.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } else if (surface.surface_type == stInternal) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } + } + } + region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); + region->m_fill_surfaces.surfaces.erase(std::remove_if(region->m_fill_surfaces.surfaces.begin(), + region->m_fill_surfaces.surfaces.end(), + [](const Surface &s) { return s.empty(); }), + region->m_fill_surfaces.surfaces.end()); + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 33758f3f0..399685e9c 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -89,7 +89,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->minimum_support_area = // this->minimum_bottom_area = // this->support_offset = -// this->support_tree_branch_distance = 2.5 * line_width ?? + this->support_tree_branch_distance = scaled(config.support_tree_branch_distance.value); this->support_tree_angle = std::clamp(config.support_tree_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_angle_slow = std::clamp(config.support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); this->support_tree_branch_diameter = scaled(config.support_tree_branch_diameter.value); @@ -329,6 +329,7 @@ void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord for (int k = int(j - 1); k >= 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); @@ -416,9 +417,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) return (*result).get(); @@ -426,7 +424,11 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L 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); } - const_cast(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel); + if (orig_radius == 0) + // Placable areas for radius 0 are calculated in the general collision code. + return this->getCollision(0, layer_idx, true); + else + const_cast(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel); return getPlaceableAreas(orig_radius, layer_idx, throw_on_cancel); } @@ -470,6 +472,7 @@ void TreeModelVolumes::calculateCollision(const std::vector &ke }); } +// Calculate collisions and placable areas for radius and for layer 0 to max_layer_idx inclusive. void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx, std::function throw_on_cancel) { // assert(radius == this->ceilRadius(radius)); @@ -480,12 +483,14 @@ 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{}); + // Layer range for which the collisions will be calculated. + 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.idx_begin, data.idx_end); for (size_t outline_idx : layer_outline_indices) if (const std::vector &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { @@ -493,8 +498,6 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex const coord_t layer_height = settings.layer_height; 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 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 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. @@ -505,35 +508,40 @@ 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 = std::as_const(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.idx_begin - z_distance_bottom_layers), + std::min(outlines.size(), data.idx_end + z_distance_top_layers)); + tbb::parallel_for(tbb::blocked_range(collision_areas_offsetted.idx_begin, collision_areas_offsetted.idx_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); + 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, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, - xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last, &throw_on_cancel] - (const tbb::blocked_range& range) { - for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++layer_idx) { + const bool processing_last_mesh = outline_idx == layer_outline_indices.size(); + tbb::parallel_for(tbb::blocked_range(data.idx_begin, data.idx_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()) && i <= 0) + 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]); - else if (j >= 0 && layer_idx + i < int(outlines.size()) && i > 0) { + 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[layer_idx + i]); + 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: @@ -566,37 +574,37 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex // 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. - - collision_areas_original = offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2); - append(collisions, collision_areas_original); + 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)); throw_on_cancel(); } }); // 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] + // Now calculate the placable areas. + tbb::parallel_for(tbb::blocked_range(std::max(data.idx_begin, 1), data.idx_end), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, processing_last_mesh, + min_resolution = m_min_resolution, &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 - 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 = collision_areas_offsetted[layer_idx_below]; + Polygons placable = diff(below, 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); @@ -619,9 +627,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex } #endif 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) diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index d2af13c34..f5b5669bb 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -328,6 +328,26 @@ public: } private: + // Caching polygons for a range of layers. + struct LayerPolygonCache { + std::vector polygons; + LayerIndex idx_begin; + LayerIndex idx_end; + + void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) { + this->idx_begin = aidx_begin; + this->idx_end = aidx_end; + this->polygons.assign(aidx_end - aidx_begin, {}); + } + + LayerIndex begin() const { return idx_begin; } + LayerIndex end() const { return idx_end; } + size_t size() const { return polygons.size(); } + + bool has(LayerIndex idx) const { return idx >= idx_begin && idx < idx_end; } + Polygons& operator[](LayerIndex idx) { return polygons[idx + idx_begin]; } + }; + /*! * \brief Convenience typedef for the keys to the caches */ @@ -363,6 +383,13 @@ private: 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.idx_begin; + allocate_layers(i + LayerIndex(in.size())); + for (auto &d : in.polygons) + 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. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 2906f6b93..240bcf7d1 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -744,7 +744,9 @@ 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, + // Polygon to fill in with a zig-zag pattern supporting an overhang. + const Polygons &polygon, + const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { #if 0 @@ -783,6 +785,9 @@ static std::optional> polyline_sample_next_point_at_dis append(lines, to_polylines(polygons)); return lines; #else +// const bool connected_zigzags = roof ? false : config.connect_zigzags; +// const int support_shift = roof ? 0 : support_infill_distance / 2; + const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); FillParams fill_params; @@ -1018,431 +1023,13 @@ int generate_raft_contact( } using SupportElements = std::deque; -/*! - * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. - * - * Generates Points where the Model should be supported and creates the areas where these points have to be placed. - * - * \param mesh[in] The mesh that is currently processed. - * \param move_bounds[out] Storage for the influence areas. - * \param storage[in] Background storage, required for adding roofs. - */ -static void generate_initial_areas( + +void finalize_raft_contact( const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, + const int raft_contact_layer_idx, SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayerStorage &layer_storage, - std::function throw_on_cancel) + std::vector &move_bounds) { - 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, print_object.slicing_parameters() }; - 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 bool min_xy_dist = mesh_config.xy_distance > mesh_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); - // 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)); - // 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) - //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(); - // 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; - - const size_t num_raft_layers = config.raft_layers.size(); - const size_t num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); - const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); - const int raft_contact_layer_idx = generate_raft_contact(print_object, config, top_contacts, layer_storage); - - std::mutex mutex_layer_storage, mutex_movebounds; - std::vector> already_inserted(num_support_layers); - tbb::parallel_for(tbb::blocked_range(first_support_layer, num_support_layers), - [&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; - // 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); - } - - 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)); - } - } - }; - - 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 - }; - - { - 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); - } - - // 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::TopContact, print_object.slicing_parameters(), config, insert_layer_idx - dtt_roof_tip); - append(l->polygons, std::move(added_roofs)); - } - } - } - - for (LineInformation line : lines) { - bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width - for (auto point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); - } - }; - - // 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); - //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 - Polygons remaining_overhang = intersection( - 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)), - 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. - //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; ) { - 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 : - 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; - // 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)); - // Extending the overhangs by the inflated remaining overhangs. - 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 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) { - LineInformations overhang_lines; - { - //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, - // which is then resmapled - // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, - // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate - // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that - // the area that is valid a layer below is to small for support roof. - Polylines polylines = ensure_maximum_distance_polyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); - if (polylines.size() <= 3) - // add the outer wall to ensure it is correct supported instead - polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); - for (const auto &line : polylines) { - LineInformation res_line; - for (Point p : line) - res_line.emplace_back(p, LineStatus::INVALID); - overhang_lines.emplace_back(res_line); - } - validate_range(overhang_lines); - } - for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { - // 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); - // 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); }; - - std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid - overhang_lines = split.first; - // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. - LineInformations fresh_valid_points = 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); - } - } - } - - 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); - 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); - } - 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::TopContact, print_object.slicing_parameters(), config, 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::TopContact, print_object.slicing_parameters(), config, 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); - throw_on_cancel(); - } - } - }); - 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, @@ -1490,6 +1077,559 @@ static void generate_initial_areas( } } +class InterfacePlacer { +public: + InterfacePlacer(const SlicingParameters &slicing_parameters, const TreeModelVolumes &volumes, const TreeSupportSettings &config, bool force_tip_to_roof, size_t num_support_layers, + std::vector &move_bounds, SupportGeneratorLayerStorage &layer_storage, SupportGeneratorLayersPtr &top_contacts) : + slicing_parameters(slicing_parameters), volumes(volumes), config(config), force_tip_to_roof(force_tip_to_roof), + move_bounds(move_bounds), layer_storage(layer_storage), top_contacts(top_contacts) { + m_already_inserted.assign(num_support_layers, {}); + this->min_xy_dist = config.xy_distance > config.xy_min_distance; + } + const SlicingParameters &slicing_parameters; + const TreeModelVolumes &volumes; + const TreeSupportSettings &config; + bool force_tip_to_roof; + bool min_xy_dist; + + // Outputs + std::vector &move_bounds; + SupportGeneratorLayerStorage &layer_storage; + SupportGeneratorLayersPtr &top_contacts; + +private: + // 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::mutex m_mutex_layer_storage; + std::vector> m_already_inserted; + +public: + void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx) + { + SupportGeneratorLayer*& l = top_contacts[insert_layer_idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); + // will be unioned in finalize_interface_and_support_areas() + append(l->polygons, std::move(new_roofs)); + } + + void add_roof(Polygons &&new_roofs, const size_t insert_layer_idx) + { + std::lock_guard lock(m_mutex_layer_storage); + add_roof_unguarded(std::move(new_roofs), insert_layer_idx); + } + + void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + { + if (! new_roofs.empty()) { + std::lock_guard lock(m_mutex_layer_storage); + for (size_t idx = 0; idx < dtt_roof; ++ idx) + if (! new_roofs[idx].empty()) + add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx); + } + } + + void add_roof_build_plate(Polygons &&overhang_areas) + { + std::lock_guard lock(m_mutex_layer_storage); + SupportGeneratorLayer*& l = top_contacts[0]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); + append(l->polygons, std::move(overhang_areas)); + } + + 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. + bool supports_roof, + // 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 > 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); + } + + 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 > 0 || supports_roof, + disable_ovalistation); + } + } + + 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); + { + std::lock_guard critical_section_movebounds(m_mutex_movebounds); + Point hash_pos = p.first / ((config.min_radius + 1) / 10); + 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)); + } + } + }; +}; + +// 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, + // Number of roof (contact, interface) layers between the overhang and tree tips. + const size_t num_support_roof_layers, + // + const coord_t connect_length, + // Configuration classes + const TreeSupportMeshGroupSettings &mesh_group_settings, + const SupportParameters &support_params, + // Configuration & Output + InterfacePlacer &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 = [&support_params, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { + return generate_support_infill_lines(area, support_params, true, layer_idx, mesh_group_settings.support_roof_line_distance); + }; + + LineInformations overhang_lines; + 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. 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 = 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; + } + } + interface_placer.add_roofs(std::move(added_roofs), layer_idx, dtt_roof); + } + + 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, support_params, 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� 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); + } + + if (int(dtt_roof) >= layer_idx && large_horizontal_roof) + // reached buildplate + interface_placer.add_roof_build_plate(std::move(overhang_area)); + 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? + dtt_roof > 0, + // Don't move until the following distance to top is reached. + roof_enabled ? num_support_roof_layers - dtt_roof : 0); + } +} + +/*! + * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. + * + * Generates Points where the Model should be supported and creates the areas where these points have to be placed. + * + * \param mesh[in] The mesh that is currently processed. + * \param move_bounds[out] Storage for the influence areas. + * \param storage[in] Background storage, required for adding roofs. + */ +static 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, + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayerStorage &layer_storage, + std::function throw_on_cancel) +{ + using AvoidanceType = TreeModelVolumes::AvoidanceType; + TreeSupportMeshGroupSettings mesh_group_settings(print_object); + SupportParameters support_params(print_object); + support_params.with_sheath = true; + support_params.support_density = 0; + + // 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 = config.xy_distance > config.xy_min_distance; + +#if 0 + if (mesh.overhang_areas.size() <= z_distance_delta) + return; +#endif + + 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 = 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), 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 * config.support_line_width; + ; + const size_t num_support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + config.layer_height / 2) / config.layer_height : 0; + const bool roof_enabled = num_support_roof_layers > 0; + const bool force_tip_to_roof = sqr(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) * config.layer_height) : std::numeric_limits::max(); + // 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 = config.z_distance_top_layers > 0 ? + std::max(round_up_divide(config.xy_distance, max_overhang_speed / 2), 2 * config.z_distance_top_layers) : + 0; + + size_t num_support_layers; + int raft_contact_layer_idx; + // Layers with their overhang regions. + std::vector> raw_overhangs; + + { + 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, top_contacts, layer_storage); + // 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] }); + } + + InterfacePlacer interface_placer{ print_object.slicing_parameters(), volumes, config, force_tip_to_roof, num_support_layers, + // Outputs + move_bounds, layer_storage, top_contacts }; + + tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), + [&volumes, &config, &raw_overhangs, &mesh_group_settings, &support_params, + min_xy_dist, force_tip_to_roof, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, + &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; + + // 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; + { + // 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, 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 + Polygons remaining_overhang = intersection( + 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), 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. + //+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 + config.support_line_width / 8 < extra_outset; ) { + const coord_t offset_current_step = std::min( + 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 = 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)); + // Extending the overhangs by the inflated remaining overhangs. + 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) { + LineInformations overhang_lines; + { + //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, + // which is then resmapled + // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, + // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate + // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that + // the area that is valid a layer below is to small for support roof. + Polylines polylines = 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); + for (const auto &line : polylines) { + LineInformation res_line; + for (Point p : line) + res_line.emplace_back(p, LineStatus::INVALID); + overhang_lines.emplace_back(res_line); + } + validate_range(overhang_lines); + } + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { + // get least restricted avoidance for layer_idx-lag_ctr + 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); }; + + std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid + overhang_lines = split.first; + // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); + validate_range(fresh_valid_points); + + 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(); + + if (roof_enabled) { + // Try to support the overhangs by dense interfaces for num_support_roof_layers, cover the bottom most interface with tree tips. + static constexpr const coord_t support_roof_offset = 0; + 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)) { + sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, support_params, 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)) { + sample_overhang_area(to_polygons(std::move(support_part)), + // Don't + false, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, support_params, interface_placer); + throw_on_cancel(); + } + } + }); + + finalize_raft_contact(print_object, raft_contact_layer_idx, top_contacts, move_bounds); +} + static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { Point ret = from; @@ -3911,7 +4051,7 @@ 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() }; diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index ff3c0f8f3..f4ec76cda 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -122,7 +122,7 @@ struct SupportElementStateBits { bool use_min_xy_dist : 1; /*! - * \brief True if this Element or any parent provides support to a support roof. + * \brief True if this Element or any parent (element above) provides support to a support roof. */ bool supports_roof : 1; @@ -193,7 +193,7 @@ struct SupportElementState : public SupportElementStateBits 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. + * \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move. */ uint32_t dont_move_until; @@ -218,6 +218,8 @@ struct SupportElementState : public SupportElementStateBits dst.skip_ovalisation = false; return dst; } + + [[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; } }; struct SupportElement diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index b91d1559f..7faa79435 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -34,6 +34,10 @@ // #define SLIC3R_DEBUG_SLICE_PROCESSING +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + #define DEBUG_INTERSECTIONLINE +#endif + #if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) #include "SVG.hpp" #endif @@ -125,7 +129,7 @@ public: }; uint32_t flags { 0 }; -#if DEBUG_INTERSECTIONLINE +#ifdef DEBUG_INTERSECTIONLINE enum class Source { BottomPlane, TopPlane, @@ -1446,19 +1450,19 @@ static std::vector make_slab_loops( for (const IntersectionLine &l : lines.at_slice[slice_below]) if (l.edge_type != IntersectionLine::FacetEdgeType::Top) { in.emplace_back(l); -#if DEBUG_INTERSECTIONLINE +#ifdef DEBUG_INTERSECTIONLINE in.back().source = IntersectionLine::Source::BottomPlane; #endif // DEBUG_INTERSECTIONLINE } } { // Edges in between slice_below and slice_above. -#if DEBUG_INTERSECTIONLINE +#ifdef DEBUG_INTERSECTIONLINE size_t old_size = in.size(); #endif // DEBUG_INTERSECTIONLINE // Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges. append(in, lines.between_slices[line_idx]); -#if DEBUG_INTERSECTIONLINE +#ifdef DEBUG_INTERSECTIONLINE for (auto it = in.begin() + old_size; it != in.end(); ++ it) { assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab); it->source = IntersectionLine::Source::Slab; @@ -1476,7 +1480,7 @@ static std::vector make_slab_loops( l.edge_a_id += num_edges; if (l.edge_b_id >= 0) l.edge_b_id += num_edges; -#if DEBUG_INTERSECTIONLINE +#ifdef DEBUG_INTERSECTIONLINE l.source = IntersectionLine::Source::TopPlane; #endif // DEBUG_INTERSECTIONLINE } diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index bef4c0d89..b4f0dd9d8 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -157,26 +157,23 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } -void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes, bool show_texture) +void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture) { - render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture, false); + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false); } void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor) { - render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, false, true); + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true); } void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, - bool show_axes, bool show_texture, bool picking) + bool show_texture, bool picking) { m_scale_factor = scale_factor; glsafe(::glEnable(GL_DEPTH_TEST)); - if (show_axes) - render_axes(); - m_model.model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR); switch (m_type) @@ -366,9 +363,6 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { - if (!m_show_axes) - return; - if (m_build_volume.valid()) #if ENABLE_WORLD_COORDINATE m_axes.render(Transform3d::Identity(), 0.25f); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 36aefef79..031a8e8a3 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -84,7 +84,6 @@ private: #endif // ENABLE_WORLD_COORDINATE float m_scale_factor{ 1.0f }; - bool m_show_axes{ true }; public: Bed3D() = default; @@ -112,9 +111,8 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void toggle_show_axes() { m_show_axes = !m_show_axes; } - - void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes, bool show_texture); + void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture); + void render_axes(); void render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor); private: @@ -125,8 +123,7 @@ private: void init_contourlines(); static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, - bool show_axes, bool show_texture, bool picking); - void render_axes(); + bool show_texture, bool picking); void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture); void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix); void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5eb1d2087..c8c3923f7 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -291,7 +291,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) (config->opt_bool("support_material") || config->opt_int("support_material_enforce_layers") > 0); for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", - "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_top_rate" }) + "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) toggle_field(key, has_organic_supports); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", diff --git a/src/slic3r/GUI/FileArchiveDialog.cpp b/src/slic3r/GUI/FileArchiveDialog.cpp index 7337258cb..fc2e27bf8 100644 --- a/src/slic3r/GUI/FileArchiveDialog.cpp +++ b/src/slic3r/GUI/FileArchiveDialog.cpp @@ -206,7 +206,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar reduce_stack(stack, struct_size); } if (!file.has_extension() && stack.size() == struct_size) - stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr(nullptr) : stack.back()), GUI::format_wxstr(file.filename().string()), true)); // filename string to wstring? + stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr(nullptr) : stack.back()), boost::nowide::widen(file.filename().string()), true)); // filename string to wstring? return struct_size + 1; }; @@ -223,7 +223,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar path = boost::filesystem::path(extra.substr(0, extra_size)); } else { wxString wname = boost::nowide::widen(stat.m_filename); - std::string name = GUI::format(wname); + std::string name = boost::nowide::narrow(wname); path = boost::filesystem::path(name); } assert(!path.empty()); @@ -247,7 +247,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar if (!stack.empty()) parent = stack.back(); if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files - m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring? + m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring? entry_count++; } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d1d44709e..f95d748ba 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1573,8 +1573,10 @@ void GLCanvas3D::render() _render_objects(GLVolumeCollection::ERenderType::Opaque); _render_sla_slices(); _render_selection(); + if (m_show_bed_axes) + _render_bed_axes(); if (is_looking_downward) - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false); if (!m_main_toolbar.is_enabled()) _render_gcode(); _render_objects(GLVolumeCollection::ERenderType::Transparent); @@ -1597,7 +1599,7 @@ void GLCanvas3D::render() _render_selection_sidebar_hints(); _render_current_gizmo(); if (!is_looking_downward) - _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true); #if ENABLE_RAYCAST_PICKING_DEBUG if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging()) @@ -2355,7 +2357,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #else /* __APPLE__ */ case WXK_CONTROL_D: #endif /* __APPLE__ */ - m_bed.toggle_show_axes(); + m_show_bed_axes = !m_show_bed_axes; m_dirty = true; break; #ifdef __APPLE__ @@ -4423,7 +4425,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const glsafe(::glDisable(GL_DEPTH_TEST)); if (thumbnail_params.show_bed) - _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false); + _render_bed(view_matrix, projection_matrix, !camera.is_looking_downward()); // restore background color if (thumbnail_params.transparent_background) @@ -5477,7 +5479,7 @@ void GLCanvas3D::_render_background() glsafe(::glEnable(GL_DEPTH_TEST)); } -void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) +void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) { float scale_factor = 1.0; #if ENABLE_RETINA_GL @@ -5491,7 +5493,12 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& && m_gizmos.get_current_type() != GLGizmosManager::Seam && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); - m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture); + m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_texture); +} + +void GLCanvas3D::_render_bed_axes() +{ + m_bed.render_axes(); } void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9657be85c..6ebe7bcc6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -522,6 +522,7 @@ private: ECursorType m_cursor_type; GLSelectionRectangle m_rectangle_selection; std::vector m_hover_volume_idxs; + bool m_show_bed_axes{ true }; // Following variable is obsolete and it should be safe to remove it. // I just don't want to do it now before a release (Lukas Matena 24.3.2019) @@ -984,7 +985,8 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); void _render_background(); - void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); + void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); + void _render_bed_axes(); void _render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); void _render_objects(GLVolumeCollection::ERenderType type); void _render_gcode(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0c4953e3a..076bef54b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2305,18 +2305,6 @@ bool GUI_App::load_language(wxString language, bool initial) // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. wxTranslations::Get()->SetLanguage(language_dict); - { - // ysFIXME after fix for wxWidgets issue (https://github.com/wxWidgets/wxWidgets/issues/23210) - // UKR Localization specific workaround till the wxWidgets doesn't fixed: - // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), - // where LocaleTag is a Tag of locale in BCP 47 - like notation. - // For Ukrainian Language LocaleTag is "uk". - // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", - // and, as a result, locales are set to English_United Kingdom - - if (language_info->CanonicalName == "uk") - setlocale(0, language_info->GetCanonicalWithRegion().data()); - } m_wxLocale->AddCatalog(SLIC3R_APP_KEY); m_imgui->set_language(into_u8(language_info->CanonicalName)); //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c16d77751..415f008fb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -707,6 +707,12 @@ void ObjectManipulation::update_ui_from_settings() void ObjectManipulation::update_settings_value(const Selection& selection) { + if (selection.is_empty()) { + // No selection, reset the cache. + reset_settings_value(); + return; + } + m_new_move_label_string = L("Position"); m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); @@ -836,11 +842,6 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } - else { - // No selection, reset the cache. -// assert(selection.is_empty()); - reset_settings_value(); - } } void ObjectManipulation::update_if_dirty() @@ -1395,8 +1396,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double if (new_value > 0.0) change_size_value(axis, new_value); else { - new_value = m_cache.size(axis); - m_cache.size(axis) = 0.0; + Vec3d& size = m_imperial_units ? m_cache.size_inches : m_cache.size; + new_value = size(axis); + size(axis) = 0.0; m_cache.size_rounded(axis) = DBL_MAX; change_size_value(axis, new_value); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3ba67e60e..e9b6fa694 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -799,7 +799,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() void GLGizmoCut3D::render_cut_line() { - if (!cut_line_processing() || m_line_end == Vec3d::Zero()) + if (!cut_line_processing() || m_line_end.isApprox(Vec3d::Zero())) return; glsafe(::glEnable(GL_DEPTH_TEST)); @@ -1130,7 +1130,7 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) rotation[m_hover_id] = theta; const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation); - const bool update_tbb = m_rotation_m.rotation() != rotation_tmp.rotation(); + const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation()); m_rotation_m = rotation_tmp; if (update_tbb) m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m); @@ -1262,7 +1262,7 @@ void GLGizmoCut3D::update_bb() const BoundingBoxf3 box = bounding_box(); if (!box.defined) return; - if (m_max_pos != box.max || m_min_pos != box.min) { + if (!m_max_pos.isApprox(box.max) || !m_min_pos.isApprox(box.min)) { m_bounding_box = box; @@ -1679,7 +1679,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) const bool has_connectors = !connectors.empty(); - const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center == m_plane_center; + const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); wxString act_name = _L("Reset cutting plane"); if (render_reset_button("cut_plane", into_u8(act_name))) { @@ -2247,7 +2247,7 @@ void GLGizmoCut3D::update_connector_shape() bool GLGizmoCut3D::cut_line_processing() const { - return m_line_beg != Vec3d::Zero(); + return !m_line_beg.isApprox(Vec3d::Zero()); } void GLGizmoCut3D::discard_cut_line_processing() diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 905849b8a..defc8be3f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5576,7 +5576,7 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) { wxString wname = boost::nowide::widen(stat.m_filename); - std::string name = GUI::format(wname); + std::string name = boost::nowide::narrow(wname); fs::path archive_path(name); std::string extra(1024, 0); @@ -6503,9 +6503,9 @@ void Plater::export_stl_obj(bool extended, bool selection_only) } } - if (path.EndsWith(".stl")) + if (path.Lower().EndsWith(".stl")) Slic3r::store_stl(path_u8.c_str(), &mesh, true); - else if (path.EndsWith(".obj")) + else if (path.Lower().EndsWith(".obj")) Slic3r::store_obj(path_u8.c_str(), &mesh); // p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6923aaff4..41a015e9f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1531,6 +1531,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter"); optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle"); optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter"); + optgroup->append_single_option_line("support_tree_branch_distance", category_path + "tree_branch_distance"); optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate"); page = add_options_page(L("Speed"), "time"); diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index 97c7ef99d..f6df83b77 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -1024,9 +1024,15 @@ TEST_CASE("pointOnPolygonContour", "[Geometry]") { REQUIRE(getX(first) == getX(ecache.coords(0))); REQUIRE(getY(first) == getY(ecache.coords(0))); - auto last = *std::prev(input.end()); - REQUIRE(getX(last) == getX(ecache.coords(1.0))); - REQUIRE(getY(last) == getY(ecache.coords(1.0))); + if constexpr (ClosureTypeV == Closure::CLOSED) { + auto last = *std::prev(input.end()); + REQUIRE(getX(last) == getX(ecache.coords(1.0))); + REQUIRE(getY(last) == getY(ecache.coords(1.0))); + } else { + auto last = *input.begin(); + REQUIRE(getX(last) == getX(ecache.coords(1.0))); + REQUIRE(getY(last) == getY(ecache.coords(1.0))); + } for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01));