From 1bd3404c03be5d4dcdbf8f0c245f3fea632b38ef Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Thu, 6 Aug 2026 15:29:40 -0500 Subject: [PATCH] Fix belt brim emission: dropped first-contact bands, tool selection, inner-only predicate (A,B,C,D) - Emit coincident belt_brim_by_layer bands even when the leading object layer has no InstanceVisit (zero-extrusion lead-in / no coinciding support), so the brim at first belt contact is no longer dropped. - Register each coincident band's brim filament in ToolOrdering and emit each band exactly once, in its brim-filament pass; emit ordinary-layer aprons in the brim pass before object extrusion (correct tool, brim-first) instead of with whatever tool was active. - has_belt_brim(): inner-only brims need brim_width>0 (leading/extra produce no inner geometry), fixing spurious prime-tower/spiral rejection; mirror in wants_brim. Single-extruder/single-object output is unchanged except previously-dropped bands now print. --- src/libslic3r/GCode.cpp | 116 ++++++++++++++++++++++++--- src/libslic3r/GCode/ToolOrdering.cpp | 38 +++++++-- src/libslic3r/Print.cpp | 10 ++- src/libslic3r/Print.hpp | 6 ++ src/libslic3r/PrintObject.cpp | 29 ++++++- 5 files changed, 175 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 46d8593182..976c99fd9a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5891,16 +5891,12 @@ LayerResult GCode::process_layer( //BBS: set layer time fan speed after layer change gcode gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; - // Belt printers: an apron band prints below its own object's first layer, but with - // several objects on the belt another one can already be printing at this print_z. - // The layer then has an object layer and takes this ordinary path instead of the - // brim-only branch, so the band has to be emitted here or it would be dropped. - // Before any object extrusion at this Z, as the brim must go down first. - if (print.has_belt_brim()) { - const Vec2d saved_origin = m_origin; - gcode += this->emit_belt_brim_bands(print, layers, single_object_instance_idx); - this->set_origin(saved_origin); - } + // Belt printers: ordinary-layer apron bands (a band whose print_z coincides with an + // object/support layer, so it takes this path rather than the brim-only branch) are + // NOT emitted here anymore. They used to be laid down with whatever tool happened to + // be active; instead they are now emitted inside the extruder loop below, in their + // own brim-filament pass and before that pass's object extrusion, so the brim goes + // down first with the correct tool. See the emit_belt_brim_for_extruder call. //Calibration Layer-specific GCode // ORCA-Belt: on belt printers the calibration object is counter-rotated to @@ -6584,6 +6580,49 @@ LayerResult GCode::process_layer( // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. m_skirt_group_done.resize(print.skirt_brim_groups().size()); + + // Belt brim bookkeeping. A coincident belt_brim_by_layer band must be emitted + // exactly once, in its object's brim-filament pass; this records which have gone + // down so the in-visit emit and the end-of-layer orphan sweep never double it. + // Key = (LayerToPrint index, instance_id). + std::set> belt_brim_emitted; + + // Emit every ORDINARY-layer apron band (belt_brim_prologue band coinciding with an + // object/support layer) whose brim filament is this pass's extruder. Mirrors + // emit_belt_brim_bands() per band, but filtered to one brim filament so each band + // prints in the correct tool's pass (Finding B). extruder_id is 0-based (the + // reindexed tool domain); belt_brim_filament() is 1-based, so subtract one. + auto emit_belt_brim_for_extruder = [this, &print, &layers, single_object_instance_idx](unsigned int extruder_id) -> std::string { + std::string gc; + for (const LayerToPrint <p : layers) { + const BeltBrimBand *band = ltp.belt_brim_band; + if (band == nullptr || band->fills.empty() || ltp.original_object == nullptr) + continue; + const PrintObject &object = *ltp.original_object; + if (! object.has_belt_brim() || (unsigned int)(object.belt_brim_filament() - 1) != extruder_id) + continue; + // Speeds, flow and retraction all read m_config. + m_config.apply(print.default_region_config()); + m_config.apply(object.config(), true); + const size_t i_begin = single_object_instance_idx == size_t(-1) ? 0 : single_object_instance_idx; + const size_t i_end = single_object_instance_idx == size_t(-1) ? object.instances().size() + : single_object_instance_idx + 1; + for (size_t i = i_begin; i < i_end && i < object.instances().size(); ++ i) { + // Band geometry is object-local, like the object's own extrusions. + const Point &offset = object.instances()[i].shift; + this->set_origin(unscale(offset)); + this->on_set_origin(&object, offset); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity *ee : band->fills.entities) + if (ee != nullptr) + gc += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); + m_avoid_crossing_perimeters.use_external_mp(false); + m_avoid_crossing_perimeters.disable_once(); + } + } + return gc; + }; + for (unsigned int extruder_id : layer_tools.extruders) { if (print.config().skirt_type == stCombined && !print.skirt_brim_groups().empty()) { @@ -6681,6 +6720,16 @@ LayerResult GCode::process_layer( if (layer_tools.has_wipe_tower && m_wipe_tower) m_last_processor_extrusion_role = erWipeTower; + // Belt printers: now that this pass's tool is selected, lay down any ordinary-layer + // apron band whose brim filament is this extruder, before the object extrusion at + // this Z (brim goes down first, with the correct tool). Restore the origin so the + // object-setup code below is unaffected. + if (print.has_belt_brim()) { + const Vec2d saved_origin = m_origin; + gcode += emit_belt_brim_for_extruder(extruder_id); + this->set_origin(saved_origin); + } + auto &filament_plan = filament_to_print_instances[extruder_id]; std::vector &instances_to_print = filament_plan.first; const std::vector &instance_visits = filament_plan.second; @@ -6697,8 +6746,20 @@ LayerResult GCode::process_layer( const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; if (visit.first_visit && print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) { gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id); - gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer, - layer_to_print.object_layer); + const PrintObject &vobj = instance_to_print.print_object; + if (vobj.has_belt_brim()) { + // Coincident belt brim: emit once, only in this object's brim-filament + // pass (extruder_id and belt_brim_filament()-1 are both 0-based here), + // and dedup on the LayerToPrint index (not Layer::id()) so the orphan + // sweep below never re-emits it. + if (extruder_id == (unsigned int)(vobj.belt_brim_filament() - 1) && + belt_brim_emitted.insert({ instance_to_print.layer_id, instance_to_print.instance_id }).second) + gcode += generate_object_brim(print, vobj, instance_to_print.instance_id, first_layer, + layer_to_print.object_layer); + } else { + gcode += generate_object_brim(print, vobj, instance_to_print.instance_id, first_layer, + layer_to_print.object_layer); + } } // To control print speed of the 1st object layer printed over raft interface. @@ -6886,6 +6947,37 @@ LayerResult GCode::process_layer( } } } + + // Belt brim orphan sweep (Finding C). A coincident belt_brim_by_layer band lives on + // an object layer, but that layer can yield no InstanceVisit above - a zero-extrusion + // lead-in slice with no coinciding support - so the in-visit emit never fired and the + // band would be dropped. Emit any such band exactly once here, keyed the same way as + // the in-visit emit so already-printed bands are skipped. These orphan layers carry + // no object material, so ending on the brim's position is harmless; we still save and + // restore m_origin, and only toolchange when the brim filament differs from the active + // one - a no-op on single-extruder prints, keeping their output unchanged. + if (print.has_belt_brim()) { + const Vec2d saved_origin = m_origin; + for (const LayerToPrint <p : layers) { + const PrintObject *obj = ltp.original_object; + if (obj == nullptr || ! obj->has_belt_brim() || ltp.object_layer == nullptr) + continue; + const size_t ltp_idx = size_t(<p - layers.data()); + const unsigned int brim0 = (unsigned int)(obj->belt_brim_filament() - 1); + const size_t i_begin = single_object_instance_idx == size_t(-1) ? 0 : single_object_instance_idx; + const size_t i_end = single_object_instance_idx == size_t(-1) ? obj->instances().size() + : single_object_instance_idx + 1; + for (size_t instance_id = i_begin; instance_id < i_end && instance_id < obj->instances().size(); ++ instance_id) { + if (! belt_brim_emitted.insert({ ltp_idx, instance_id }).second) + continue; + if (m_writer->filament() == nullptr || m_writer->filament()->id() != brim0) + gcode += this->set_extruder(brim0, print_z); + gcode += generate_object_brim(print, *obj, instance_id, first_layer, ltp.object_layer); + } + } + this->set_origin(saved_origin); + } + if (first_layer) { for (auto iter = by_extruder.begin(); iter != by_extruder.end(); ++iter) { if (!iter->second.empty()) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index e0520a3694..f96748848f 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -876,14 +876,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // push. Deliberately not layer_tools.has_object, which drives skirt marking // and wiping overrides. if (! object.belt_brim_prologue().empty()) { - unsigned int brim_filament = 0; - for (size_t i = 0; i < object.num_printing_regions(); ++ i) { - const unsigned int f = object.printing_region(i).config().outer_wall_filament_id.value; - if (f > 0 && (brim_filament == 0 || f < brim_filament)) - brim_filament = f; - } - if (brim_filament == 0) - brim_filament = 1; + // 1-based, same domain the object/support pushes above use; reindexed to 0-based + // with the rest of the list later. + const unsigned int brim_filament = object.belt_brim_filament(); for (const BeltBrimBand &band : object.belt_brim_prologue()) { if (band.fills.empty()) continue; @@ -893,6 +888,25 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } } + // Coincident brim bands (belt_brim_by_layer) print ON an object layer rather than + // below it, but that layer can produce no InstanceVisit in process_layer - a + // zero-extrusion lead-in slice with no coinciding support - and the band would then + // be silently dropped. Register the brim filament on every layer that carries a + // coincident band, in the same 1-based domain as the prologue push above, so a brim + // pass always exists there. + if (object.has_belt_brim()) { + const unsigned int brim_filament = object.belt_brim_filament(); + const auto &by_layer = object.belt_brim_by_layer(); + const size_t n = std::min(by_layer.size(), object.layers().size()); + for (size_t i = 0; i < n; ++ i) { + if (by_layer[i].empty()) + continue; + LayerTools &layer_tools = this->tools_for_layer(object.layers()[i]->print_z); + layer_tools.extruders.push_back(brim_filament); + layer_tools.has_belt_brim = true; + } + } + for (auto& layer : m_layer_tools) { // Sort and remove duplicates sort_remove_duplicates(layer.extruders); @@ -940,6 +954,14 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // below the object's first layer, and treating those layers as raft would put a // wipe tower at negative Z. Belt brim and the prime tower are mutually // exclusive (rejected in Print::validate()), so simply drop the clause there. + // + // Gate on config.belt_printer, NOT on has_belt_brim: every layer below the + // object bottom on a belt printer is legitimately a sub-object stream - brim + // apron, belt support printed below Z0, or the object's own lead-in - and none of + // them is ever raft, because Print::validate() rejects raft_layers>0 on a belt + // printer outright. Narrowing this to has_belt_brim would reclassify + // belt-support-below-floor layers as raft on brim-less belt prints and reintroduce + // the negative-Z wipe tower, so the broad belt_printer gate is correct. const bool belt_no_raft_gap = config.belt_printer.value; for (LayerTools < : m_layer_tools) lt.has_wipe_tower |= (lt.has_object && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0)) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2df4cde79b..017fa19736 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1374,9 +1374,15 @@ StringObjectException Print::validate(std::vector *warnin for (const PrintObject *object : m_objects) { const PrintObjectConfig &ocfg = object->config(); + // Mirror PrintObject::has_belt_brim(): an inner-only brim needs a positive + // brim_width (leading/extra widen only the outer ring), so keep this + // predicate in step or the belt-brim warnings below would fire for a brim + // that has_belt_brim() rejects. const bool wants_brim = ocfg.brim_type != btNoBrim - && (ocfg.brim_width.value > 0. || ocfg.leading_brim_length.value > 0. - || ocfg.extra_brim_width.value > 0.); + && (ocfg.brim_type == btInnerOnly + ? ocfg.brim_width.value > 0. + : (ocfg.brim_width.value > 0. || ocfg.leading_brim_length.value > 0. + || ocfg.extra_brim_width.value > 0.)); if (! wants_brim) continue; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 4b1f4891f2..ff5a505360 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -392,6 +392,12 @@ public: // trimming and the spiral vase probe, and widening it would perturb belt // support output. bool has_belt_brim() const; + // Brim filament for this object's belt brim, returned in the 1-based domain of + // PrintRegion::outer_wall_filament_id and the raw values pushed into + // LayerTools::extruders in ToolOrdering::collect_extruders (ToolOrdering reindexes + // the whole list to 0-based afterwards). Lowest positive outer_wall_filament_id + // over the printing regions, 1 if none is explicitly set. + unsigned int belt_brim_filament() const; // False when this object's instances sit at different points ALONG the belt, which // would need a separate set of bands each. Public so validate() can explain it. bool belt_brim_instances_compatible() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c5d5409d73..68cedfc534 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1171,12 +1171,37 @@ bool PrintObject::has_belt_brim() const return false; if (m_config.brim_type == btNoBrim) return false; - if (m_config.brim_width.value <= 0. && m_config.leading_brim_length.value <= 0. - && m_config.extra_brim_width.value <= 0.) + // An inner-only brim has no leading/extra geometry: leading_brim_length and + // extra_brim_width both widen the OUTER ring, which btInnerOnly never emits, so it + // produces nothing unless brim_width itself is positive. Every other brim type is + // satisfied by any one of the three widths. Requiring the width here (instead of + // "any width") stops has_belt_brim() - and therefore Print::validate() - from + // rejecting the prime tower / spiral vase for a brim that would never be drawn. + if (m_config.brim_type == btInnerOnly) { + if (m_config.brim_width.value <= 0.) + return false; + } else if (m_config.brim_width.value <= 0. && m_config.leading_brim_length.value <= 0. + && m_config.extra_brim_width.value <= 0.) { return false; + } return ! this->has_raft(); } +unsigned int PrintObject::belt_brim_filament() const +{ + // 1-based, matching PrintRegion::outer_wall_filament_id and the raw values pushed + // into LayerTools::extruders in ToolOrdering::collect_extruders (the whole list is + // reindexed to 0-based later). Lowest positive outer-wall filament over the + // printing regions; 1 when none is explicitly set. + unsigned int brim_filament = 0; + for (size_t i = 0; i < this->num_printing_regions(); ++ i) { + const unsigned int f = this->printing_region(i).config().outer_wall_filament_id.value; + if (f > 0 && (brim_filament == 0 || f < brim_filament)) + brim_filament = f; + } + return brim_filament == 0 ? 1u : brim_filament; +} + bool PrintObject::belt_brim_instances_compatible() const { // One set of bands is shared by every instance of this object, so they must all sit at