From 854dae8dd24ccd5e815b5aea1837b346bcb31215 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Sat, 13 Jun 2026 06:04:10 -0500 Subject: [PATCH] strategy incremental --- src/libslic3r/Print.cpp | 27 ++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 48 +++++++++++++++++++++++---------------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 390bba1ff2..b5d3701e41 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -4345,6 +4345,21 @@ void Print::_plan_belt_purge() const bool use_flush_matrix = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material; const float flush_multiplier = (float) m_config.flush_multiplier.get_at(0); + // Diagnostic: the prism only absorbs purge at toolchange layers whose + // print_z coincides with one of its own layers. Compare the prism's layer + // print_z range to the toolchange print_z range and count how many + // toolchange layers actually land on a prism layer. This distinguishes a + // range/grid-alignment failure (no coverage) from a capacity shortfall + // (covered but not enough cross-section). + const PrintObject *diag_prism = nullptr; + for (const PrintObject *po : m_objects) + if (po->config().belt_purge_tower_object.value && !po->layers().empty()) { diag_prism = po; break; } + if (diag_prism != nullptr) + BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge prism layer range print_z=[" + << diag_prism->layers().front()->print_z << ", " << diag_prism->layers().back()->print_z + << "] nlayers=" << diag_prism->layers().size(); + int tc_layers = 0, tc_layers_covered = 0; + float total_leftover = 0.f; float worst_layer_leftover = 0.f; double worst_layer_z = 0.; @@ -4352,9 +4367,16 @@ void Print::_plan_belt_purge() unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder(); for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { float layer_leftover = 0.f; + bool layer_has_tc = false; for (const unsigned int extruder_id : layer_tools.extruders) { if (extruder_id == current_extruder_id) continue; + if (!layer_has_tc) { + layer_has_tc = true; + ++tc_layers; + if (diag_prism != nullptr && diag_prism->get_layer_at_printz(layer_tools.print_z, EPSILON) != nullptr) + ++tc_layers_covered; + } float volume_to_wipe = use_flush_matrix ? wipe_volumes[current_extruder_id][extruder_id] * flush_multiplier : (float) m_config.prime_volume; @@ -4379,6 +4401,11 @@ void Print::_plan_belt_purge() this->throw_if_canceled(); } + BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge coverage: " << tc_layers_covered << "/" << tc_layers + << " toolchange layers land on a prism layer" + << (tc_layers > 0 && tc_layers_covered == 0 ? " (RANGE/GRID MISALIGNMENT — prism absorbs nothing)" : + tc_layers_covered < tc_layers ? " (partial coverage)" : " (full coverage)"); + if (total_leftover > 1.f) { this->active_step_add_warning( PrintStateBase::WarningLevel::CRITICAL, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cda0a1557e..6e3ccd6da3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9041,12 +9041,19 @@ bool Plater::priv::ensure_belt_purge_tower() } const double v_layer = double(filaments.size() - 1) * max_flush; - // One tilted slicing plane cuts a width x (height/sin) rectangle out of - // the prism interior; one layer slab absorbs that area x layer height. - const double eta = 0.85; // perimeters/infill packing safety factor - double height = v_layer * sin_t / (width * layer_h * eta); + // Height from the per-layer purge demand. A tilted slicing plane cuts a + // width x (height/sin) rectangle out of the bar, so one layer slab absorbs + // width * (height/sin) * layer_height of purge. Solve for the height that + // holds the worst-case per-layer purge, with: + // eta - infill/perimeter packing (not all of the cross-section is solid) + // safety - margin for the tilt ramp at the bar ends and layer-grid + // alignment slop, where a layer cuts less than the full section + // and a minimum so the tower is a real printable body rather than a sliver. + const double eta = 0.85; + const double safety = 1.6; const double printable_height = printer_config.has("printable_height") ? printer_config.opt_float("printable_height") : 250.; - height = std::clamp(height, 2. * layer_h, std::max(2. * layer_h, printable_height)); + double height = safety * v_layer * sin_t / (width * layer_h * eta); + height = std::clamp(height, 8.0, std::max(8.0, printable_height)); // --- Idempotence (input-keyed) ---------------------------------------- // Key on the generation inputs, NOT the prism's resulting bbox: plate @@ -9068,28 +9075,31 @@ bool Plater::priv::ensure_belt_purge_tower() return false; // already up to date — do not touch the model // --- Position ---------------------------------------------------------- - // Lay the bar along the belt, anchored at the parts' belt-axis minimum so - // it leads the print at the belt entry. A tilted slicing plane through a - // part of height z reaches belt position y + z*cot(theta), so extend the - // trailing end to cover the tallest part's upper toolchanges plus the - // prism's own tilt span. + // Default belt-printer placement (per user request): + // - belt-travel axis: anchored at the parts' belt-axis MINIMUM (the belt + // entry / parts' min Y for an X-rotation belt) so the tower leads in. + // - across-belt axis: parked right up against the bed's MAXIMUM edge + // (max X for an X-rotation belt), out of the way of the parts. + // The bar extends from the parts' belt minimum past the parts; a tilted + // slicing plane through a part of height z reaches belt position + // y + z*cot(theta), so the trailing end covers the tallest part's upper + // toolchanges plus the bar's own tilt span. const double margin = 5.; - const double belt_start = belt_min - margin; // anchored at parts' belt minimum + const double belt_start = belt_min; // parts' belt-axis minimum const double belt_end = belt_max + (z_max + height) * cot_t + margin; // trailing tilt allowance const double length = std::max(belt_end - belt_start, 10.); const double belt_center = 0.5 * (belt_start + belt_end); - // Lateral placement: just beside the parts (the -lateral side by default, - // flipping to the +lateral side if that would fall off the bed). + // Across-belt: flush against the bed's maximum edge (small inset), falling + // back to just past the parts if the bed shape is unavailable. const Vec3d plate_origin = plate->get_origin(); - const double gap = 5.; - double lat_center = lat_min - gap - 0.5 * width; + const double inset = 1.; + double lat_center = lat_max + 5. + 0.5 * width; if (const auto *bed_opt = printer_config.option("printable_area"); bed_opt != nullptr && !bed_opt->values.empty()) { - const BoundingBoxf bed_ext = get_extents(bed_opt->values); - const double bed_lat_min = plate_origin[belt_is_y ? 0 : 1] + (belt_is_y ? bed_ext.min.x() : bed_ext.min.y()); - if (lat_center - 0.5 * width < bed_lat_min) - lat_center = lat_max + gap + 0.5 * width; // not enough room on the -lateral side + const BoundingBoxf bed_ext = get_extents(bed_opt->values); + const double bed_lat_max = plate_origin[belt_is_y ? 0 : 1] + (belt_is_y ? bed_ext.max.x() : bed_ext.max.y()); + lat_center = bed_lat_max - inset - 0.5 * width; } // Orient the bar: long axis = belt travel, width = lateral, height = Z.