From 96d46e7f82ae0dc25bc8d3c84e243db5a78d02c9 Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:48:31 +0300 Subject: [PATCH] Refactor skirt/brim + bugfixes related to them (#14333) Refactor skirt and brim ownership and emission flow Refactor skirt and brim generation around a common object/group ownership model. Skirts and brims are now emitted as a coordinated preamble (skirt -> brim -> object) instead of being generated and emitted through multiple independent code paths. Changes: - Fix repeated skirt emission caused by the previous skirt state tracking logic. - Restore local skirt/brim ordering for per-object skirts in By Layer mode. - Emit brims together with their owning object or object group. - Handle combined brims independently from skirt grouping. - Handle draft shields through the same ownership model as skirts. - Fix draft shield generation when skirt height is zero. - Generate draft shields after brim geometry is known, preventing draft shields from overlapping brims. - Reject unsafe grouped per-object skirt configurations in By Object mode. - Remove legacy skirt emission paths and state-management workarounds. Support brim generation remains unchanged. Co-authored-by: SoftFever --- src/libslic3r/Brim.cpp | 38 ++++-- src/libslic3r/Brim.hpp | 3 + src/libslic3r/GCode.cpp | 250 ++++++++++++++++++---------------------- src/libslic3r/GCode.hpp | 13 ++- src/libslic3r/Print.cpp | 149 ++++++++++++++++++------ src/libslic3r/Print.hpp | 18 ++- 6 files changed, 283 insertions(+), 188 deletions(-) diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 814ebf7f7c9..91d72faa70d 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -833,8 +833,7 @@ Polygons tryExPolygonOffset(const ExPolygons& islandAreaEx, const Print& print) } return loops; } -//BBS: a function creates the ExtrusionEntityCollection from the brim area defined by ExPolygons -ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area) { +static ExtrusionEntityCollection makeBrimInfillImpl(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area, bool apply_plate_offset) { Polygons loops = tryExPolygonOffset(singleBrimArea, print); Flow flow = print.brim_flow(); loops = union_pt_chained_outside_in(loops); @@ -864,16 +863,29 @@ ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const optimize_polylines_by_reversing(&all_loops); all_loops = connect_brim_lines(std::move(all_loops), offset(singleBrimArea, float(SCALED_EPSILON)), float(flow.scaled_spacing()) * 2.f); - //BBS: finally apply the plate offset which may very large - auto plate_offset = print.get_plate_origin(); - Point scaled_plate_offset = Point(scaled(plate_offset.x()), scaled(plate_offset.y())); - for (Polyline& one_loop : all_loops) - one_loop.translate(scaled_plate_offset); + if (apply_plate_offset) { + //BBS: finally apply the plate offset which may very large + auto plate_offset = print.get_plate_origin(); + Point scaled_plate_offset = Point(scaled(plate_offset.x()), scaled(plate_offset.y())); + for (Polyline& one_loop : all_loops) + one_loop.translate(scaled_plate_offset); + } extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erBrim, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); return brim; } +//BBS: a function creates the ExtrusionEntityCollection from the brim area defined by ExPolygons +ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area) +{ + return makeBrimInfillImpl(singleBrimArea, print, islands_area, true); +} + +ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area) +{ + return makeBrimInfillImpl(singleBrimArea, print, islands_area, false); +} + //BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area, std::map& brimMap, @@ -943,11 +955,13 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ if (supportBrimAreasOut != nullptr) *supportBrimAreasOut = translate_area_map(supportBrimAreaMap); - const bool combine_brims = print.config().combine_brims.value; - const bool is_by_object = (print.config().print_sequence == PrintSequence::ByObject); - const bool can_combine_brims = combine_brims && !is_by_object; + const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject && + (print.has_skirt() || print.has_infinite_skirt()); + const bool combine_brims = print.config().combine_brims.value && + !has_per_object_skirt_or_shield && + print.config().print_sequence != PrintSequence::ByObject; - if (!can_combine_brims) { + if (!combine_brims) { // Orca: Generate brims separately when brims cannot be combined. for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) { if (!iter->second.empty()) { @@ -960,7 +974,7 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ }; } } else { - // Orca: Unified brim mode (non-sequential printing) + // Orca: Unified brim mode. ExPolygons all_brims_merged; std::vector brim_object_ids; diff --git a/src/libslic3r/Brim.hpp b/src/libslic3r/Brim.hpp index 52f7d741e32..2afd8122470 100644 --- a/src/libslic3r/Brim.hpp +++ b/src/libslic3r/Brim.hpp @@ -24,6 +24,9 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, std::map* objectBrimAreasOut = nullptr, std::map* supportBrimAreasOut = nullptr); +ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area); +ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area); + // BBS: automatically make brim ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bcfc5722448..c40003e71b3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2032,6 +2032,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu // BBS m_curr_print = print; + m_skirt_group_done.clear(); GCodeWriter::full_gcode_comment = print->config().gcode_comments; CNumericLocalesSetter locales_setter; @@ -3223,6 +3224,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. file.write(this->set_extruder(initial_extruder_id, 0.)); } + + this->m_objsWithBrim.clear(); + this->m_objSupportsWithBrim.clear(); + m_brim_done = false; + // BBS: set that indicates objs with brim for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) { if (!iter->second.empty()) @@ -4282,7 +4288,8 @@ namespace Skirt { std::map> skirt_loops_per_extruder_out; //For sequential print, the following test may fail when extruding the 2nd and other objects. // assert(skirt_done.empty()); - if (skirt_done.empty() && print.has_skirt() && ! skirt.entities.empty() && layer_tools.has_skirt) { + const bool has_skirt_or_draft_shield = print.has_skirt() || print.has_infinite_skirt(); + if (skirt_done.empty() && has_skirt_or_draft_shield && ! skirt.entities.empty() && layer_tools.has_skirt) { skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); skirt_done.emplace_back(layer_tools.print_z); } @@ -4299,15 +4306,16 @@ namespace Skirt { // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; - if (print.has_skirt() && ! skirt.entities.empty() && layer_tools.has_skirt && + const bool has_skirt_or_draft_shield = print.has_skirt() || print.has_infinite_skirt(); + if (has_skirt_or_draft_shield && ! skirt.entities.empty() && layer_tools.has_skirt && // Not enough skirt layers printed yet. //FIXME infinite or high skirt does not make sense for sequential print! (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) { - bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; - assert(valid); - // This print_z has not been extruded yet (sequential print) - // FIXME: The skirt_done should not be empty at this point. The check is a workaround - if (valid) { + assert(!skirt_done.empty()); + if (skirt_done.empty()) + return skirt_loops_per_extruder_out; + // This print_z has not been extruded yet. + if (skirt_done.back() < layer_tools.print_z - EPSILON) { #if 0 // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); @@ -4315,7 +4323,6 @@ namespace Skirt { // Prime all extruders planned for this layer, see skirt_loops_per_extruder_all_printing(print, skirt, layer_tools, skirt_loops_per_extruder_out); #endif - assert(!skirt_done.empty()); skirt_done.emplace_back(layer_tools.print_z); } } @@ -4382,7 +4389,8 @@ std::string GCode::generate_skirt(const Print &print, const float skirt_start_angle, const LayerTools &layer_tools, const Layer& layer, - unsigned int extruder_id) + unsigned int extruder_id, + std::vector &skirt_done) { bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); @@ -4392,8 +4400,8 @@ std::string GCode::generate_skirt(const Print &print, // Map from extruder ID to index of skirt loops to be extruded with that extruder. std::map> skirt_loops_per_extruder; skirt_loops_per_extruder = first_layer ? - Skirt::make_skirt_loops_per_extruder_1st_layer(print, skirt, layer_tools, m_skirt_done) : - Skirt::make_skirt_loops_per_extruder_other_layers(print, skirt, layer_tools, m_skirt_done); + Skirt::make_skirt_loops_per_extruder_1st_layer(print, skirt, layer_tools, skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, skirt, layer_tools, skirt_done); if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; @@ -4401,7 +4409,7 @@ std::string GCode::generate_skirt(const Print &print, set_origin(unscaled(offset)); m_avoid_crossing_perimeters.use_external_mp(); - Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); + Flow layer_skirt_flow = print.skirt_flow().with_height(float(skirt_done.back() - (skirt_done.size() == 1 ? 0. : skirt_done[skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); // Decide where to start looping: // - If it’s the first layer or if we do NOT want a single-wall skirt/draft shield, @@ -4443,6 +4451,84 @@ std::string GCode::generate_skirt(const Print &print, return gcode; } +static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id) +{ + const std::vector& groups = print.skirt_brim_groups(); + for (size_t idx = 0; idx < groups.size(); ++idx) + if (std::find(groups[idx].object_ids.begin(), groups[idx].object_ids.end(), object_id) != groups[idx].object_ids.end()) + return idx; + return size_t(-1); +} + +std::string GCode::generate_object_skirt_group(const Print &print, + const PrintObject &object, + const LayerTools &layer_tools, + const Layer& layer, + unsigned int extruder_id) +{ + if (print.config().skirt_type != stPerObject || print.skirt_brim_groups().empty()) + return {}; + + const size_t group_idx = find_skirt_brim_group_idx(print, object.id()); + if (group_idx == size_t(-1) || print.skirt_brim_groups()[group_idx].skirt.empty()) + return {}; + + LayerTools object_skirt_tools = layer_tools; + object_skirt_tools.extruders = { extruder_id }; + object_skirt_tools.has_skirt = true; + + return generate_skirt(print, print.skirt_brim_groups()[group_idx].skirt, Point(0, 0), object.config().skirt_start_angle, + object_skirt_tools, layer, extruder_id, m_skirt_group_done[group_idx]); +} + +std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, bool first_layer) +{ + if (!first_layer) + return {}; + + auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector& object_ids) { + std::string gcode; + const bool already_emitted = std::none_of(object_ids.begin(), object_ids.end(), [this](ObjectID object_id) { + return m_objsWithBrim.find(object_id) != m_objsWithBrim.end(); + }); + if (already_emitted || brim.empty()) + return gcode; + + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity* ee : brim.entities) + if (ee != nullptr) + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + m_avoid_crossing_perimeters.use_external_mp(false); + m_avoid_crossing_perimeters.disable_once(); + for (ObjectID object_id : object_ids) + m_objsWithBrim.erase(object_id); + return gcode; + }; + + const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject && + (print.has_skirt() || print.has_infinite_skirt()); + if (print.config().combine_brims && !has_per_object_skirt_or_shield && + print.config().print_sequence != PrintSequence::ByObject && print.m_brimMap.size() == 1) { + const auto brim_it = print.m_brimMap.begin(); + return emit_brim(brim_it->second, { brim_it->first }); + } + + const size_t group_idx = find_skirt_brim_group_idx(print, object.id()); + if (group_idx != size_t(-1)) { + std::string gcode; + for (const Print::SkirtBrimGroup::Brim& brim : print.skirt_brim_groups()[group_idx].brims) + if (std::find(brim.object_ids.begin(), brim.object_ids.end(), object.id()) != brim.object_ids.end()) + gcode += emit_brim(brim.brim, brim.object_ids); + return gcode; + } + + const auto brim_it = print.m_brimMap.find(object.id()); + if (brim_it == print.m_brimMap.end()) + return {}; + return emit_brim(brim_it->second, { object.id() }); +} + // In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. @@ -4554,8 +4640,6 @@ LayerResult GCode::process_layer( } PrinterStructure printer_structure = m_config.printer_structure.value; - PrintSequence print_sequence = m_config.print_sequence; - bool sequence_by_layer = print_sequence == PrintSequence::ByLayer; bool is_i3_printer = printer_structure == PrinterStructure::psI3; bool is_multi_extruder = m_config.nozzle_diameter.size() > 1; @@ -5151,27 +5235,17 @@ LayerResult GCode::process_layer( bool has_insert_wrapping_detection_gcode = false; // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. + m_skirt_group_done.resize(print.skirt_brim_groups().size()); for (unsigned int extruder_id : layer_tools.extruders) { - if ((print.config().skirt_type == stCombined || - (print.config().skirt_type == stPerObject && print.config().print_sequence == PrintSequence::ByLayer)) && - !print.skirt_groups().empty()) { - bool skirt_generated_for_current_print_z = false; - for (const ExtrusionEntityCollection& skirt_group : print.skirt_groups()) { - if (skirt_group.empty()) + if (print.config().skirt_type == stCombined && !print.skirt_brim_groups().empty()) { + for (size_t group_idx = 0; group_idx < print.skirt_brim_groups().size(); ++group_idx) { + const Print::SkirtBrimGroup& group = print.skirt_brim_groups()[group_idx]; + if (group.skirt.empty()) continue; - // Orca: each grouped skirt is emitted as its own collection so higher skirt layers - // follow the same per-group behavior as the first layer. - if (first_layer) - m_skirt_done.clear(); - else if (skirt_generated_for_current_print_z && !m_skirt_done.empty()) - m_skirt_done.pop_back(); - - std::string skirt_gcode = generate_skirt(print, skirt_group, Point(0, 0), layer.object()->config().skirt_start_angle, - layer_tools, layer, extruder_id); - if (!skirt_gcode.empty()) - skirt_generated_for_current_print_z = true; + std::string skirt_gcode = generate_skirt(print, group.skirt, Point(0, 0), layer.object()->config().skirt_start_angle, + layer_tools, layer, extruder_id, m_skirt_group_done[group_idx]); gcode += std::move(skirt_gcode); } } @@ -5245,108 +5319,20 @@ LayerResult GCode::process_layer( std::vector &instances_to_print = filament_to_print_instances[extruder_id]; - // BBS - if (print.config().skirt_type == stPerObject && - print.config().print_sequence == PrintSequence::ByObject && - !layer.object()->object_skirt().empty() && - ((layer.id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled)) - ) - { - for (InstanceToPrint& instance_to_print : instances_to_print) { - - if (instance_to_print.print_object.object_skirt().empty()) - continue; - - if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && - print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities.size() > 0) - continue; - - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && - print.m_brimMap.at(instance_to_print.print_object.id()).entities.size() > 0) - continue; - if (first_layer) - m_skirt_done.clear(); - - if (layer.id() == 1 && m_skirt_done.size() > 1) - m_skirt_done.erase(m_skirt_done.begin()+1,m_skirt_done.end()); - - const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - gcode += generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, instance_to_print.print_object.config().skirt_start_angle, layer_tools, layer, extruder_id); - } - } - - // Orca: Print unified global brim after the skirt and before any object. - // Only do this if `combine_brims` is enabled and we are printing by layer. - if (first_layer && sequence_by_layer && m_config.combine_brims && !print.m_brimMap.empty()) { - const ObjectID unified_object_id = [&]() -> ObjectID { - ObjectID id; - for (const auto& [obj_id, brim] : print.m_brimMap) { - const bool has_printable_entities = std::any_of(brim.entities.begin(), brim.entities.end(), - [](const ExtrusionEntity* ee) { return ee != nullptr; }); - if (!has_printable_entities) - continue; - - if (id.valid()) - return ObjectID(); - - id = obj_id; - } - return id; - }(); - - if (unified_object_id.valid() && this->m_objsWithBrim.find(unified_object_id) != this->m_objsWithBrim.end()) { - const ExtrusionEntityCollection& unified_brim = print.m_brimMap.at(unified_object_id); - this->set_origin(0., 0.); - for (const ExtrusionEntity* ee : unified_brim.entities) - if (ee != nullptr) - gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); - - // Mark brim as printed for this object to avoid per-object brim emission later. - this->m_objsWithBrim.erase(unified_object_id); - } - } - - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): + // We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature): std::vector by_region_per_copy_cache; for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; - bool skirt_generated_for_current_print_z = false; - for (InstanceToPrint &instance_to_print : instances_to_print) { - if (print.config().skirt_type == stPerObject && - !instance_to_print.print_object.object_skirt().empty() && - print.config().print_sequence == PrintSequence::ByLayer) - { - const LayerToPrint& layer_to_print = layers[instance_to_print.layer_id]; - const Layer* skirt_layer = layer_to_print.object_layer; - if (skirt_layer == nullptr && layer_to_print.support_layer != nullptr && - layer_to_print.support_layer->id() < layer_to_print.support_layer->object()->slicing_parameters().raft_layers()) { - skirt_layer = layer_to_print.support_layer; - } - - if (skirt_layer != nullptr && - (skirt_layer->id() < print.config().skirt_height || print.config().draft_shield == DraftShield::dsEnabled)) { - const bool skirt_first_layer = (skirt_layer->id() == 0 && std::abs(skirt_layer->bottom_z()) < EPSILON); - if (skirt_first_layer) - m_skirt_done.clear(); - - if (skirt_generated_for_current_print_z && !m_skirt_done.empty()) - m_skirt_done.pop_back(); - - const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - std::string skirt_gcode = generate_skirt(print, instance_to_print.print_object.object_skirt(), offset, - instance_to_print.print_object.config().skirt_start_angle, layer_tools, - *skirt_layer, extruder_id); - if (!skirt_gcode.empty()) - skirt_generated_for_current_print_z = true; - gcode += std::move(skirt_gcode); - } - } - const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) { + gcode += generate_object_skirt_group(print, instance_to_print.print_object, layer_tools, layer, extruder_id); + gcode += generate_object_brim(print, instance_to_print.print_object, first_layer); + } + // To control print speed of the 1st object layer printed over raft interface. bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); @@ -5445,20 +5431,6 @@ LayerResult GCode::process_layer( // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //BBS: add brim by obj by extruder - if (first_layer) { - if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { - this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); - } - m_avoid_crossing_perimeters.use_external_mp(false); - // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once(); - this->m_objsWithBrim.erase(instance_to_print.print_object.id()); - } - } // When starting a new object, use the external motion planner for the first travel move. const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 03e76a1effc..176af4c5101 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -343,7 +343,16 @@ private: const float skirt_start_angle, const LayerTools &layer_tools, const Layer& layer, + unsigned int extruder_id, + std::vector &skirt_done); + std::string generate_object_skirt_group(const Print &print, + const PrintObject &object, + const LayerTools &layer_tools, + const Layer& layer, unsigned int extruder_id); + std::string generate_object_brim(const Print &print, + const PrintObject &object, + bool first_layer); LayerResult process_layer( const Print &print, @@ -609,8 +618,8 @@ private: std::unique_ptr m_small_area_infill_flow_compensator; - // Heights (print_z) at which the skirt has already been extruded. - std::vector m_skirt_done; + // Heights (print_z) at which each grouped skirt has already been extruded. + std::vector> m_skirt_group_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. bool m_brim_done; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index bbe164fdb94..803d900f183 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2443,21 +2443,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) start_time = (long long)Slic3r::Utils::get_current_time_utc(); m_skirt.clear(); - m_skirt_groups.clear(); + m_skirt_brim_groups.clear(); + m_has_shared_per_object_skirt = false; m_skirt_convex_hull.clear(); m_objectBrimAreas.clear(); m_supportBrimAreas.clear(); m_first_layer_convex_hull.points.clear(); for (PrintObject *object : m_objects) object->m_skirt.clear(); - const bool draft_shield = config().draft_shield != dsDisabled; - - if (this->has_skirt() && draft_shield) { - // In case that draft shield is active, generate skirt first so brim - // can be trimmed to make room for it. - _make_skirt(); - } - //BBS: get the objects' indices when GCodes are generated ToolOrdering tool_ordering; unsigned int initial_extruder_id = (unsigned int)-1; @@ -2548,11 +2541,16 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) } - if (has_skirt() && ! draft_shield) { - // In case that draft shield is NOT active, generate skirt now. - // It will be placed around the brim, so brim has to be ready. + if (has_skirt() || has_infinite_skirt() || has_brim()) { + // Generate skirt/brim groups after brim so per-object and draft-shield footprints + // include brims when grouping and offsetting skirt loops. assert(m_skirt.empty()); _make_skirt(); + if (m_config.print_sequence == PrintSequence::ByObject && + m_config.skirt_type == stPerObject && + this->has_shared_per_object_skirt()) { + throw Slic3r::SlicingError(L("Per-object skirts cannot fit between the objects in By object print sequence.\n\nMove the objects farther apart, reduce brim/skirt size, switch Skirt type to Combined, or switch Print sequence to By layer.")); + } } this->finalize_first_layer_convex_hull(); @@ -2644,6 +2642,8 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor void Print::_make_skirt() { + const bool generate_skirt = this->has_skirt() || this->has_infinite_skirt(); + // First off we need to decide how tall the skirt must be. // The skirt_height option from config is expressed in layers, but our // object might have different layer heights, so we need to find the print_z @@ -2655,11 +2655,13 @@ void Print::_make_skirt() // prepended to the first 'n' layers (with 'n' = skirt_height). // $skirt_height_z in this case is the highest possible skirt height for safety. coordf_t skirt_height_z = 0.; - for (const PrintObject *object : m_objects) { - size_t skirt_layers = this->has_infinite_skirt() ? - object->layer_count() : - std::min(size_t(m_config.skirt_height.value), object->layer_count()); - skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z); + if (generate_skirt) { + for (const PrintObject *object : m_objects) { + size_t skirt_layers = this->has_infinite_skirt() ? + object->layer_count() : + std::min(size_t(m_config.skirt_height.value), object->layer_count()); + skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z); + } } struct ObjectSkirtHull { @@ -2673,7 +2675,7 @@ void Print::_make_skirt() Points object_points; // Get object layers up to skirt_height_z. for (const Layer *layer : object->m_layers) { - if (layer->print_z > skirt_height_z) + if (generate_skirt && layer->print_z > skirt_height_z) break; for (const ExPolygon &expoly : layer->lslices) // Collect the outer contour points only, ignore holes for the calculation of the convex hull. @@ -2681,7 +2683,7 @@ void Print::_make_skirt() } // Get support layers up to skirt_height_z. for (const SupportLayer *layer : object->support_layers()) { - if (layer->print_z > skirt_height_z) + if (generate_skirt && layer->print_z > skirt_height_z) break; layer->support_fills.collect_points(object_points); } @@ -2771,17 +2773,15 @@ void Print::_make_skirt() }; m_skirt.clear(); - m_skirt_groups.clear(); + m_skirt_brim_groups.clear(); + m_has_shared_per_object_skirt = false; + for (const ObjectSkirtHull& object_hull : object_convex_hulls) + object_hull.object->m_skirt.clear(); - if (m_config.skirt_type == stPerObject && m_config.print_sequence == PrintSequence::ByObject) { - for (const ObjectSkirtHull& object_hull : object_convex_hulls) { - object_hull.object->m_skirt.clear(); - append_skirt_loops_for_hull(object_hull.hull, object_hull.object->m_skirt, false); - object_hull.object->m_skirt.reverse(); - } - } else if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) { + if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) { struct SkirtGroupItem { Points occupied_points; + ObjectID object_id; bool emits_skirt; }; @@ -2811,13 +2811,13 @@ void Print::_make_skirt() continue; // Orca: include the object's brim/support-brim footprint before checking skirt collisions. - group_items.push_back({ std::move(occupied_points), true }); + group_items.push_back({ std::move(occupied_points), object->id(), true }); } // Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself. Points wipe_tower_points = this->first_layer_wipe_tower_corners(); if (wipe_tower_points.size() >= 3) - group_items.push_back({ std::move(wipe_tower_points), false }); + group_items.push_back({ std::move(wipe_tower_points), ObjectID(), false }); std::vector parent(group_items.size()); std::iota(parent.begin(), parent.end(), 0); @@ -2843,14 +2843,17 @@ void Print::_make_skirt() auto build_grouped_points = [&]() { struct GroupData { - Points points; - bool emits_skirt = false; + Points points; + std::vector object_ids; + bool emits_skirt = false; }; std::map grouped; for (size_t i = 0; i < group_items.size(); ++i) { GroupData& group = grouped[find_parent(i)]; append(group.points, group_items[i].occupied_points); + if (group_items[i].object_id.valid()) + group.object_ids.push_back(group_items[i].object_id); group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt; } return grouped; @@ -2889,19 +2892,99 @@ void Print::_make_skirt() } } + auto make_group_brims = [this](const std::vector& group_object_ids) { + std::vector brims; + std::vector brim_object_ids; + for (ObjectID object_id : group_object_ids) { + const auto brim_it = m_brimMap.find(object_id); + if (brim_it != m_brimMap.end() && !brim_it->second.empty()) + brim_object_ids.push_back(object_id); + } + + const bool global_combined_brim = m_config.combine_brims && m_config.skirt_type != stPerObject && m_brimMap.size() == 1; + auto brim_owner_ids = [&group_object_ids, global_combined_brim](const std::vector& object_ids) { + return global_combined_brim ? group_object_ids : object_ids; + }; + + const bool combine_group_brims = m_config.combine_brims && brim_object_ids.size() > 1; + if (!combine_group_brims) { + for (ObjectID object_id : brim_object_ids) + brims.push_back({ m_brimMap.at(object_id), brim_owner_ids({ object_id }) }); + return brims; + } + + std::vector brim_parent(brim_object_ids.size()); + std::iota(brim_parent.begin(), brim_parent.end(), 0); + auto find_brim_parent = [&brim_parent](size_t idx) { + while (brim_parent[idx] != idx) { + brim_parent[idx] = brim_parent[brim_parent[idx]]; + idx = brim_parent[idx]; + } + return idx; + }; + auto unite_brims = [&brim_parent, &find_brim_parent](size_t a, size_t b) { + a = find_brim_parent(a); + b = find_brim_parent(b); + if (a != b) + brim_parent[b] = a; + }; + + const coord_t brim_contact_distance = coord_t(brim_flow().scaled_spacing() * 2.); + for (size_t i = 0; i < brim_object_ids.size(); ++i) { + const auto area_i = m_objectBrimAreas.find(brim_object_ids[i]); + if (area_i == m_objectBrimAreas.end()) + continue; + for (size_t j = i + 1; j < brim_object_ids.size(); ++j) { + const auto area_j = m_objectBrimAreas.find(brim_object_ids[j]); + if (area_j != m_objectBrimAreas.end() && + !intersection_ex(offset_ex(area_i->second, brim_contact_distance, jtRound, SCALED_RESOLUTION), area_j->second).empty()) + unite_brims(i, j); + } + } + + std::map> combined_brim_ids; + for (size_t i = 0; i < brim_object_ids.size(); ++i) + combined_brim_ids[find_brim_parent(i)].push_back(brim_object_ids[i]); + + for (const auto& [_, object_ids] : combined_brim_ids) { + if (object_ids.size() == 1) { + brims.push_back({ m_brimMap.at(object_ids.front()), brim_owner_ids(object_ids) }); + continue; + } + + ExPolygons combined_area; + for (ObjectID object_id : object_ids) + expolygons_append(combined_area, m_objectBrimAreas.at(object_id)); + combined_area = union_ex(combined_area); + const float scaled_resolution = float(scaled(m_config.resolution.value)); + const float brim_cleanup_delta = std::max(scaled_resolution, float(SCALED_EPSILON)); + combined_area = offset2_ex(combined_area, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution); + + Polygons islands_area; + brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), object_ids }); + } + + return brims; + }; + auto grouped_points = build_grouped_points(); for (auto& [_, group] : grouped_points) { if (!group.emits_skirt || group.points.size() < 3) continue; + if (generate_skirt && m_config.skirt_type == stPerObject && group.object_ids.size() > 1) + m_has_shared_per_object_skirt = true; // Orca: after merging, use the occupied outline directly; do not add skirt distance twice. ExtrusionEntityCollection group_skirt; - append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true); + if (generate_skirt) + append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true); + std::vector group_brims = make_group_brims(group.object_ids); if (!group_skirt.empty()) { group_skirt.reverse(); // Orca: keep m_skirt as a flattened compatibility mirror for preview/extents. m_skirt.append(group_skirt.entities); - m_skirt_groups.push_back(std::move(group_skirt)); } + if (!group_skirt.empty() || !group_brims.empty()) + m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.object_ids), std::move(group_brims) }); } } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b113086a8b6..c69c5b6570a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -970,8 +970,21 @@ public: PrintObjectPtrs& objects_mutable() { return m_objects; } PrintRegionPtrs& print_regions_mutable() { return m_print_regions; } std::vector layers_sorted_for_object(float start, float end, std::vector &layers_of_objects, std::vector &boundingBox_for_objects, VecOfPoints& objects_instances_shift); + struct SkirtBrimGroup { + struct Brim { + ExtrusionEntityCollection brim; + std::vector object_ids; + }; + + ExtrusionEntityCollection skirt; + std::vector object_ids; + // Brims stay separate unless Combine brims merges colliding brims inside this group. + std::vector brims; + }; + const ExtrusionEntityCollection& skirt() const { return m_skirt; } - const std::vector& skirt_groups() const { return m_skirt_groups; } + const std::vector& skirt_brim_groups() const { return m_skirt_brim_groups; } + bool has_shared_per_object_skirt() const { return m_has_shared_per_object_skirt; } // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. // It does NOT encompass user extrusions generated by custom G-code, @@ -1145,7 +1158,8 @@ private: // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; - std::vector m_skirt_groups; + std::vector m_skirt_brim_groups; + bool m_has_shared_per_object_skirt { false }; // BBS: collecting extrusion paths to build brim by objs std::map m_brimMap; std::map m_supportBrimMap;