From 57fda9a6ac535ab9247422b365cc8e02e5aa4900 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Mar 2023 13:08:38 +0100 Subject: [PATCH 01/32] Fix for #9991 - Cut tool does not work properly as in alpha 4 --- src/libslic3r/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()); From 9ecf1e413489b39c416ad59fec81303e0659e1aa Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 9 Mar 2023 14:46:15 +0100 Subject: [PATCH 02/32] Fix of ThickPolyline::clip_end() --- src/libslic3r/Polyline.cpp | 45 +++++++++++++++++++++----------------- src/libslic3r/Polyline.hpp | 1 + 2 files changed, 26 insertions(+), 20 deletions(-) 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(); } From 906ccadfbcd7fc360d230fff9370c34ef5a3e980 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Mar 2023 17:21:31 +0100 Subject: [PATCH 03/32] Update for wxWidgets Related to fix for workaround for UKR localization and Fix for #9768 - Need different fixes in interface (points 1 and 3) --- deps/wxWidgets/wxWidgets.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From ca0b218a56ab502a2c51f5e9d7d67ee6a12bbe35 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 10 Mar 2023 08:38:21 +0100 Subject: [PATCH 04/32] Fix of a crash in bridging over sparse #9996 --- src/libslic3r/PrintObject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b9552a2ef..bee233e59 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1658,7 +1658,9 @@ void PrintObject::bridge_over_infill() } // 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(); + Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer + ? po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring(); + : Polylines(); for (std::pair candidates : bridging_surface_candidates) { if (candidates.second.empty()) { From 9ce81d6d12e312d48f6a27573b6bb6990af0ee0b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Mar 2023 09:42:06 +0100 Subject: [PATCH 05/32] Organic Supports improvements: Added support_tree_branch_distance parameter to UI Fixed error in calculation of placeable areas, which made some trees to cut through an object. Locked the tree tips against smoothing of their centerline path. Reduced density of tips with zero interface layers (see continuous_tips). Reduced default support_tree_top_rate to 15% Refactored placement of interfaces for readability. --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 14 +- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/TreeModelVolumes.cpp | 98 +-- src/libslic3r/TreeModelVolumes.hpp | 27 + src/libslic3r/TreeSupport.cpp | 988 +++++++++++++++----------- src/libslic3r/TreeSupport.hpp | 6 +- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 10 files changed, 666 insertions(+), 474 deletions(-) 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/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 bee233e59..bfeb827da 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -693,6 +693,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" 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..2ce404161 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 : + sqrt(sqr(config.min_radius) - sqr(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/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/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"); From 6f5b71b7df9d504254f97c07cfdff62bc6703544 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Mar 2023 09:48:02 +0100 Subject: [PATCH 06/32] Fix of ca0b218a56ab502a2c51f5e9d7d67ee6a12bbe35 --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index bfeb827da..4ddb36b9e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1660,7 +1660,7 @@ void PrintObject::bridge_over_infill() // generate sparse infill polylines from lower layers to get anchorable polylines Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer - ? po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring(); + ? po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring() : Polylines(); for (std::pair candidates : bridging_surface_candidates) { From 750e374357ece077f4bd1415e94c8f2ad01b8dbf Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 10 Mar 2023 09:51:51 +0100 Subject: [PATCH 07/32] Follow-up of f708d9fcb9b84ca98474475152cba45cc793c218 - Fixed rendering of printbed reference axes when any gizmo is open --- src/slic3r/GUI/3DBed.cpp | 14 ++++---------- src/slic3r/GUI/3DBed.hpp | 9 +++------ src/slic3r/GUI/GLCanvas3D.cpp | 19 +++++++++++++------ src/slic3r/GUI/GLCanvas3D.hpp | 4 +++- 4 files changed, 23 insertions(+), 23 deletions(-) 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/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 05cff51fa..1f2f668c3 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 e91a727c5..ad33a5d68 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(); From f6445eefe3b2bb429fdb569267935c53f438290c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 10 Mar 2023 11:49:53 +0100 Subject: [PATCH 08/32] Fix a memory leak in placeholder parser --- src/libslic3r/PlaceholderParser.cpp | 1 + 1 file changed, 1 insertion(+) 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, From fd890888280abf1ad7a1d4e1ee12d32157fadede Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Mar 2023 13:08:45 +0100 Subject: [PATCH 09/32] Fix for SPE-1494 - When inches is enabled, then entered negative or zero value was transformed to the wrong one --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c16d77751..2a172b91f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1395,8 +1395,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); } From 035997a0495ce7e7149a6f01a00faae4aed30817 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Mar 2023 13:47:50 +0100 Subject: [PATCH 10/32] Deleted workaround related to UA localization --- src/slic3r/GUI/GUI_App.cpp | 12 ------------ 1 file changed, 12 deletions(-) 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. From c2fe61261dd4490b63114c8154575e036617c9ec Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 10 Mar 2023 15:21:20 +0100 Subject: [PATCH 11/32] Export STL - check path extension in lower case. #10000 --- src/slic3r/GUI/Plater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 905849b8a..22ce86c9f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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)); } From 2f9c98311498e7f312157bbf0090447019609fd1 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 6 Mar 2023 16:56:29 +0100 Subject: [PATCH 12/32] Fix of wrong use of GUI::format --- src/slic3r/GUI/FileArchiveDialog.cpp | 6 +++--- src/slic3r/GUI/Plater.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 22ce86c9f..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); From 62b84b71128409737ba59d1585939b1974a12782 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 13 Mar 2023 10:34:00 +0100 Subject: [PATCH 13/32] Fix arrange contour cache not addressing last segment - Only when shape closure type was OPEN, which it is for ExPolygon - Also remove duplicated first corner --- .../include/libnest2d/geometry_traits.hpp | 2 +- .../include/libnest2d/placers/nfpplacer.hpp | 33 ++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) 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); From eb31dcec4488a3137ee217a73e4438b4138ccbd4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 13 Mar 2023 11:13:33 +0100 Subject: [PATCH 14/32] Fixed crash when deleting 2nd instance of object --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2a172b91f..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() From 1154f51aabcf581bcd6a7123f699ee1924973e2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 13 Mar 2023 13:52:21 +0100 Subject: [PATCH 15/32] Fix failing libnest2d tests Fix the test itself --- tests/libnest2d/libnest2d_tests_main.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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)); From b0a8757b12b02b97a3142851449e9d6e00927536 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 13 Mar 2023 16:31:48 +0100 Subject: [PATCH 16/32] CutGizmo: Use isApprox() instead of "==" operator for Vec3d values --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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() From 993d6bd561baf57ad2e36c247a39759e2742b666 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 14 Mar 2023 11:11:51 +0100 Subject: [PATCH 17/32] Fixed a hang in tree supports, caused by integer overflow on coord_t. Fixes #10048 --- src/libslic3r/TreeSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 2ce404161..240bcf7d1 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -1451,7 +1451,7 @@ static void generate_initial_areas( // 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 : - sqrt(sqr(config.min_radius) - sqr(config.min_radius - config.support_line_width / 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. From f4e44f975069bcf08531d0d15e9b793f85d5ea41 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 8 Mar 2023 18:42:27 +0100 Subject: [PATCH 18/32] rework of bridiging over sparse infill in progress --- src/libslic3r/Fill/Fill.cpp | 17 +-- src/libslic3r/Layer.hpp | 7 +- src/libslic3r/Print.hpp | 6 +- src/libslic3r/PrintObject.cpp | 194 ++++++++++++++++++++++++++++++---- 4 files changed, 187 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 02f613c15..8ca0199d0 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -486,14 +486,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 +639,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) const { std::vector surface_fills = group_fills(*this); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); @@ -656,14 +648,13 @@ 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 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 ipAdaptiveCubic: + case ipSupportCubic: case ipRectilinear: case ipMonotonic: case ipMonotonicLines: @@ -688,7 +679,7 @@ 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(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index a59c029b8..0cba55d3c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -368,8 +368,11 @@ 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) const; void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 3795c2449..df2bde469 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ +#include "Fill/FillAdaptive.hpp" #include "PrintBase.hpp" #include "BoundingBox.hpp" @@ -385,7 +386,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 +412,8 @@ 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 adaptive_fill_octrees; }; struct WipeTowerData diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4ddb36b9e..33029aac5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -37,7 +37,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -400,7 +402,8 @@ 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(); + const auto& adaptive_fill_octree = this->adaptive_fill_octrees.first; + const auto& support_fill_octree = this->adaptive_fill_octrees.second; auto lightning_generator = this->prepare_lightning_infill_data(); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; @@ -509,7 +512,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 +527,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])); @@ -1582,9 +1582,9 @@ 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) + CandidateSurface(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) {} const Surface *original_surface; @@ -1593,9 +1593,161 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; + 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, true).scaled_spacing(); + Polygons internal_area = shrink(to_polygons(layer->lower_layer->lslices), 4 * spacing); + Polygons lower_layer_solids; + for (const LayerRegion *region : layer->lower_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_layer_solids.insert(lower_layer_solids.end(), p.begin(), p.end()); + } + } + } + lower_layer_solids = expand(lower_layer_solids, 4 * spacing); + internal_area = diff(internal_area, lower_layer_solids); + + for (const LayerRegion *region : layer->regions()) { + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + for (const Surface *s : region_internal_solids) { + Polygons away_from_perimeter = intersection(to_polygons(s->expolygon), internal_area); + if (!away_from_perimeter.empty()) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(away_from_perimeter, 5 * spacing)); + candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); + } + } + } + } + }); + + std::map> surfaces_by_layer; + std::vector> surfaces_w_bottom_z; + for (const CandidateSurface& c : candidate_surfaces) { + surfaces_by_layer[c.region->layer()->id()].push_back(c); + surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z()); + } + + this->adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + + std::map infill_lines; + 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); + } + + tbb::parallel_for(tbb::blocked_range(0, layers_to_generate_infill.size()), [po = static_cast(this), + &infill_lines](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + infill_lines.at( + lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->adaptive_fill_octrees.first.get(), + po->adaptive_fill_octrees.second.get()); + } + }); + + std::vector> jobs; + for (auto pair : surfaces_by_layer) { + if (jobs.empty() || jobs.back().second < pair.first) { + jobs.emplace_back(pair.first, pair.first + 1); + } else { + jobs.back().second = pair.first + 1; + } + } + + auto gahter_lower_layers_sparse_infill = [](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 special_infill{}; + Polygons not_sparse_infill{}; + double bottom_z = po->get_layer(lidx)->print_z - target_flow_height - EPSILON; + 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; + } + } + + 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(); + } + + 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); + + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + + if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { + continue; + } + }; + + tbb::parallel_for(tbb::blocked_range(0, jobs.size()), [po = this, &jobs, &surfaces_by_layer](tbb::blocked_range r) { + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + for (size_t lidx = jobs[job_idx].first; lidx < jobs[job_idx].second; lidx++) { + const Layer *layer = po->get_layer(lidx); + + // 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 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> bridging_surfaces; - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), + 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++) { const Layer *layer = po->get_layer(lidx); From f8e7d1b01c114b4d45f9e221c6b5bb935065d650 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Mar 2023 16:17:08 +0100 Subject: [PATCH 19/32] core implemented, now fixing the issues --- src/libslic3r/Fill/Fill.cpp | 6 +- src/libslic3r/PrintObject.cpp | 956 +++++++++++++--------------------- 2 files changed, 380 insertions(+), 582 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8ca0199d0..a6b420607 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 { @@ -649,7 +650,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc for (SurfaceFill &surface_fill : surface_fills) { switch (surface_fill.params.pattern) { - case ipLightning: continue; break; + case ipLightning: { + auto polylines = to_polylines(shrink_ex(surface_fill.expolygons, 5.0 * surface_fill.params.flow.scaled_spacing())); + sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + }; break; case ipCount: continue; break; case ipSupportBase: continue; break; case ipEnsuring: continue; break; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 33029aac5..42e652437 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" @@ -1648,669 +1649,462 @@ void PrintObject::bridge_over_infill() } 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 lidx = r.begin(); lidx < r.end(); lidx++) { + 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->adaptive_fill_octrees.first.get(), po->adaptive_fill_octrees.second.get()); } }); - std::vector> jobs; + // 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; for (auto pair : surfaces_by_layer) { - if (jobs.empty() || jobs.back().second < pair.first) { - jobs.emplace_back(pair.first, pair.first + 1); + 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() - + EPSILON) { + clustered_layers_for_threads.push_back({pair.first}); } else { - jobs.back().second = pair.first + 1; + clustered_layers_for_threads.back().push_back(pair.first); } } - auto gahter_lower_layers_sparse_infill = [](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 special_infill{}; - Polygons not_sparse_infill{}; - double bottom_z = po->get_layer(lidx)->print_z - target_flow_height - EPSILON; - 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; - } - } + // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. + auto gather_areas_w_depth = + [](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 - 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 : 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 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 && !has_special_infill) { + 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 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()); } } } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + } + + 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; + } + angle += PI * 0.5; + counted_directions[angle]++; + } } - current_links = next_links; - next_links.clear(); } - 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); + 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; + } + } - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - - if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { - continue; + 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; + } + + return bridging_angle; }; - tbb::parallel_for(tbb::blocked_range(0, jobs.size()), [po = this, &jobs, &surfaces_by_layer](tbb::blocked_range r) { - for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { - for (size_t lidx = jobs[job_idx].first; lidx < jobs[job_idx].second; lidx++) { - const Layer *layer = po->get_layer(lidx); - - // 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 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(); - }); + // 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)); } - } - }); + }; - std::unordered_map> bridging_surfaces; + 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); + }; - 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++) { - const Layer *layer = po->get_layer(lidx); + 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); - // 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; + 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}; + } - // 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]); + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; + 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() -= 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 (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 { - for (const auto& r : layer->regions()) { - regions_to_check.insert(r); - } - break; + // 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(); } } - for ( const LayerRegion *region : regions_to_check) { - SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + 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()); - // 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 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); } - for (const Surface *s : region_internal_solids) { - surface_to_region[s] = region; - } - 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; + // 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()); } - // generate sparse infill polylines from lower layers to get anchorable polylines - Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer - ? po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring() - : Polylines(); +#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 + } - for (std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; + polygons_rotate(expanded_bridged_area, -aligning_angle); + return expanded_bridged_area; + }; - 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, clustered_layers_for_threads.size()), [po = 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] - // 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) + // 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); + + // 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 = po->get_layer(lidx)->print_z - thick_bridges_depth - EPSILON; + if (job_idx > 0) { + for (int lower_job_idx = job_idx; 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; - 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; - } - } - - 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(); } - 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); + // 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); + Lines anchors = to_lines(intersection_pl(infill_lines[lidx - 1], expansion_area)); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + 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_bridged = intersection(candidate.new_polys, deep_infill_area); - if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { + if (area_to_be_bridged.empty()) continue; - } - } - 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); - } - 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()) { + Polygons boundary_area = union_(expansion_area, expand(area_to_be_bridged, flow.scaled_spacing())); + Lines boundary_lines = to_lines(boundary_area); + if (boundary_lines.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]) { + double bridging_angle = 0; + Polygons tmp_expanded_area = expand(area_to_be_bridged, 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; 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; + if (!anchors.empty()) { + bridging_angle = determine_bridging_angle(area_to_be_bridged, anchors, + candidate.region->region().config().fill_pattern.value); + } else { + // use expansion boundaries as anchors. However the current area must be removed from such filter. + // Also, use Infill pattern that is neutral for angle determination, since there are no infill lines. + bridging_angle = determine_bridging_angle(area_to_be_bridged, boundary_lines, InfillPattern::ipLine); } } - 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)); - } - }; + boundary_lines.insert(boundary_lines.end(), anchors.begin(), anchors.end()); + Polygons bridged_area = construct_anchored_polygon(area_to_be_bridged, boundary_lines, flow, bridging_angle); + bridged_area = intersection(bridged_area, boundary_area); + bridged_area = opening(bridged_area, flow.scaled_spacing()); + expansion_area = diff(expansion_area, bridged_area); - 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 + expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, bridged_area, candidate.region, bridging_angle)); } + 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, - &bridging_surfaces](tbb::blocked_range r) { + &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); - std::unordered_map new_surfaces; - 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; - } - } - - 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()); - } - - 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); - } - } - } - } - } + 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()) { - if (new_surfaces.find(region) != new_surfaces.end()) { - region->m_fill_surfaces = new_surfaces[region]; + 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()); } } }); From 3782d24ccfbf027ccf910d4c7c029eeb2d252c63 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Mar 2023 17:04:35 +0100 Subject: [PATCH 20/32] Fixed bugs with bridging area determination --- src/libslic3r/PrintObject.cpp | 113 +++++++++++++++++----------------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 42e652437..ad2f9da3c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1604,26 +1604,28 @@ void PrintObject::bridge_over_infill() continue; } auto spacing = layer->regions().front()->flow(frSolidInfill, true).scaled_spacing(); - Polygons internal_area = shrink(to_polygons(layer->lower_layer->lslices), 4 * spacing); + ExPolygons internal_area; Polygons lower_layer_solids; for (const LayerRegion *region : layer->lower_layer->regions()) { - bool has_low_density = region->region().config().fill_density.value < 100; + internal_area.insert(internal_area.end(), region->fill_expolygons().begin(), region->fill_expolygons().end()); for (const Surface &surface : region->fill_surfaces()) { - if (surface.surface_type == stInternal && has_low_density) { + 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()); } } } - lower_layer_solids = expand(lower_layer_solids, 4 * spacing); - internal_area = diff(internal_area, lower_layer_solids); + lower_layer_solids = expand(lower_layer_solids, 4 * spacing); + Polygons unsupported_area = to_polygons(internal_area); + unsupported_area = shrink(unsupported_area, 4 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); for (const LayerRegion *region : layer->regions()) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); for (const Surface *s : region_internal_solids) { - Polygons away_from_perimeter = intersection(to_polygons(s->expolygon), internal_area); - if (!away_from_perimeter.empty()) { - Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(away_from_perimeter, 5 * spacing)); + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + if (!unsupported.empty()) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing)); candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); } } @@ -1956,11 +1958,10 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, clustered_layers_for_threads.size()), [po = this, &surfaces_by_layer, &clustered_layers_for_threads, - &gather_areas_w_depth, - &infill_lines, + &gather_areas_w_depth, &infill_lines, &determine_bridging_angle, - &construct_anchored_polygon] - (tbb::blocked_range r) { + &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]; @@ -2000,6 +2001,7 @@ void PrintObject::bridge_over_infill() break; } } + } // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors Polygons expansion_area; @@ -2046,11 +2048,12 @@ void PrintObject::bridge_over_infill() boundary_lines.insert(boundary_lines.end(), anchors.begin(), anchors.end()); Polygons bridged_area = construct_anchored_polygon(area_to_be_bridged, boundary_lines, flow, bridging_angle); - bridged_area = intersection(bridged_area, boundary_area); - bridged_area = opening(bridged_area, flow.scaled_spacing()); - expansion_area = diff(expansion_area, bridged_area); + bridged_area = intersection(bridged_area, boundary_area); + bridged_area = opening(bridged_area, flow.scaled_spacing()); + expansion_area = diff(expansion_area, bridged_area); - expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, bridged_area, candidate.region, bridging_angle)); + expanded_surfaces.push_back( + CandidateSurface(candidate.original_surface, bridged_area, candidate.region, bridging_angle)); } surfaces_by_layer[lidx].swap(expanded_surfaces); expanded_surfaces.clear(); @@ -2058,58 +2061,56 @@ void PrintObject::bridge_over_infill() } }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + 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); + 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()); - } + 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 (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)) { + 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); } - 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(); } + 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()); } + 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(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() From ad693532d3167f8aca5103e9fd824a79ffdaf393 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 10 Mar 2023 13:13:40 +0100 Subject: [PATCH 21/32] Fixed clustering for threads issue - inverse comparison, lower job idx incorrectly initialized --- src/libslic3r/PrintObject.cpp | 55 +++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ad2f9da3c..4ee24235d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -1633,6 +1634,13 @@ void PrintObject::bridge_over_infill() } }); +#ifdef DEBUG_BRIDGE_OVER_INFILL + for (const auto &c : candidate_surfaces) { + debug_draw(std::to_string(c.region->layer()->id()) + "_candidate_surface_" + std::to_string(area(c.original_surface->expolygon)), + to_lines(c.region->layer()->lslices), to_lines(c.original_surface->expolygon), to_lines(c.new_polys), {}); + } +#endif + std::map> surfaces_by_layer; std::vector> surfaces_w_bottom_z; for (const CandidateSurface& c : candidate_surfaces) { @@ -1661,10 +1669,17 @@ void PrintObject::bridge_over_infill() } }); +#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 + // 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; + // 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 > + 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() - EPSILON) { @@ -1674,6 +1689,17 @@ void PrintObject::bridge_over_infill() } } +#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 + // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. auto gather_areas_w_depth = [](const PrintObject *po, int lidx, float target_flow_height) { @@ -1820,10 +1846,6 @@ void PrintObject::bridge_over_infill() auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; 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]); @@ -1941,15 +1963,6 @@ void PrintObject::bridge_over_infill() 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); @@ -1990,7 +2003,7 @@ void PrintObject::bridge_over_infill() // reason we did the clustering of layers per thread. double bottom_z = po->get_layer(lidx)->print_z - thick_bridges_depth - EPSILON; if (job_idx > 0) { - for (int lower_job_idx = job_idx; lower_job_idx >= 0; lower_job_idx--) { + 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) { @@ -2024,9 +2037,6 @@ void PrintObject::bridge_over_infill() Polygons boundary_area = union_(expansion_area, expand(area_to_be_bridged, flow.scaled_spacing())); Lines boundary_lines = to_lines(boundary_area); - if (boundary_lines.empty()) - continue; - double bridging_angle = 0; Polygons tmp_expanded_area = expand(area_to_be_bridged, 3.0 * flow.scaled_spacing()); for (const CandidateSurface &s : expanded_surfaces) { @@ -2040,7 +2050,7 @@ void PrintObject::bridge_over_infill() bridging_angle = determine_bridging_angle(area_to_be_bridged, anchors, candidate.region->region().config().fill_pattern.value); } else { - // use expansion boundaries as anchors. However the current area must be removed from such filter. + // 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_bridged, boundary_lines, InfillPattern::ipLine); } @@ -2052,6 +2062,13 @@ void PrintObject::bridge_over_infill() bridged_area = opening(bridged_area, flow.scaled_spacing()); expansion_area = diff(expansion_area, bridged_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(candidate.original_surface->expolygon), to_lines(candidate.new_polys), + to_lines(bridged_area)); +#endif + expanded_surfaces.push_back( CandidateSurface(candidate.original_surface, bridged_area, candidate.region, bridging_angle)); } From d223eef38d2d9b1f8608b5522526ce4b1fe0a4b6 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 10 Mar 2023 15:35:11 +0100 Subject: [PATCH 22/32] Do not generate other than sparse infill lines Split jobs if candidates bounding boxes do not overlap - otherwise it can become completely linearized and very slow Improve formatting --- src/libslic3r/Fill/Fill.cpp | 4 + src/libslic3r/PrintObject.cpp | 198 ++++++++++++++++++++-------------- 2 files changed, 121 insertions(+), 81 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a6b420607..8db49e559 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -649,6 +649,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc Polylines sparse_infill_polylines{}; for (SurfaceFill &surface_fill : surface_fills) { + if (surface_fill.surface.surface_type != stInternal) { + continue; + } + switch (surface_fill.params.pattern) { case ipLightning: { auto polylines = to_polylines(shrink_ex(surface_fill.expolygons, 5.0 * surface_fill.params.flow.scaled_spacing())); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4ee24235d..8a1bacb0b 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1595,110 +1595,146 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; - tbb::concurrent_vector candidate_surfaces; + std::map> surfaces_by_layer; - 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, true).scaled_spacing(); - ExPolygons internal_area; - Polygons lower_layer_solids; - for (const LayerRegion *region : layer->lower_layer->regions()) { - internal_area.insert(internal_area.end(), region->fill_expolygons().begin(), region->fill_expolygons().end()); - 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()); + // 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, true).scaled_spacing(); + ExPolygons internal_area; + Polygons lower_layer_solids; + for (const LayerRegion *region : layer->lower_layer->regions()) { + internal_area.insert(internal_area.end(), region->fill_expolygons().begin(), region->fill_expolygons().end()); + 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()); + } + } + } + lower_layer_solids = expand(lower_layer_solids, 4 * spacing); + Polygons unsupported_area = to_polygons(internal_area); + unsupported_area = shrink(unsupported_area, 4 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); + + for (const LayerRegion *region : layer->regions()) { + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + for (const Surface *s : region_internal_solids) { + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + if (!unsupported.empty()) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing)); + candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); + } } } } - lower_layer_solids = expand(lower_layer_solids, 4 * spacing); - Polygons unsupported_area = to_polygons(internal_area); - unsupported_area = shrink(unsupported_area, 4 * spacing); - unsupported_area = diff(unsupported_area, lower_layer_solids); - - for (const LayerRegion *region : layer->regions()) { - SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - for (const Surface *s : region_internal_solids) { - Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); - if (!unsupported.empty()) { - Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing)); - candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); - } - } - } - } - }); + }); #ifdef DEBUG_BRIDGE_OVER_INFILL - for (const auto &c : candidate_surfaces) { - debug_draw(std::to_string(c.region->layer()->id()) + "_candidate_surface_" + std::to_string(area(c.original_surface->expolygon)), - to_lines(c.region->layer()->lslices), to_lines(c.original_surface->expolygon), to_lines(c.new_polys), {}); - } + for (const auto &c : candidate_surfaces) { + debug_draw(std::to_string(c.region->layer()->id()) + "_candidate_surface_" + std::to_string(area(c.original_surface->expolygon)), + to_lines(c.region->layer()->lslices), to_lines(c.original_surface->expolygon), to_lines(c.new_polys), {}); + } #endif - std::map> surfaces_by_layer; - std::vector> surfaces_w_bottom_z; - for (const CandidateSurface& c : candidate_surfaces) { - surfaces_by_layer[c.region->layer()->id()].push_back(c); - surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z()); + for (const CandidateSurface &c : candidate_surfaces) { + surfaces_by_layer[c.region->layer()->id()].push_back(c); + } } - this->adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); - std::map infill_lines; - 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); - } - - 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->adaptive_fill_octrees.first.get(), - po->adaptive_fill_octrees.second.get()); + // 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()); + } } - }); + this->adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + 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); + } + + 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->adaptive_fill_octrees.first.get(), + po->adaptive_fill_octrees.second.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), {}, {}); - } + 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 + } // 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; - // 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() - - EPSILON) { - clustered_layers_for_threads.push_back({pair.first}); - } else { - clustered_layers_for_threads.back().push_back(pair.first); + { + 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] = {}; + } + + 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}); + } + } + }); + + // 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() - + 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); + } } - } #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 << "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; } - std::cout << std::endl; - } #endif + } // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. auto gather_areas_w_depth = From 76209d89ff16f5adb51d26f0aaa63fab84591388 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 10 Mar 2023 16:59:21 +0100 Subject: [PATCH 23/32] Fixed lighting infill crash. TODO filtering of small ensuring regions --- src/libslic3r/Fill/Fill.cpp | 1 + src/libslic3r/PrintObject.cpp | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8db49e559..e42c4babb 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -657,6 +657,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipLightning: { auto polylines = to_polylines(shrink_ex(surface_fill.expolygons, 5.0 * surface_fill.params.flow.scaled_spacing())); sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + continue; }; break; case ipCount: continue; break; case ipSupportBase: continue; break; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8a1bacb0b..38e3d3400 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1560,7 +1560,7 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() -// #define DEBUG_BRIDGE_OVER_INFILL +#define DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) { @@ -1608,10 +1608,11 @@ void PrintObject::bridge_over_infill() continue; } auto spacing = layer->regions().front()->flow(frSolidInfill, true).scaled_spacing(); - ExPolygons internal_area; + Polygons unsupported_area; Polygons lower_layer_solids; for (const LayerRegion *region : layer->lower_layer->regions()) { - internal_area.insert(internal_area.end(), region->fill_expolygons().begin(), region->fill_expolygons().end()); + Polygons fill_polys = to_polygons(region->fill_expolygons()); + unsupported_area = union_(unsupported_area, fill_polys); 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); @@ -1619,31 +1620,32 @@ void PrintObject::bridge_over_infill() } } } - lower_layer_solids = expand(lower_layer_solids, 4 * spacing); - Polygons unsupported_area = to_polygons(internal_area); - unsupported_area = shrink(unsupported_area, 4 * spacing); - unsupported_area = diff(unsupported_area, lower_layer_solids); + + //TODO if region touches the extremes, then check for its area and filter. otherwise, keep even the smallest one + + lower_layer_solids = expand(lower_layer_solids, 4 * spacing); + unsupported_area = shrink(unsupported_area, 4 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); for (const LayerRegion *region : layer->regions()) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); for (const Surface *s : region_internal_solids) { Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); - if (!unsupported.empty()) { + if (area(unsupported) > spacing * spacing) { Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing)); candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(region->layer()->id()) + "_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 } } } } }); -#ifdef DEBUG_BRIDGE_OVER_INFILL - for (const auto &c : candidate_surfaces) { - debug_draw(std::to_string(c.region->layer()->id()) + "_candidate_surface_" + std::to_string(area(c.original_surface->expolygon)), - to_lines(c.region->layer()->lslices), to_lines(c.original_surface->expolygon), to_lines(c.new_polys), {}); - } -#endif - for (const CandidateSurface &c : candidate_surfaces) { surfaces_by_layer[c.region->layer()->id()].push_back(c); } From 487d9209edf0db1620d409a2a2dd614c215a8454 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Mar 2023 13:02:08 +0100 Subject: [PATCH 24/32] Fix and improve region filtering --- src/libslic3r/PrintObject.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 38e3d3400..0ef6043e1 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1607,12 +1607,12 @@ void PrintObject::bridge_over_infill() if (layer->lower_layer == nullptr) { continue; } - auto spacing = layer->regions().front()->flow(frSolidInfill, true).scaled_spacing(); + auto spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); Polygons unsupported_area; Polygons lower_layer_solids; for (const LayerRegion *region : layer->lower_layer->regions()) { Polygons fill_polys = to_polygons(region->fill_expolygons()); - unsupported_area = union_(unsupported_area, fill_polys); + 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); @@ -1621,17 +1621,16 @@ void PrintObject::bridge_over_infill() } } - //TODO if region touches the extremes, then check for its area and filter. otherwise, keep even the smallest one - lower_layer_solids = expand(lower_layer_solids, 4 * spacing); - unsupported_area = shrink(unsupported_area, 4 * spacing); + unsupported_area = shrink(unsupported_area, 5 * spacing); unsupported_area = diff(unsupported_area, lower_layer_solids); for (const LayerRegion *region : layer->regions()) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); for (const Surface *s : region_internal_solids) { - Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); - if (area(unsupported) > spacing * spacing) { + 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)); candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); From 12f1cd0bc06e21f3e5230a19f08d8f0bb57e01c1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Mar 2023 16:08:32 +0100 Subject: [PATCH 25/32] added semi support for lightning infill --- src/libslic3r/Fill/Fill.cpp | 14 ++++--- src/libslic3r/Layer.hpp | 3 +- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintObject.cpp | 72 ++++++++++++++++++++++------------- 4 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e42c4babb..a6e5e1fb4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -640,7 +640,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } -Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree) 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(); @@ -654,14 +654,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc } switch (surface_fill.params.pattern) { - case ipLightning: { - auto polylines = to_polylines(shrink_ex(surface_fill.expolygons, 5.0 * surface_fill.params.flow.scaled_spacing())); - sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); - continue; - }; break; case ipCount: continue; break; case ipSupportBase: continue; break; case ipEnsuring: continue; break; + case ipLightning: case ipAdaptiveCubic: case ipSupportCubic: case ipRectilinear: @@ -692,6 +688,12 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc 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 0cba55d3c..b3d071c9d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -372,7 +372,8 @@ public: 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) const; + 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/Print.hpp b/src/libslic3r/Print.hpp index df2bde469..9fbbe378a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -2,6 +2,7 @@ #define slic3r_Print_hpp_ #include "Fill/FillAdaptive.hpp" +#include "Fill/FillLightning.hpp" #include "PrintBase.hpp" #include "BoundingBox.hpp" @@ -413,7 +414,8 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::pair adaptive_fill_octrees; + std::pair m_adaptive_fill_octrees; + FillLightning::GeneratorPtr m_lightning_generator; }; struct WipeTowerData diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0ef6043e1..2bd19ba5a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -404,17 +404,16 @@ void PrintObject::infill() if (this->set_started(posInfill)) { m_print->set_status(45, L("making infill")); - const auto& adaptive_fill_octree = this->adaptive_fill_octrees.first; - const auto& support_fill_octree = this->adaptive_fill_octrees.second; - 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()); } } ); @@ -1560,7 +1559,7 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() -#define DEBUG_BRIDGE_OVER_INFILL +// #define DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) { @@ -1586,13 +1585,22 @@ void PrintObject::bridge_over_infill() struct CandidateSurface { - CandidateSurface(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, + Polygons new_polys, + const LayerRegion *region, + double bridge_angle, + bool supported_by_lightning) + : original_surface(original_surface) + , new_polys(new_polys) + , region(region) + , bridge_angle(bridge_angle) + , supported_by_lightning(supported_by_lightning) {} const Surface *original_surface; Polygons new_polys; const LayerRegion *region; double bridge_angle; + bool supported_by_lightning; }; std::map> surfaces_by_layer; @@ -1610,7 +1618,11 @@ void PrintObject::bridge_over_infill() 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()) { @@ -1632,7 +1644,7 @@ void PrintObject::bridge_over_infill() 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)); - candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0)); + candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0, contains_only_lightning)); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(region->layer()->id()) + "_candidate_surface_" + std::to_string(area(s->expolygon)), @@ -1660,7 +1672,9 @@ void PrintObject::bridge_over_infill() } } - this->adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + this->m_lightning_generator = this->prepare_lightning_infill_data(); + std::vector layers_to_generate_infill; for (const auto &pair : surfaces_by_layer) { assert(pair.first > 0); @@ -1674,8 +1688,9 @@ void PrintObject::bridge_over_infill() 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->adaptive_fill_octrees.first.get(), - po->adaptive_fill_octrees.second.get()); + 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 @@ -2061,21 +2076,21 @@ void PrintObject::bridge_over_infill() } expansion_area = closing(expansion_area, SCALED_EPSILON); expansion_area = intersection(expansion_area, deep_infill_area); - Lines anchors = to_lines(intersection_pl(infill_lines[lidx - 1], expansion_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_bridged = intersection(candidate.new_polys, deep_infill_area); + Polygons area_to_be_bridge = intersection(candidate.new_polys, deep_infill_area); - if (area_to_be_bridged.empty()) + if (area_to_be_bridge.empty()) continue; - Polygons boundary_area = union_(expansion_area, expand(area_to_be_bridged, flow.scaled_spacing())); - Lines boundary_lines = to_lines(boundary_area); + Polygons boundary_area = union_(expansion_area, expand(area_to_be_bridge, flow.scaled_spacing())); + Polylines boundary_plines = to_polylines(boundary_area); double bridging_angle = 0; - Polygons tmp_expanded_area = expand(area_to_be_bridged, 3.0 * flow.scaled_spacing()); + Polygons tmp_expanded_area = expand(area_to_be_bridge, 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; @@ -2084,30 +2099,33 @@ void PrintObject::bridge_over_infill() } if (bridging_angle == 0) { if (!anchors.empty()) { - bridging_angle = determine_bridging_angle(area_to_be_bridged, anchors, + 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_bridged, boundary_lines, InfillPattern::ipLine); + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine); } } - boundary_lines.insert(boundary_lines.end(), anchors.begin(), anchors.end()); - Polygons bridged_area = construct_anchored_polygon(area_to_be_bridged, boundary_lines, flow, bridging_angle); - bridged_area = intersection(bridged_area, boundary_area); - bridged_area = opening(bridged_area, flow.scaled_spacing()); - expansion_area = diff(expansion_area, bridged_area); + 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); + 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(candidate.original_surface->expolygon), to_lines(candidate.new_polys), - to_lines(bridged_area)); + to_lines(bridging_area)); #endif expanded_surfaces.push_back( - CandidateSurface(candidate.original_surface, bridged_area, candidate.region, bridging_angle)); + CandidateSurface(candidate.original_surface, bridging_area, candidate.region, bridging_angle, candidate.supported_by_lightning)); } surfaces_by_layer[lidx].swap(expanded_surfaces); expanded_surfaces.clear(); From 822ea84e0d483e4a4d8c5bdbb82c7a89e35618e2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Mar 2023 17:08:41 +0100 Subject: [PATCH 26/32] Fix lambda having modify access - not needed --- src/libslic3r/PrintObject.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 2bd19ba5a..68ddb7ba9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2021,11 +2021,11 @@ void PrintObject::bridge_over_infill() return expanded_bridged_area; }; - tbb::parallel_for(tbb::blocked_range(0, clustered_layers_for_threads.size()), [po = this, &surfaces_by_layer, - &clustered_layers_for_threads, - &gather_areas_w_depth, &infill_lines, - &determine_bridging_angle, - &construct_anchored_polygon]( + 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++) { From 4af37ca4a52c812f2b4fa77f2833d2e02c43e44d Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Mar 2023 10:43:31 +0100 Subject: [PATCH 27/32] fix bug - layer id is not same as layer idx, because of raft (super confusing) --- src/libslic3r/PrintObject.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 68ddb7ba9..5efad85b7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1586,17 +1586,20 @@ void PrintObject::bridge_over_infill() struct CandidateSurface { 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; @@ -1644,10 +1647,10 @@ void PrintObject::bridge_over_infill() 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)); - candidate_surfaces.push_back(CandidateSurface(s, worth_bridging, region, 0, contains_only_lightning)); + candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0, contains_only_lightning)); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw(std::to_string(region->layer()->id()) + "_candidate_surface_" + std::to_string(area(s->expolygon)), + 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 @@ -1658,7 +1661,7 @@ void PrintObject::bridge_over_infill() }); for (const CandidateSurface &c : candidate_surfaces) { - surfaces_by_layer[c.region->layer()->id()].push_back(c); + surfaces_by_layer[c.layer_index].push_back(c); } } @@ -2053,7 +2056,7 @@ void PrintObject::bridge_over_infill() // 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 = po->get_layer(lidx)->print_z - thick_bridges_depth - EPSILON; + 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]; @@ -2124,8 +2127,8 @@ void PrintObject::bridge_over_infill() to_lines(bridging_area)); #endif - expanded_surfaces.push_back( - CandidateSurface(candidate.original_surface, bridging_area, candidate.region, bridging_angle, candidate.supported_by_lightning)); + 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(); From 257837c07176e416a72bd1b58ae37bda7170922d Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Mar 2023 11:38:16 +0100 Subject: [PATCH 28/32] Fix one and zero perimeter case - no bridging was generated --- src/libslic3r/PrintObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5efad85b7..8dbf96b26 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2090,7 +2090,7 @@ void PrintObject::bridge_over_infill() if (area_to_be_bridge.empty()) continue; - Polygons boundary_area = union_(expansion_area, 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; Polygons tmp_expanded_area = expand(area_to_be_bridge, 3.0 * flow.scaled_spacing()); @@ -2118,7 +2118,7 @@ void PrintObject::bridge_over_infill() Polygons 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); + 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) + From c6fd339a39b0b7279eadf44936f7c26098ccec7c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Mar 2023 12:12:18 +0100 Subject: [PATCH 29/32] remove cracks in boundary surface via closing - without it, the bridges were sometimes cut in half --- src/libslic3r/PrintObject.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8dbf96b26..b16dea179 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2090,10 +2090,11 @@ void PrintObject::bridge_over_infill() if (area_to_be_bridge.empty()) continue; - Polygons boundary_area = union_(expansion_area, area_to_be_bridge); - Polylines boundary_plines = to_polylines(boundary_area); - double bridging_angle = 0; - Polygons tmp_expanded_area = expand(area_to_be_bridge, 3.0 * flow.scaled_spacing()); + Polygons boundary_area = union_(expansion_area, area_to_be_bridge); + boundary_area = closing(boundary_area, 0.3 * flow.scaled_spacing()); + Polylines boundary_plines = to_polylines(boundary_area); + double bridging_angle = 0; + Polygons tmp_expanded_area = expand(area_to_be_bridge, 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; From eb73bf7f24b94c3b0b273026444c51f2b96078d9 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Mar 2023 16:40:55 +0100 Subject: [PATCH 30/32] Bridging over sparse infill - improve coliding regions merging, smoothen results, dissolve tiny ensuring regions around bridging --- src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintObject.cpp | 110 +++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 48 deletions(-) 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/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b16dea179..ccdc7a914 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1647,6 +1647,12 @@ void PrintObject::bridge_over_infill() 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 @@ -1705,6 +1711,7 @@ void PrintObject::bridge_over_infill() // 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; @@ -1732,7 +1739,8 @@ void PrintObject::bridge_over_infill() 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() - + 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]) @@ -1756,35 +1764,34 @@ void PrintObject::bridge_over_infill() } // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. - auto gather_areas_w_depth = - [](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 - 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; + 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()); - } + 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); } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + } - return diff(lower_layers_sparse_infill, not_sparse_infill); - }; + 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) { @@ -2052,7 +2059,7 @@ void PrintObject::bridge_over_infill() // 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); + 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. @@ -2090,26 +2097,18 @@ void PrintObject::bridge_over_infill() if (area_to_be_bridge.empty()) continue; - Polygons boundary_area = union_(expansion_area, area_to_be_bridge); - boundary_area = closing(boundary_area, 0.3 * flow.scaled_spacing()); - Polylines boundary_plines = to_polylines(boundary_area); - double bridging_angle = 0; - Polygons tmp_expanded_area = expand(area_to_be_bridge, 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; - break; - } - } - if (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); - } + 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()); @@ -2117,6 +2116,23 @@ void PrintObject::bridge_over_infill() 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); @@ -2124,7 +2140,7 @@ void PrintObject::bridge_over_infill() #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(candidate.original_surface->expolygon), to_lines(candidate.new_polys), + to_lines(layer->lslices), to_lines(boundary_plines), to_lines(candidate.new_polys), to_lines(bridging_area)); #endif From f2f9b890961c6f8b0b3b78e22694ceebd962491e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 14 Mar 2023 19:32:27 +0100 Subject: [PATCH 31/32] Fix of #9963 Bridge angle not accept degree but rad Fixes SPE-1583 --- src/libslic3r/LayerRegion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From c585aaa1cbd75c4cd05ff453e4eba6b4eb4b6ec7 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 14 Mar 2023 19:33:24 +0100 Subject: [PATCH 32/32] TriangleMeshSlicer.cpp: Fixed compilation of debug output --- src/libslic3r/TriangleMeshSlicer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 }