diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bab9109b7a..cd180e5300 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -137,7 +137,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) static const t_config_enum_values s_keys_map_SupportMaterialStyle { { "grid", smsGrid }, - { "snug", smsSnug } + { "snug", smsSnug }, + { "tree", smsTree } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fc42e1f104..96b7564391 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -573,71 +573,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // intermediate_layers.clear(); // interface_layers.clear(); - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - SupportGeneratorLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - SupportGeneratorLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == SupporLayerType::TopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } + generate_support_layers(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; @@ -3911,6 +3847,82 @@ void modulate_extrusion_by_overlapping_layers( extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } +void generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + SupportGeneratorLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + layers_append(layers_sorted, raft_layers); + layers_append(layers_sorted, bottom_contacts); + layers_append(layers_sorted, top_contacts); + layers_append(layers_sorted, intermediate_layers); + layers_append(layers_sorted, interface_layers); + layers_append(layers_sorted, base_interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + SupportGeneratorLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == SupporLayerType::TopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } +} + void generate_support_toolpaths( SupportLayerPtrs &support_layers, const PrintObjectConfig &config, diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 703ff384c3..e9baa4dbd5 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -147,6 +147,15 @@ struct SupportParameters { bool with_sheath; }; +void generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + // Produce the support G-code. // Used by both classic and tree supports. void generate_support_toolpaths( diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 6ca20cf086..25d5a3d358 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -80,12 +80,17 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr static Polygons calculateMachineBorderCollision(Polygon machine_border) { - Polygons machine_volume_border; // Put a border of 1m around the print volume so that we don't collide. - append(machine_volume_border, offset(machine_border, scaled(1000.))); +#if 1 + //FIXME just returning no border will let tree support legs collide with print bed boundary + return {}; +#else + //FIXME offsetting by 1000mm easily overflows int32_tr coordinate. + Polygons out = offset(machine_border, scaled(1000.), jtMiter, 1.2); machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area. - machine_volume_border.emplace_back(std::move(machine_border)); - return machine_volume_border; + out.emplace_back(std::move(machine_border)); + return out; +#endif } TreeModelVolumes::TreeModelVolumes( @@ -545,7 +550,6 @@ void TreeModelVolumes::calculateCollision(std::deque keys) const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); - const LayerIndex max_anti_overhang_layer = m_anti_overhang.size() - 1; const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. @@ -566,49 +570,48 @@ void TreeModelVolumes::calculateCollision(std::deque keys) if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. - collision_areas = offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2); - append(data[key], collision_areas); // if a key does not exist when it is accessed it is added! + // if a key does not exist when it is accessed it is added! + append(data[key], offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2)); } // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. - for (LayerIndex layer_idx = max_required_layer; layer_idx >= min_layer_bottom; -- layer_idx) { + for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) { key.second = layer_idx; for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { - data[key] = union_(data[key]); - Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; + RadiusLayerPair key_next_layer(radius, layer_idx + 1); + //data[key] = union_(data[key]); + Polygons above = data[key_next_layer]; // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. - above = max_anti_overhang_layer >= layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); - Polygons placeable = diff(data[key], above); - data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = union_(data_placeable[RadiusLayerPair(radius, layer_idx + 1)], placeable); + //FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1? + above = m_anti_overhang.size() > layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); + data_placeable[key_next_layer] = union_(data_placeable[key_next_layer], diff(data[key], above)); } } // Add collision layers above to ensure correct support_top_distance. - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { + for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; ++ layer_idx) { key.second = layer_idx; - for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; layer_offset++) - append(data[key], data[RadiusLayerPair(radius, layer_idx + layer_offset)]); - data[key] = max_anti_overhang_layer >= layer_idx ? union_(data[key], offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(data[key]); + Polygons collisions = std::move(data[key]); + for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset) + append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]); + data[key] = m_anti_overhang.size() > layer_idx ? union_(collisions, offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); } - for (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) { + for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) { // all these dont have the correct z_distance_top_layers as they can still have areas above them auto it = data.find(RadiusLayerPair(radius, layer_idx)); if (it != data.end()) data.erase(it); } - for (auto pair : data) { - pair.second = simplify(pair.second, m_min_resolution); - data_outer[pair.first] = union_(data_outer[pair.first], pair.second); + for (auto pair : data) + data_outer[pair.first] = union_(data_outer[pair.first], simplify(pair.second, m_min_resolution)); + if (radius == 0) { + for (auto pair : data_placeable) + data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], simplify(pair.second, m_min_resolution)); } - if (radius == 0) - for (auto pair : data_placeable) { - pair.second = simplify(pair.second, m_min_resolution); - data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], pair.second); - } } #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -648,7 +651,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke coord_t radius = key.first; coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound); + Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, scaled(0.01)); col = simplify(col, m_min_resolution); data[RadiusLayerPair(radius, layer_idx)] = col; } @@ -663,11 +666,11 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) { const size_t steps = std::abs(distance / max_safe_step_distance); - assert(distance * max_safe_step_distance >= 0); + assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); ExPolygons ret = union_ex(me); for (size_t i = 0; i < steps; ++ i) - ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, 1.2), collision)); - return union_(offset(ret, distance % max_safe_step_distance, jt, 1.2), collision); + ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision)); + return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision); } void TreeModelVolumes::calculateAvoidance(std::deque keys) @@ -768,7 +771,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) key.second = layer; Polygons placeable = getPlaceableAreas(0, layer); placeable = simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. - placeable = offset(union_ex(placeable), - radius); + placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2); data[layer] = std::pair(key, placeable); } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index d519164f41..9930811371 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -27,7 +27,9 @@ #include +#include #include +#include namespace Slic3r { @@ -124,7 +126,7 @@ void TreeSupport::showError(std::string message, bool critical) static bool layer_has_overhangs(const Layer &layer) { for (const LayerRegion* layerm : layer.regions()) - if (layerm->slices.has(stBottom) || layerm->slices.has(stBottom)) + if (layerm->slices.has(stBottom) || layerm->slices.has(stBottomBridge)) return true; return false; } @@ -172,7 +174,7 @@ void TreeSupport::generateSupportAreas(PrintObject& print_object) break; ++ idx; } - this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -1000., -1000. }, Vec2d{ -1000., +1000. }, Vec2d{ +1000., +1000. }, Vec2d{ +1000., -1000. } }, 0.), { idx }); + this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }); } void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) @@ -267,9 +269,17 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo } } + auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + remove_undefined_layers(bottom_contacts); + remove_undefined_layers(top_contacts); + remove_undefined_layers(intermediate_layers); + // Produce the support G-code. // Used by both classic and tree supports. SupportGeneratorLayersPtr raft_layers, interface_layers, base_interface_layers; + generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); generate_support_toolpaths(print_object.support_layers(), print_object.config(), SupportParameters(print_object), print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); @@ -527,7 +537,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in { Polylines result; for (Polyline part : input) { - if (part.empty() == 0) + if (part.empty()) continue; double len = length(part.points); @@ -537,7 +547,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in { // Insert the opposite point of the first one. //FIXME pretty expensive - Polyline pl(line.points); + Polyline pl(part); pl.clip_end(len / 2); line.points.emplace_back(pl.points.back()); } @@ -673,7 +683,7 @@ static [[nodiscard]] Polylines generateSupportInfillLines( filler->layer_id = layer_idx; filler->spacing = flow.spacing(); - fill_params.density = float(roof ? support_params.interface_density : float(filler->spacing) / float(support_infill_distance)); + fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / float(support_infill_distance)); fill_params.dont_adjust = true; Polylines out; @@ -722,7 +732,7 @@ static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons sec if (result.empty()) { BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. - result = union_(offset(to_polylines(first), scaled(0.002)), offset(to_polylines(second), scaled(0.002))); + result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); } } @@ -772,13 +782,16 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance } // offset in steps for (size_t i = 0; i < steps; i++) { - ret = diff(offset(ret, step_size, ClipperLib::jtRound), collision); + ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision); // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. if (i % 10 == 7) ret = polygons_simplify(ret, scaled(0.015)); } // offset the remainder - ret = polygons_simplify(offset(ret, distance - steps * step_size, ClipperLib::jtRound), scaled(0.015)); + float last_offset = distance - steps * step_size; + if (last_offset > SCALED_EPSILON) + ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); + ret = polygons_simplify(ret, scaled(0.015)); if (do_final_difference) ret = diff(ret, collision); @@ -787,12 +800,36 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance // Using the std::deque as an allocator. inline SupportGeneratorLayer& layer_allocate( - std::deque& layer_storage, - SupporLayerType layer_type) + std::deque &layer_storage, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + size_t layer_idx) { layer_storage.push_back(SupportGeneratorLayer()); - layer_storage.back().layer_type = layer_type; - return layer_storage.back(); + SupportGeneratorLayer *layer_new = &layer_storage.back(); + layer_new->layer_type = layer_type; + layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; + layer_new->height = slicing_params.layer_height; + layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; + return *layer_new; +} + +inline SupportGeneratorLayer& layer_allocate( + std::deque &layer_storage, + tbb::spin_mutex& layer_storage_mutex, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + size_t layer_idx) +{ + layer_storage_mutex.lock(); + layer_storage.push_back(SupportGeneratorLayer()); + SupportGeneratorLayer *layer_new = &layer_storage.back(); + layer_storage_mutex.unlock(); + layer_new->layer_type = layer_type; + layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; + layer_new->height = slicing_params.layer_height; + layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; + return *layer_new; } void TreeSupport::generateInitialAreas( @@ -802,6 +839,8 @@ void TreeSupport::generateInitialAreas( SupportGeneratorLayersPtr &top_interface_layers, SupportGeneratorLayerStorage &layer_storage) { + tbb::global_control(tbb::global_control::max_allowed_parallelism, 1); + Polygon base_circle; const int base_radius = 10; for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { @@ -841,10 +880,13 @@ void TreeSupport::generateInitialAreas( for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) continue; - - Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // take the least restrictive avoidance possible + // take the least restrictive avoidance possible + Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : + m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005)); + relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; @@ -921,13 +963,15 @@ void TreeSupport::generateInitialAreas( roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius); added_roofs.emplace_back(roof_circle); } - added_roofs = union_(added_roofs); - { - std::lock_guard critical_section_storage(critical_sections); - SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); - append(l->polygons, std::move(added_roofs)); + if (! added_roofs.empty()) { + added_roofs = union_(added_roofs); + { + std::lock_guard critical_section_storage(critical_sections); + SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), insert_layer_idx - dtt_roof_tip); + append(l->polygons, std::move(added_roofs)); + } } } @@ -945,7 +989,7 @@ void TreeSupport::generateInitialAreas( Polygons overhang_raw = layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)); Polygons overhang_regular = safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang - Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5)), relevant_forbidden); + Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5, jtMiter, 1.2)), relevant_forbidden); coord_t extra_total_offset_acc = 0; // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. @@ -1032,7 +1076,7 @@ void TreeSupport::generateInitialAreas( // here the roof is handled. If roof can not be added the branches will try to not move instead Polygons forbidden_next = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ // prevent rounding errors down the line - forbidden_next = offset(union_ex(forbidden_next), scaled(0.005)); + forbidden_next = offset(union_ex(forbidden_next), scaled(0.005), jtMiter, 1.2); Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter @@ -1066,13 +1110,14 @@ void TreeSupport::generateInitialAreas( { std::lock_guard critical_section_storage(critical_sections); - for (size_t idx = 0; idx < dtt_roof; idx++) { - SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); - // will be unioned in finalizeInterfaceAndSupportAreas - append(l->polygons, std::move(added_roofs[idx])); - } + for (size_t idx = 0; idx < dtt_roof; ++ idx) + if (! added_roofs[idx].empty()) { + SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx - idx); + // will be unioned in finalizeInterfaceAndSupportAreas + append(l->polygons, std::move(added_roofs[idx])); + } } if (overhang_lines.empty()) { @@ -1088,9 +1133,9 @@ void TreeSupport::generateInitialAreas( // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, // as some support is better than none. - Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2); + Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensureMaximumDistancePolyline( - to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length))) < 1 ? + to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < 1 ? reduced_overhang_outset : overhang_outset), connect_length, min_support_points); @@ -1099,11 +1144,11 @@ void TreeSupport::generateInitialAreas( overhang_lines = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer); } - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) { // reached buildplate + if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { // reached buildplate std::lock_guard critical_section_storage(critical_sections); SupportGeneratorLayer*& l = top_contacts[0]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), 0); append(l->polygons, std::move(overhang_outset)); } else // normal trees have to be generated addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); @@ -1314,7 +1359,7 @@ static void mergeHelper( Polygons intersect = intersection(small_rad_increased_by_big_minus_small, bigger_rad.second); if (area(intersect) > tiny_area_threshold) { // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - if (area(offset(intersect, scaled(-0.025))) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. + if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. continue; // Do the actual merge now that the branches are confirmed to be able to intersect. @@ -1365,7 +1410,7 @@ static void mergeHelper( erase.emplace_back(reduced_check_iter->first); erase.emplace_back(influence_iter->first); - Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter reduced_aabb.emplace(key, get_extents(merge)); @@ -1569,8 +1614,11 @@ std::optional TreeSupport::increaseSingleArea(AreaI radius = m_config.getCollisionRadius(current_elem); const coord_t foot_radius_increase = m_config.branch_radius * (std::max(m_config.diameter_scale_bp_radius - m_config.diameter_angle_scale_factor, 0.0)); - double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); +//FIXME bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; +// bool increase_bp_foot = false; if (increase_bp_foot && m_config.getRadius(current_elem) >= m_config.branch_radius && m_config.getRadius(current_elem) >= m_config.increase_radius_until_radius) if (validWithRadius(m_config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { @@ -1749,7 +1797,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (!settings.no_error) { // ERROR CASE // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased - Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005)); + Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005), jtMiter, 1.2); Polygons base_error_area = union_(*parent->area, lines_offset); result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); BOOST_LOG_TRIVIAL(error) << @@ -1832,7 +1880,7 @@ void TreeSupport::createLayerPathing(std::vector>& mov size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible - for (LayerIndex layer_idx = move_bounds.size() - 1; layer_idx > 0; layer_idx--) + for (int layer_idx = int(move_bounds.size()) - 1; layer_idx > 0; -- layer_idx) { // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. bool merge_this_layer = size_t(last_merge - layer_idx) >= merge_every_x_layers; @@ -2138,7 +2186,8 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. + poly = diff(offset(union_(poly), std::min(coord_t(50), m_config.support_line_width / 4), jtMiter, 1.2), + m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. return poly; }; @@ -2171,7 +2220,7 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)); } } @@ -2201,7 +2250,7 @@ void TreeSupport::smoothBranchAreas(std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); std::vector>> update_next(processing.size()); // with this a lock can be avoided @@ -2219,7 +2268,7 @@ void TreeSupport::smoothBranchAreas(std::vectorparents) if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) update_next[processing_idx].emplace_back(std::pair(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area))); @@ -2239,7 +2288,7 @@ void TreeSupport::smoothBranchAreas(std::vector updated_last_iteration; - for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) { + for (int layer_idx = int(layer_tree_polygons.size()) - 2; layer_idx >= 0; -- layer_idx) { std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); std::vector> update_next(processing.size(), std::pair(nullptr, Polygons())); // with this a lock can be avoided @@ -2253,7 +2302,7 @@ void TreeSupport::smoothBranchAreas(std::vectorparents.size(); ++ idx) { SupportElement* parent = data_pair.first->parents[idx]; coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase); + Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase, jtMiter, 1.2); Point direction = data_pair.first->result_on_layer - parent->result_on_layer; // move the polygons object for (auto& outer : result) @@ -2326,7 +2375,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( #endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. - std::mutex critical_sections; + tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { @@ -2338,7 +2387,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { if (support_roof == nullptr) { - support_roof = &layer_allocate(layer_storage, SupporLayerType::TopContact); + support_roof = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx); support_roof->polygons = union_(support_roof_storage[layer_idx]); } else support_roof->polygons = union_(support_roof->polygons, support_roof_storage[layer_idx]); @@ -2383,10 +2432,10 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( // Subtract support floors from the support area and add them to the support floor instead. if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; - if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact); - Polygons floor_layer = std::move(support_bottom->polygons); - Polygons layer_outset = diff(offset(support_layer_storage[layer_idx], m_config.support_bottom_offset), m_volumes.getCollision(0, layer_idx, false)); + Polygons layer_outset = diff( + m_config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], m_config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], + m_volumes.getCollision(0, layer_idx, false)); + Polygons floor_layer; size_t layers_below = 0; while (layers_below <= m_config.support_bottom_layers) { // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_config.performance_interface_skip_layers distance from each other. @@ -2398,14 +2447,18 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( else break; } - support_bottom->polygons = union_(floor_layer); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01))); // Subtract the support floor from the normal support. + if (! floor_layer.empty()) { + if (support_bottom == nullptr) + support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), layer_idx); + support_bottom->polygons = union_(floor_layer, support_bottom->polygons); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. + } } - { + if (! support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer *&l = intermediate_layers[layer_idx]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::Base); + l = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), layer_idx); append(l->polygons, union_(support_layer_storage[layer_idx])); } diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index a2fc49f01d..894705a32d 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -200,10 +200,11 @@ public: if (config.diameter_scale_bp_radius > 0) { coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); - elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch + // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); } - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. last_area_increase = { std::min(first.last_area_increase.type, second.last_area_increase.type), @@ -398,9 +399,7 @@ public: // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. if (has_to_rely_on_min_xy_dist_only) - { xy_min_distance = std::max(coord_t(100), xy_min_distance); // If set to low rounding errors WILL cause errors. Best to keep it above 25. - } xy_distance = std::max(xy_distance, xy_min_distance); @@ -678,7 +677,7 @@ public: */ [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const { - double scale = (layer_start_bp_radius - layer_idx) * diameter_scale_bp_radius; + double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; return scale > 0 ? branch_radius + branch_radius * scale : 0; }