mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-25 19:07:47 +00:00
cleanup, early purge tower stop if no longer necessary
This commit is contained in:
316
src/libslic3r/BeltPurge.cpp
Normal file
316
src/libslic3r/BeltPurge.cpp
Normal file
@@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
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<double>::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<float> flush_matrix(cast<float>(
|
||||
get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size())));
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
for (unsigned int i = 0; i < number_of_extruders; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(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
|
||||
@@ -86,6 +86,7 @@ set(lisbslic3r_sources
|
||||
BeltGCode.hpp
|
||||
BeltGCodeWriter.cpp
|
||||
BeltGCodeWriter.hpp
|
||||
BeltPurge.cpp
|
||||
BeltSliceStrategy.cpp
|
||||
BeltSliceStrategy.hpp
|
||||
BeltTransform.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<double>::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<float> flush_matrix(cast<float>(
|
||||
get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size())));
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
for (unsigned int i = 0; i < number_of_extruders; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user