allow belt purge to skip unnecessary purge volume

This commit is contained in:
harrierpigeon
2026-06-18 09:35:30 -05:00
parent 3d270c2aa7
commit ee3e014f02
3 changed files with 46 additions and 39 deletions

View File

@@ -198,9 +198,10 @@ void Print::_plan_belt_purge()
// 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; }
PrintObject *prism_po = nullptr;
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) { prism_po = po; break; }
const PrintObject *diag_prism = prism_po;
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
@@ -227,37 +228,8 @@ void Print::_plan_belt_purge()
float volume_to_wipe = use_flush_matrix ?
wipe_volumes[current_extruder_id][extruder_id] * flush_multiplier :
(float) m_config.prime_volume;
// DIAGNOSTIC (first dozen toolchanges, warning level): how much
// extrudable volume does the prism actually have at this layer, and
// how much of the requested purge did it absorb? Distinguishes "prism
// layer found but tiny/empty" from "found with geometry but marking
// didn't consume it".
const int swap_idx_on_layer = [&]{ int n = 0; for (unsigned int e : layer_tools.extruders) { if (e == extruder_id) break; ++n; } return n; }();
double prism_vol = -1.;
{
if (const Layer *pl = diag_prism != nullptr ? diag_prism->get_layer_at_printz(layer_tools.print_z, EPSILON) : nullptr) {
prism_vol = 0.;
for (const LayerRegion *lr : pl->regions()) {
for (const ExtrusionEntity *ee : lr->fills.entities) prism_vol += ee->total_volume();
for (const ExtrusionEntity *ee : lr->perimeters.entities) prism_vol += ee->total_volume();
}
}
}
float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id,
volume_to_wipe);
// DIAGNOSTIC: log the toolchanges that FAIL to fully absorb (warning,
// capped) — these are the ones causing the leftover.
static thread_local int dbg_fail = 0;
if (leftover > 0.01f && dbg_fail < 20) {
++dbg_fail;
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge UNABSORBED print_z=" << layer_tools.print_z
<< " filament " << current_extruder_id << "->" << extruder_id
<< " swap#" << swap_idx_on_layer << " of " << layer_tools.extruders.size() << " extruders"
<< " requested=" << volume_to_wipe
<< " absorbed=" << volume_to_wipe - leftover
<< " leftover=" << leftover
<< " prism_layer_extrudable_vol=" << prism_vol;
}
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] purge toolchange print_z=" << layer_tools.print_z
<< " filament " << current_extruder_id << "->" << extruder_id
<< " requested=" << volume_to_wipe
@@ -266,6 +238,33 @@ void Print::_plan_belt_purge()
layer_leftover += leftover;
current_extruder_id = extruder_id;
}
// Plastic saving: drop the prism's infill that no toolchange on this
// layer claimed. At this point (after the real-purge marking, before the
// force-override pass below) the prism's OVERRIDDEN fills are exactly the
// purge; the rest is infill that would otherwise print as the prism's own
// filament for nothing — which is most of the prism on layers with little
// or no purge. We keep perimeters so the bar's wall shell stays
// continuous along the belt. (Must run before ensure_perimeters_infills_
// order, which force-overrides every remaining fill and would make them
// all look "claimed".)
if (prism_po != nullptr) {
if (Layer *pl = prism_po->get_layer_at_printz(layer_tools.print_z, EPSILON)) {
const auto &we = layer_tools.wiping_extrusions();
for (LayerRegion *lr : pl->regions()) {
auto &ents = lr->fills.entities;
size_t keep = 0;
for (size_t r = 0; r < ents.size(); ++r) {
if (we.is_entity_overridden(ents[r], prism_po, 0))
ents[keep++] = ents[r];
else
delete ents[r];
}
ents.resize(keep);
}
}
}
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
if (layer_leftover > 0.f) {
total_leftover += layer_leftover;

View File

@@ -45,6 +45,14 @@ public:
void ensure_perimeters_infills_order(const Print& print);
// Returns true if entity is not printed with its usual extruder for a given copy
// (i.e. it was claimed as a wiping/purge extrusion). Used by the belt purge
// tower to tell which prism fills carry purge vs. which are unclaimed waste.
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
auto it = entity_map.find(std::make_tuple(entity, object));
return it == entity_map.end() ? false : it->second[copy_id] != -1;
}
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
bool out = this->is_overriddable(ee, print_config, object, region);
@@ -80,12 +88,6 @@ private:
void set_support_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
void set_support_interface_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
// Returns true in case that entity is not printed with its usual extruder for a given copy:
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
auto it = entity_map.find(std::make_tuple(entity, object));
return it == entity_map.end() ? false : it->second[copy_id] != -1;
}
std::map<std::tuple<const ExtrusionEntity*, const PrintObject *>, ExtruderPerCopy> entity_map; // to keep track of who prints what
// BBS
std::map<const PrintObject*, int> support_map;

View File

@@ -280,7 +280,13 @@ bool ensure_belt_purge_tower(Model &model, PartPlateList &partplate_list, Object
// (each island width/N wide, separated by a small gap), so per-layer capacity
// per island ~= max_flush, matching the height sizing.
const int n_islands = std::max(1, (int) filaments.size() - 1);
const double gap = 2.0;
// Minimal gap between sub-bars: they must stay just-separated so the slicer
// keeps them as distinct islands (hence distinct infill collections, one per
// simultaneous swap). Zero gap would union them into one collection and
// reintroduce the multi-swap-per-layer absorption bug; a hair over ~2 line
// widths also keeps gap-fill from bridging them. 1 mm is about as close as
// they can butt up while staying individually purgeable.
const double gap = 1.0;
const double w_sub = std::max(1.0, (width - (n_islands - 1) * gap) / n_islands);
// --- (Re)create ---------------------------------------------------------