diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 9159de617f..e0d699d51a 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -1330,72 +1330,82 @@ SupportGeneratorLayersPtr generate_support_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_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); append(layers_sorted, raft_layers); append(layers_sorted, bottom_contacts); append(layers_sorted, top_contacts); append(layers_sorted, intermediate_layers); append(layers_sorted, interface_layers); append(layers_sorted, base_interface_layers); - // remove dupliated layers + + // remove duplicated layers std::sort(layers_sorted.begin(), layers_sorted.end()); layers_sorted.erase(std::unique(layers_sorted.begin(), layers_sorted.end()), layers_sorted.end()); // 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; + int interface_id = 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. + // Group layers with nearly the same print_z (floating point fuzz). 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 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; + + bool empty_layer = true; + bool has_interface_layer = false; + 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; - } + + if (!layer.polygons.empty()) { + empty_layer = false; + + if (one_of(layer.layer_type, support_types_interface)) + has_interface_layer = true; } + layer.print_z = zavg; - height_min = std::min(height_min, layer.height); + 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; - } + + if (!empty_layer) { + // Default: keep the global interface_id counter. + int this_iface_id = interface_id; + + // Special case: TreeOrganic raft-interface layers. + // Interface IDs may be unstable here, leading to inconsistent angles. + // Use deterministic IDs: alternate 0/1 by distance from the base raft. + bool is_tree_organic = (object.config().support_style == SupportMaterialStyle::smsTreeOrganic || + (object.config().support_style == SupportMaterialStyle::smsDefault && is_tree(object.config().support_type))); + + if (is_tree_organic && + layer_id >= object.slicing_parameters().base_raft_layers && + layer_id < object.slicing_parameters().raft_layers()) + { + // Alternate IDs 0,1,0,1... based on distance from base raft. + this_iface_id = (layer_id - object.slicing_parameters().base_raft_layers) & 1; } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; + // END special case + + object.add_support_layer(layer_id++, this_iface_id, height_min, zavg); + + // Only increment the global counter if this set actually contains interface/contact layers. + if (has_interface_layer) + interface_id++; } + i = j; } + return layers_sorted; } @@ -1571,12 +1581,12 @@ void generate_support_toolpaths( if (filler_base_interface) filler_base_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - const float support_interface_angle = (support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ? - support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); + const float support_interface_angle = support_params.support_interface_angle(support_layer.interface_id()); // Find polygons with the same print_z. SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; @@ -1669,10 +1679,9 @@ void generate_support_toolpaths( filler->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. raft_contact ? support_params.raft_interface_angle(support_layer.interface_id()) : - support_interface_angle; + support_interface_angle; // Use interface angle for the interface layers. double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 655ee0d806..34dc32fb0c 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -111,6 +111,8 @@ struct SupportParameters { SupportMaterialPattern support_pattern = object_config.support_base_pattern; this->with_sheath = object_config.tree_support_wall_count > 0; + // Cache Rectilinear Interlaced flag once + this->interlaced_interface = (object_config.support_interface_pattern == smipRectilinearInterlaced); this->base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; @@ -127,37 +129,10 @@ struct SupportParameters { ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - this->raft_angle_1st_layer = 0.f; - this->raft_angle_base = 0.f; - this->raft_angle_interface = 0.f; - if (slicing_params.base_raft_layers > 1) { - assert(slicing_params.raft_layers() >= 4); - // There are all raft layer types (1st layer, base, interface & contact layers) available. - this->raft_angle_1st_layer = this->interface_angle; - this->raft_angle_base = this->base_angle; - this->raft_angle_interface = this->interface_angle; - if ((slicing_params.interface_raft_layers & 1) == 0) - // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. - this->raft_angle_interface += float(0.5 * M_PI); - } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { - assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); - // 1st layer, interface & contact layers available. - this->raft_angle_1st_layer = this->base_angle; - this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; - } else if (slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 1); - assert(slicing_params.raft_layers() == 1); - this->raft_angle_1st_layer = float(0.5 * M_PI); - this->raft_angle_interface = this->raft_angle_1st_layer; - } else { - // No raft. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 0); - assert(slicing_params.raft_layers() == 0); - } - + this->raft_angle_1st_layer = float(M_PI_2); + this->raft_angle_base = 0.0f; + this->raft_angle_interface = slicing_params.base_raft_layers == 1 ? 0.0f : float(M_PI_2); // make it perpendicular to the layer beneath it + const auto nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_interface_filament - 1); const coordf_t extrusion_width = object_config.line_width.get_abs_value(nozzle_diameter); support_extrusion_width = object_config.support_line_width.get_abs_value(nozzle_diameter); @@ -254,6 +229,8 @@ struct SupportParameters { InfillPattern contact_fill_pattern; // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? bool with_sheath; + // True if support interface pattern is Rectilinear Interlaced + bool interlaced_interface = false; // Branches of organic supports with area larger than this threshold will be extruded with double lines. double tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(5.0)) * M_PI;; @@ -262,8 +239,30 @@ struct SupportParameters { float raft_angle_interface; // Produce a raft interface angle for a given SupportLayer::interface_id() - float raft_interface_angle(size_t interface_id) const - { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } + float raft_interface_angle(size_t interface_id) const + { + // Raft interfaces → always alternate 0°/90° + return this->raft_angle_interface + + ((interface_id & 1) ? float(M_PI_2) : 0.0f); + } + + float support_interface_angle(size_t interface_id) const + { + if (this->support_style == smsSnug) { + // Snug → ±45° + return this->interface_angle + + ((interface_id & 1) ? -float(M_PI_4) : +float(M_PI_4)); + } + + if (this->interlaced_interface) { + // Interlaced → 0°/90° + return this->interface_angle + + ((interface_id & 1) ? float(M_PI_2) : 0.0f); + } + + // Default → fixed + return this->interface_angle; + } bool independent_layer_height = false; const double thresh_big_overhang = Slic3r::sqr(scale_(10)); diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 50272009cd..d7a48288f8 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -618,7 +618,7 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p if(support_pattern == smpLightning) m_support_params.base_fill_pattern = ipLightning; - diameter_angle_scale_factor = std::clamp(m_object_config->tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + diameter_angle_scale_factor = std::clamp(m_object_config->tree_support_branch_diameter_angle * M_PI / 180., 0., M_PI_2 - EPSILON); is_slim = is_tree_slim(support_type, m_support_params.support_style); is_strong = is_tree(support_type) && m_support_params.support_style == smsTreeStrong; base_radius = std::max(MIN_BRANCH_RADIUS, m_object_config->tree_support_branch_diameter.value / 2); @@ -1369,7 +1369,7 @@ void TreeSupport::generate_toolpaths() Flow support_flow = Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); Fill* filler_raft = Fill::new_from_type(ipRectilinear); - filler_raft->angle = layer_nr == 0 ? PI/2 : 0; + filler_raft->angle = layer_nr == 0 ? M_PI_2 : 0; filler_raft->spacing = support_flow.spacing(); FillParams fill_params; @@ -1404,14 +1404,24 @@ void TreeSupport::generate_toolpaths() // raft interfaces for (layer_nr = m_slicing_params.base_raft_layers; - layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; + layer_nr < m_raft_layers; layer_nr++) { SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); Fill* filler_interface = Fill::new_from_type(ipRectilinear); - filler_interface->angle = PI / 2; // interface should be perpendicular to base + + const size_t iface_idx = layer_nr - m_slicing_params.base_raft_layers; + const bool single_raft_base_layer = (m_slicing_params.base_raft_layers == 1); + + // Make interface lines alternate 0°/90° between layers, to improve bonding. + // If only 1 base layer → start at 0°; otherwise (0 or ≥2 base layers) → start at 90°. + filler_interface->angle = + (iface_idx & 1) + ? (single_raft_base_layer ? float(M_PI_2) : 0.0f) + : (single_raft_base_layer ? 0.0f : float(M_PI_2)); + filler_interface->spacing = support_flow.spacing(); FillParams fill_params; @@ -1431,7 +1441,7 @@ void TreeSupport::generate_toolpaths() SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); Fill* filler_raft = Fill::new_from_type(ipRectilinear); - filler_raft->angle = PI / 2; + filler_raft->angle = M_PI_2; filler_raft->spacing = support_flow.spacing(); for (auto& poly : first_non_raft_base) make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false); @@ -1492,6 +1502,10 @@ void TreeSupport::generate_toolpaths() fill_params.density = interface_density; // Note: spacing means the separation between two lines as if they are tightly extruded filler_Roof1stLayer->spacing = interface_flow.spacing(); + + if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + filler_Roof1stLayer->layer_id = area_group.interface_id; + // generate a perimeter first to support interface better ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial, @@ -1505,6 +1519,10 @@ void TreeSupport::generate_toolpaths() // floor_areas fill_params.density = bottom_interface_density; filler_interface->spacing = interface_flow.spacing(); + + if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + filler_interface->layer_id = area_group.interface_id; + fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow); } else if (area_group.type == SupportLayer::RoofType) { @@ -1701,7 +1719,7 @@ void TreeSupport::generate() draw_circles(); profiler.stage_finish(STAGE_DRAW_CIRCLES); - + normalize_interface_ids(); profiler.stage_start(STAGE_GENERATE_TOOLPATHS); m_object->print()->set_status(70, _u8L("Generating support")); @@ -1930,7 +1948,7 @@ void TreeSupport::draw_circles() { double angle; if (SQUARE_SUPPORT) - angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle; + angle = (double) i / CIRCLE_RESOLUTION * TAU + M_PI_4 + nodes_angle; else angle = (double) i / CIRCLE_RESOLUTION * TAU; branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled)); @@ -1994,7 +2012,6 @@ void TreeSupport::draw_circles() coordf_t max_layers_above_base = 0; coordf_t max_layers_above_roof = 0; coordf_t max_layers_above_roof1 = 0; - int interface_id = 0; bool has_circle_node = false; bool need_extra_wall = false; ExPolygons collision_sharp_tails; @@ -2089,18 +2106,17 @@ void TreeSupport::draw_circles() } } - if (obj_layer_nr>0 && node.distance_to_top < 0) + if (obj_layer_nr > 0 && node.distance_to_top < 0) append(roof_gap_areas, area); else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false) { append(roof_1st_layer, area); max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); } - else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false) + else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 && node.is_sharp_tail == false) { append(roof_areas, area); max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); - interface_id = node.obj_layer_nr % top_interface_layers; } else { @@ -2171,7 +2187,6 @@ void TreeSupport::draw_circles() for (auto& expoly : ts_layer->roof_areas) { //if (area(expoly) < SQ(scale_(1))) continue; area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof); - area_groups.back().interface_id = interface_id; } for (auto &expoly : ts_layer->floor_areas) { //if (area(expoly) < SQ(scale_(1))) continue; @@ -2412,6 +2427,27 @@ void TreeSupport::draw_circles() } } +/*! + * Reassigns sequential interface IDs to enforce A/B alternation + * for RectilinearInterlaced support patterns. + */ +void TreeSupport::normalize_interface_ids() +{ + if (m_object_config->support_interface_pattern != smipRectilinearInterlaced) + return; + + size_t iface_parity = 0; + for (int lid = m_raft_layers; lid < m_object->support_layer_count(); ++lid) { + SupportLayer* ts_layer = m_object->get_support_layer(lid); + if (!ts_layer) continue; + + for (auto &ag : ts_layer->area_groups) + ag.interface_id = iface_parity; + + iface_parity ^= 1; + } +} + double SupportNode::diameter_angle_scale_factor; void TreeSupport::drop_nodes() @@ -2423,7 +2459,7 @@ void TreeSupport::drop_nodes() const double angle = config.tree_support_branch_angle.value * M_PI / 180.; const int wall_count = std::max(1, config.tree_support_wall_count.value); double tan_angle = tan(angle); // when nodes are thick, they can move further. this is the max angle - const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); + const coordf_t max_move_distance = (angle < M_PI_2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); const double max_move_distance2 = max_move_distance * max_move_distance; const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index e0446ad5f1..cdc11f543c 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -459,6 +459,21 @@ private: * \param contact_nodes The nodes to draw as support. */ void draw_circles(); + + /*! + * \brief Normalize interface IDs for interlaced supports. + * + * Reassigns sequential IDs to support layers when using + * RectilinearInterlaced, alternating (0,1,0,1,…) so consecutive + * interface layers never share orientation. Applies to all support + * area groups, including transition layers (e.g. TreeStrong + * body-to-interface), as well as top and bottom interfaces, so + * FillRectilinearInterlaced computes correct extrusion angles. + * + * \note Call after support layers are built and before toolpaths, + * so fillers use the normalized IDs. + */ + void normalize_interface_ids(); /*! * \brief Drops down the nodes of the tree support towards the build plate.