diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 214a38139..661ea766c 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/4fd2120c913c20c3bb66ee9d01d8ff5087a8b90a.zip - URL_HASH SHA256=5b59e8b4dccf73e109c6588f6a69bcfe4e02e930af53c43d5d1329c1f3d83ec9 + URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip + URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f0d3c5885..87a5b357f 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1492,7 +1492,7 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& // Perform cut TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); // add small Z offset to better preview upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index d805473b5..5743e38bd 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -267,29 +267,34 @@ ThickLines ThickPolyline::thicklines() const // Removes the given distance from the end of the ThickPolyline void ThickPolyline::clip_end(double distance) { - while (distance > 0) { - Vec2d last_point = this->last_point().cast(); - coordf_t last_width = this->width.back(); - this->points.pop_back(); - this->width.pop_back(); - if (this->points.empty()) - break; - - Vec2d vec = this->last_point().cast() - last_point; - coordf_t width_diff = this->width.back() - last_width; - double vec_length_sqr = vec.squaredNorm(); - if (vec_length_sqr > distance * distance) { - double t = (distance / std::sqrt(vec_length_sqr)); - this->points.emplace_back((last_point + vec * t).cast()); - this->width.emplace_back(last_width + width_diff * t); - assert(this->width.size() == (this->points.size() - 1) * 2); - return; - } else + if (! this->empty()) { + assert(this->width.size() == (this->points.size() - 1) * 2); + while (distance > 0) { + Vec2d last_point = this->last_point().cast(); + this->points.pop_back(); + if (this->points.empty()) { + assert(this->width.empty()); + break; + } + coordf_t last_width = this->width.back(); this->width.pop_back(); - distance -= std::sqrt(vec_length_sqr); + Vec2d vec = this->last_point().cast() - last_point; + coordf_t width_diff = this->width.back() - last_width; + double vec_length_sqr = vec.squaredNorm(); + if (vec_length_sqr > distance * distance) { + double t = (distance / std::sqrt(vec_length_sqr)); + this->points.emplace_back((last_point + vec * t).cast()); + this->width.emplace_back(last_width + width_diff * t); + assert(this->width.size() == (this->points.size() - 1) * 2); + return; + } else + this->width.pop_back(); + + distance -= std::sqrt(vec_length_sqr); + } } - assert(this->width.size() == (this->points.size() - 1) * 2); + assert(this->points.empty() ? this->width.empty() : this->width.size() == (this->points.size() - 1) * 2); } void ThickPolyline::start_at_index(int index) diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8e362f037..1b23388e1 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -187,6 +187,7 @@ struct ThickPolyline { const Point& last_point() const { return this->points.back(); } size_t size() const { return this->points.size(); } bool is_valid() const { return this->points.size() >= 2; } + bool empty() const { return this->points.empty(); } double length() const { return Slic3r::length(this->points); } void clear() { this->points.clear(); this->width.clear(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0e760f3d3..09514200f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -452,7 +452,7 @@ static std::vector s_Preset_print_options { "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", - "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter", + "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fb6a641cb..dbdb36c7d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2910,6 +2910,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + // Tree Support Branch Distance + // How far apart the branches need to be when they touch the model. Making this distance small will cause + // the tree support to touch the model at more points, causing better overhang but making support harder to remove. + def = this->add("support_tree_branch_distance", coFloat); + def->label = L("Branch Distance"); + def->category = L("Support material"); + def->tooltip = L("How far apart the branches need to be when they touch the model. " + "Making this distance small will cause the tree support to touch the model at more points, " + "causing better overhang but making support harder to remove."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + def = this->add("support_tree_top_rate", coPercent); def->label = L("Branch Density"); def->category = L("Support material"); @@ -2921,7 +2933,7 @@ void PrintConfigDef::init_fff_params() def->min = 5; def->max_literal = 35; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(30)); + def->set_default_value(new ConfigOptionPercent(15)); def = this->add("temperature", coInts); def->label = L("Other layers"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a2ccb546b..e5a9f4c41 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -555,6 +555,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_tree_branch_diameter)) ((ConfigOptionFloat, support_tree_branch_diameter_angle)) ((ConfigOptionPercent, support_tree_top_rate)) + ((ConfigOptionFloat, support_tree_branch_distance)) ((ConfigOptionFloat, support_tree_tip_diameter)) // The rest ((ConfigOptionBool, thick_bridges)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b9552a2ef..4ddb36b9e 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" @@ -1658,7 +1659,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()) { 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/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 81284ce5d..f43afdf42 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -109,26 +109,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) @@ -314,9 +311,6 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { - if (!m_show_axes) - return; - if (m_build_volume.valid()) m_axes.render(Transform3d::Identity(), 0.25f); } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 6198955e2..cf20b6a29 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -50,7 +50,6 @@ private: CoordAxes m_axes; float m_scale_factor{ 1.0f }; - bool m_show_axes{ true }; public: Bed3D() = default; @@ -78,9 +77,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: @@ -91,8 +89,7 @@ private: void init_contourlines(); static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, - bool show_axes, bool show_texture, bool picking); - void render_axes(); + bool show_texture, bool picking); void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture); void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix); void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5eb1d2087..c8c3923f7 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -291,7 +291,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) (config->opt_bool("support_material") || config->opt_int("support_material_enforce_layers") > 0); for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", - "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_top_rate" }) + "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) toggle_field(key, has_organic_supports); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3ea788126..e137a1b3e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1571,8 +1571,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); @@ -1595,7 +1597,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()) @@ -2353,7 +2355,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__ @@ -4357,7 +4359,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) @@ -5411,7 +5413,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 @@ -5425,7 +5427,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 757a90389..b5914e456 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -520,6 +520,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) @@ -976,7 +977,8 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); void _render_background(); - void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); + void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); + void _render_bed_axes(); void _render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); void _render_objects(GLVolumeCollection::ERenderType type); void _render_gcode(); diff --git a/src/slic3r/GUI/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");