From 5ea6ccc56a7a5664783475603b4e9006e027afb2 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Sat, 13 Jun 2026 14:35:00 -0500 Subject: [PATCH] cleanup, early purge tower stop if no longer necessary --- src/libslic3r/BeltPurge.cpp | 316 +++++++++++++++++++++++++++++ src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Print.cpp | 231 --------------------- src/libslic3r/PrintObjectSlice.cpp | 45 ---- 4 files changed, 317 insertions(+), 276 deletions(-) create mode 100644 src/libslic3r/BeltPurge.cpp diff --git a/src/libslic3r/BeltPurge.cpp b/src/libslic3r/BeltPurge.cpp new file mode 100644 index 0000000000..a9c2ffceb8 --- /dev/null +++ b/src/libslic3r/BeltPurge.cpp @@ -0,0 +1,316 @@ +// ORCA-Belt: backend of the belt purge tower (the belt replacement for the +// classic wipe/prime tower). +// +// Kept in its own translation unit so the belt-purge logic stays out of the way +// of unrelated upstream changes to Print.cpp / PrintObjectSlice.cpp and carries +// no regression risk for normal printers: none of these methods do anything +// unless the print is a belt printer with the belt purge tower enabled. +// +// Print::has_belt_purge_tower() - is the belt purge tower active? +// Print::_align_belt_purge_layers() - snap the prism's layer grid onto the +// printed objects' grid +// Print::_plan_belt_purge() - route filament-change purging into the +// prism (flush-into-objects), no wipe tower +// PrintObject::belt_shift_layer_grid() - shift a sliced layer grid +// PrintObject::belt_truncate_layers_above() - cancel the prism past the last swap +// +// (Declarations live in Print.hpp alongside the rest of the Print interface.) + +#include "Print.hpp" +#include "PrintConfig.hpp" +#include "Exception.hpp" +#include "GCode/ToolOrdering.hpp" +#include "Layer.hpp" +#include "I18N.hpp" +#include "format.hpp" +#include "LocalesUtils.hpp" +#include "libslic3r.h" + +#include +#include +#include + +#include + +namespace Slic3r { + +// Belt purge prism: purging after filament changes is routed into a sliced +// prism object via the flush-into-objects machinery instead of a wipe tower. +bool Print::has_belt_purge_tower() const +{ + // Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower + // option (not the classic enable_prime_tower). + return m_config.belt_printer.value + && m_config.enable_belt_purge_tower.value + && !m_config.spiral_mode.value + && m_config.filament_diameter.values.size() > 1; +} + +// Belt mode: snap the purge prism's layer grid onto the printed objects' grid. +// After belt slicing every object's layer print_z carries a per-object global +// z offset (mesh-vertex-scan belt_z_shift + instance-Y-dependent terms), so +// objects at different belt-Y positions do not share a layer grid. Purge +// marking looks absorbers up with get_layer_at_printz(lt.print_z, EPSILON), +// so the prism only absorbs purge at toolchange print_z values that coincide +// with one of its own layers. Shifting the prism by at most half a layer +// (a sub-layer-height displacement along the belt) makes its grid residue +// match the reference object's; both grids step by the same layer height +// (enforced by validate()), so matching residues means exact layer matches. +void Print::_align_belt_purge_layers() +{ + PrintObject *prism = nullptr; + for (PrintObject *po : m_objects) + if (po->config().belt_purge_tower_object.value) { + prism = po; + break; + } + if (prism == nullptr || prism->layers().empty()) + return; + + const double h = prism->config().layer_height.value; + if (h <= EPSILON) + return; + + // Grid residue of an object's layer grid: identical for all of an object's + // layers above the first since they step by h. + auto grid_offset = [h](const PrintObject *po) { + const double z = po->layers().front()->print_z; + return z - std::floor(z / h) * h; // in [0, h) + }; + + // Reference grid: the tallest non-prism object (proxy for the object with + // the most toolchange layers). + const PrintObject *ref = nullptr; + double ref_top = -std::numeric_limits::max(); + for (const PrintObject *po : m_objects) { + if (po->config().belt_purge_tower_object.value || po->layers().empty()) + continue; + const double top = po->layers().back()->print_z; + if (top > ref_top) { + ref_top = top; + ref = po; + } + } + if (ref == nullptr) + return; + + const double ref_offset = grid_offset(ref); + bool grids_mismatch = false; + for (const PrintObject *po : m_objects) { + if (po == ref || po->config().belt_purge_tower_object.value || po->layers().empty()) + continue; + double d = std::abs(grid_offset(po) - ref_offset); + d = std::min(d, h - d); + if (d > 5. * EPSILON) { + grids_mismatch = true; + break; + } + } + + // Shift normalized to (-h/2, h/2]. + double delta = ref_offset - grid_offset(prism); + if (delta > 0.5 * h) + delta -= h; + else if (delta <= -0.5 * h) + delta += h; + + BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] purge prism grid snap" + << " ref=" << ref->model_object()->name + << " ref_offset=" << ref_offset + << " prism_offset=" << grid_offset(prism) + << " delta=" << delta + << " grids_mismatch=" << grids_mismatch; + + prism->belt_shift_layer_grid(delta); + + if (grids_mismatch) + this->active_step_add_warning( + PrintStateBase::WarningLevel::NON_CRITICAL, + _u8L("Objects on the plate are sliced on different layer grids; the belt purge tower can only follow " + "one of them. Filament changes on layers of the other objects may not be fully purged.")); +} + +// Belt mode replacement for _make_wipe_tower(): plan filament-change purging +// into the belt purge prism (and any other flush_into_* object) using the +// flush-into-objects machinery, without generating classic wipe tower G-code. +// The toolchange itself is emitted by GCode::set_extruder() via the +// change_filament_gcode macro; the overrides marked here make the new +// filament's first extrusions land in the purge prism. +void Print::_plan_belt_purge() +{ + m_wipe_tower_data.clear(); + + // Must run before ToolOrdering is built: LayerTools merge per-object layer + // print_z values, and the prism only absorbs purge where its (snapped) + // layers coincide with the toolchange layers. + this->_align_belt_purge_layers(); + + const unsigned int number_of_extruders = (unsigned int) m_config.filament_colour.values.size(); + + // No initial priming extrusions: there is no tower to prime on. + m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int) -1, false); + m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int) -1, false); + + if (m_wipe_tower_data.tool_ordering.empty() || m_wipe_tower_data.tool_ordering.last_extruder() == unsigned(-1)) + throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); + + if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) + // No toolchanges anywhere, nothing to purge. + return; + + this->throw_if_canceled(); + + // Flush volumes per filament pair, mirroring the generic wipe tower path: + // full flush matrix for single extruder multi material with purging enabled, + // plain prime volume otherwise. + std::vector flush_matrix(cast( + get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size()))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, + flush_matrix.begin() + (i + 1) * number_of_extruders)); + 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); + + // Cancel the purge prism early: pre-scan the tool ordering for the highest + // print_z that actually has a toolchange, then drop the prism's layers above + // it so the tower stops at the last color swap (saves filament/time). This + // MUST happen before the marking loop below: ensure_perimeters_infills_order + // force-overrides the prism's extrusions on every layer (it is a dedicated + // flush object), so truncating afterwards would leave dangling overrides + // pointing into deleted layers. + { + double last_tc_z = -1.; + unsigned int cur_ext = m_wipe_tower_data.tool_ordering.first_extruder(); + for (const auto < : m_wipe_tower_data.tool_ordering.layer_tools()) + for (const unsigned int e : lt.extruders) + if (e != cur_ext) { last_tc_z = lt.print_z; cur_ext = e; } + if (last_tc_z >= 0.) + for (PrintObject *po : m_objects) + if (po->config().belt_purge_tower_object.value) { + po->belt_truncate_layers_above(last_tc_z); + break; + } + } + + // 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.; + + 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; + float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, + volume_to_wipe); + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] purge toolchange print_z=" << layer_tools.print_z + << " filament " << current_extruder_id << "->" << extruder_id + << " requested=" << volume_to_wipe + << " absorbed=" << volume_to_wipe - leftover + << " leftover=" << leftover; + layer_leftover += leftover; + current_extruder_id = extruder_id; + } + layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + if (layer_leftover > 0.f) { + total_leftover += layer_leftover; + if (layer_leftover > worst_layer_leftover) { + worst_layer_leftover = layer_leftover; + worst_layer_z = layer_tools.print_z; + } + } + 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, + Slic3r::format(_u8L("The belt purge tower cannot absorb the full purge volume: %1% mm³ in total could not " + "be purged (worst layer: %2% mm³ at height %3%). The print may show color bleeding. " + "Increase the belt purge tower width, or reduce flushing volumes."), + int(std::ceil(total_leftover)), int(std::ceil(worst_layer_leftover)), + Slic3r::float_to_string_decimal_point(worst_layer_z, 2))); + BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge planning leftover total=" << total_leftover + << " worst_layer=" << worst_layer_leftover << " at print_z=" << worst_layer_z; + } +} + +// Belt mode: shift the sliced layer grid by delta. Mirrors the global_z_offset +// application in slice() — layer print_z and belt_floor_z_shift move together +// so belt floor clipping stays consistent with the shifted grid. Used by +// Print::_align_belt_purge_layers() to snap the purge prism onto the printed +// objects' layer grid; |delta| <= half a layer height, i.e. a sub-layer shift +// of the prism along the belt. +void PrintObject::belt_shift_layer_grid(double delta) +{ + if (std::abs(delta) < EPSILON) + return; + for (Layer *layer : m_layers) + layer->print_z += delta; + for (SupportLayer *layer : m_support_layers) + layer->print_z += delta; + m_slicing_params.belt_floor_z_shift += delta; + BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] belt_shift_layer_grid" + << " obj=" << this->model_object()->name + << " delta=" << delta + << " first_layer.print_z=" << (m_layers.empty() ? 0. : m_layers.front()->print_z); +} + +// Belt mode: drop layers strictly above z (used to cancel the purge prism early +// once there are no more toolchanges above z, so the tower stops at the last +// color swap instead of wasting filament up the rest of the belt). Each layer's +// cross-section is already sliced, so removing upper layers does not affect the +// last toolchange's coverage. Deletes the Layer objects and clears the new top +// layer's upper-layer link. Returns the number of layers removed. +size_t PrintObject::belt_truncate_layers_above(coordf_t z) +{ + size_t keep = m_layers.size(); + while (keep > 0 && m_layers[keep - 1]->print_z > z + EPSILON) + --keep; + if (keep >= m_layers.size()) + return 0; + const size_t removed = m_layers.size() - keep; + for (size_t i = keep; i < m_layers.size(); ++i) + delete m_layers[i]; + m_layers.resize(keep); + if (!m_layers.empty()) + m_layers.back()->upper_layer = nullptr; + BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] truncate purge prism above print_z=" << z + << " kept=" << keep << " removed=" << removed + << " new_top=" << (m_layers.empty() ? 0. : m_layers.back()->print_z); + return removed; +} + +} // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c8796265db..9ca113e958 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -86,6 +86,7 @@ set(lisbslic3r_sources BeltGCode.hpp BeltGCodeWriter.cpp BeltGCodeWriter.hpp + BeltPurge.cpp BeltSliceStrategy.cpp BeltSliceStrategy.hpp BeltTransform.cpp diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4004cd81de..922168ccb8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -4126,17 +4126,6 @@ bool Print::has_wipe_tower() const return false; } -// Belt purge prism: purging after filament changes is routed into a sliced -// prism object via the flush-into-objects machinery instead of a wipe tower. -bool Print::has_belt_purge_tower() const -{ - // Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower - // option (not the classic enable_prime_tower). - return m_config.belt_printer.value - && m_config.enable_belt_purge_tower.value - && !m_config.spiral_mode.value - && m_config.filament_diameter.values.size() > 1; -} const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const { @@ -4224,226 +4213,6 @@ bool Print::enable_timelapse_print() const return m_config.timelapse_type.value == TimelapseType::tlSmooth; } -// Belt mode: snap the purge prism's layer grid onto the printed objects' grid. -// After belt slicing every object's layer print_z carries a per-object global -// z offset (mesh-vertex-scan belt_z_shift + instance-Y-dependent terms), so -// objects at different belt-Y positions do not share a layer grid. Purge -// marking looks absorbers up with get_layer_at_printz(lt.print_z, EPSILON), -// so the prism only absorbs purge at toolchange print_z values that coincide -// with one of its own layers. Shifting the prism by at most half a layer -// (a sub-layer-height displacement along the belt) makes its grid residue -// match the reference object's; both grids step by the same layer height -// (enforced by validate()), so matching residues means exact layer matches. -void Print::_align_belt_purge_layers() -{ - PrintObject *prism = nullptr; - for (PrintObject *po : m_objects) - if (po->config().belt_purge_tower_object.value) { - prism = po; - break; - } - if (prism == nullptr || prism->layers().empty()) - return; - - const double h = prism->config().layer_height.value; - if (h <= EPSILON) - return; - - // Grid residue of an object's layer grid: identical for all of an object's - // layers above the first since they step by h. - auto grid_offset = [h](const PrintObject *po) { - const double z = po->layers().front()->print_z; - return z - std::floor(z / h) * h; // in [0, h) - }; - - // Reference grid: the tallest non-prism object (proxy for the object with - // the most toolchange layers). - const PrintObject *ref = nullptr; - double ref_top = -std::numeric_limits::max(); - for (const PrintObject *po : m_objects) { - if (po->config().belt_purge_tower_object.value || po->layers().empty()) - continue; - const double top = po->layers().back()->print_z; - if (top > ref_top) { - ref_top = top; - ref = po; - } - } - if (ref == nullptr) - return; - - const double ref_offset = grid_offset(ref); - bool grids_mismatch = false; - for (const PrintObject *po : m_objects) { - if (po == ref || po->config().belt_purge_tower_object.value || po->layers().empty()) - continue; - double d = std::abs(grid_offset(po) - ref_offset); - d = std::min(d, h - d); - if (d > 5. * EPSILON) { - grids_mismatch = true; - break; - } - } - - // Shift normalized to (-h/2, h/2]. - double delta = ref_offset - grid_offset(prism); - if (delta > 0.5 * h) - delta -= h; - else if (delta <= -0.5 * h) - delta += h; - - BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] purge prism grid snap" - << " ref=" << ref->model_object()->name - << " ref_offset=" << ref_offset - << " prism_offset=" << grid_offset(prism) - << " delta=" << delta - << " grids_mismatch=" << grids_mismatch; - - prism->belt_shift_layer_grid(delta); - - if (grids_mismatch) - this->active_step_add_warning( - PrintStateBase::WarningLevel::NON_CRITICAL, - _u8L("Objects on the plate are sliced on different layer grids; the belt purge tower can only follow " - "one of them. Filament changes on layers of the other objects may not be fully purged.")); -} - -// Belt mode replacement for _make_wipe_tower(): plan filament-change purging -// into the belt purge prism (and any other flush_into_* object) using the -// flush-into-objects machinery, without generating classic wipe tower G-code. -// The toolchange itself is emitted by GCode::set_extruder() via the -// change_filament_gcode macro; the overrides marked here make the new -// filament's first extrusions land in the purge prism. -void Print::_plan_belt_purge() -{ - m_wipe_tower_data.clear(); - - // Must run before ToolOrdering is built: LayerTools merge per-object layer - // print_z values, and the prism only absorbs purge where its (snapped) - // layers coincide with the toolchange layers. - this->_align_belt_purge_layers(); - - const unsigned int number_of_extruders = (unsigned int) m_config.filament_colour.values.size(); - - // No initial priming extrusions: there is no tower to prime on. - m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int) -1, false); - m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int) -1, false); - - if (m_wipe_tower_data.tool_ordering.empty() || m_wipe_tower_data.tool_ordering.last_extruder() == unsigned(-1)) - throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); - - if (!m_wipe_tower_data.tool_ordering.has_wipe_tower()) - // No toolchanges anywhere, nothing to purge. - return; - - this->throw_if_canceled(); - - // Flush volumes per filament pair, mirroring the generic wipe tower path: - // full flush matrix for single extruder multi material with purging enabled, - // plain prime volume otherwise. - std::vector flush_matrix(cast( - get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size()))); - std::vector> wipe_volumes; - for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, - flush_matrix.begin() + (i + 1) * number_of_extruders)); - 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); - - // Cancel the purge prism early: pre-scan the tool ordering for the highest - // print_z that actually has a toolchange, then drop the prism's layers above - // it so the tower stops at the last color swap (saves filament/time). This - // MUST happen before the marking loop below: ensure_perimeters_infills_order - // force-overrides the prism's extrusions on every layer (it is a dedicated - // flush object), so truncating afterwards would leave dangling overrides - // pointing into deleted layers. - { - double last_tc_z = -1.; - unsigned int cur_ext = m_wipe_tower_data.tool_ordering.first_extruder(); - for (const auto < : m_wipe_tower_data.tool_ordering.layer_tools()) - for (const unsigned int e : lt.extruders) - if (e != cur_ext) { last_tc_z = lt.print_z; cur_ext = e; } - if (last_tc_z >= 0.) - for (PrintObject *po : m_objects) - if (po->config().belt_purge_tower_object.value) { - po->belt_truncate_layers_above(last_tc_z); - break; - } - } - - // 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.; - - 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; - float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, - volume_to_wipe); - BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] purge toolchange print_z=" << layer_tools.print_z - << " filament " << current_extruder_id << "->" << extruder_id - << " requested=" << volume_to_wipe - << " absorbed=" << volume_to_wipe - leftover - << " leftover=" << leftover; - layer_leftover += leftover; - current_extruder_id = extruder_id; - } - layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); - if (layer_leftover > 0.f) { - total_leftover += layer_leftover; - if (layer_leftover > worst_layer_leftover) { - worst_layer_leftover = layer_leftover; - worst_layer_z = layer_tools.print_z; - } - } - 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, - Slic3r::format(_u8L("The belt purge tower cannot absorb the full purge volume: %1% mm³ in total could not " - "be purged (worst layer: %2% mm³ at height %3%). The print may show color bleeding. " - "Increase the belt purge tower width, or reduce flushing volumes."), - int(std::ceil(total_leftover)), int(std::ceil(worst_layer_leftover)), - Slic3r::float_to_string_decimal_point(worst_layer_z, 2))); - BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge planning leftover total=" << total_leftover - << " worst_layer=" << worst_layer_leftover << " at print_z=" << worst_layer_z; - } -} void Print::_make_wipe_tower() { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index f13b208a3a..b65c79d0eb 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1392,51 +1392,6 @@ void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel thro }); // end of parallel_for } -// Belt mode: shift the sliced layer grid by delta. Mirrors the global_z_offset -// application in slice() — layer print_z and belt_floor_z_shift move together -// so belt floor clipping stays consistent with the shifted grid. Used by -// Print::_align_belt_purge_layers() to snap the purge prism onto the printed -// objects' layer grid; |delta| <= half a layer height, i.e. a sub-layer shift -// of the prism along the belt. -void PrintObject::belt_shift_layer_grid(double delta) -{ - if (std::abs(delta) < EPSILON) - return; - for (Layer *layer : m_layers) - layer->print_z += delta; - for (SupportLayer *layer : m_support_layers) - layer->print_z += delta; - m_slicing_params.belt_floor_z_shift += delta; - BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] belt_shift_layer_grid" - << " obj=" << this->model_object()->name - << " delta=" << delta - << " first_layer.print_z=" << (m_layers.empty() ? 0. : m_layers.front()->print_z); -} - -// Belt mode: drop layers strictly above z (used to cancel the purge prism early -// once there are no more toolchanges above z, so the tower stops at the last -// color swap instead of wasting filament up the rest of the belt). Each layer's -// cross-section is already sliced, so removing upper layers does not affect the -// last toolchange's coverage. Deletes the Layer objects and clears the new top -// layer's upper-layer link. Returns the number of layers removed. -size_t PrintObject::belt_truncate_layers_above(coordf_t z) -{ - size_t keep = m_layers.size(); - while (keep > 0 && m_layers[keep - 1]->print_z > z + EPSILON) - --keep; - if (keep >= m_layers.size()) - return 0; - const size_t removed = m_layers.size() - keep; - for (size_t i = keep; i < m_layers.size(); ++i) - delete m_layers[i]; - m_layers.resize(keep); - if (!m_layers.empty()) - m_layers.back()->upper_layer = nullptr; - BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] truncate purge prism above print_z=" << z - << " kept=" << keep << " removed=" << removed - << " new_top=" << (m_layers.empty() ? 0. : m_layers.back()->print_z); - return removed; -} // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions