mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-29 04:49:31 +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
|
BeltGCode.hpp
|
||||||
BeltGCodeWriter.cpp
|
BeltGCodeWriter.cpp
|
||||||
BeltGCodeWriter.hpp
|
BeltGCodeWriter.hpp
|
||||||
|
BeltPurge.cpp
|
||||||
BeltSliceStrategy.cpp
|
BeltSliceStrategy.cpp
|
||||||
BeltSliceStrategy.hpp
|
BeltSliceStrategy.hpp
|
||||||
BeltTransform.cpp
|
BeltTransform.cpp
|
||||||
|
|||||||
@@ -4126,17 +4126,6 @@ bool Print::has_wipe_tower() const
|
|||||||
return false;
|
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
|
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;
|
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()
|
void Print::_make_wipe_tower()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1392,51 +1392,6 @@ void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel thro
|
|||||||
}); // end of parallel_for
|
}); // 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,
|
// 1) Decides Z positions of the layers,
|
||||||
// 2) Initializes layers and their regions
|
// 2) Initializes layers and their regions
|
||||||
|
|||||||
Reference in New Issue
Block a user