Skip to content

Ch. 7.2 ‐ Wiping into objects

Alex Petsiuk edited this page Aug 20, 2024 · 2 revisions

Both "Wipe into infill" and "Wipe into object" options are currently not supported in the layer batching upgrade.

image

  • Wipe into this object's infill [this->add("wipe_into_infill", coBool)] -- Purging after toolchange will be done inside this object's infills. This lowers the amount of waste but may result in longer print time due to additional travel moves.
  • Wipe into this object [this->add("wipe_into_objects", coBool)] -- Object will be used to purge the nozzle after a toolchange to save material that would otherwise end up in the wipe tower and decrease print time. The colors of the objects will be mixed as a result.

GCode/ToolOrdering.cpp

Access to configurations: object->config().wipe_into_objects

WipingExtrusions control
// GCode/ToolOrdering.cpp 641
// Decides whether this entity could be overridden
bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
{
    if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region)))
        return false;

    if (object.config().wipe_into_objects)
        return true;

    if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
        return false;

    return true;
}

Control functions in the GCode/ToolOrdering.cpp

  • mark_wiping_extrusions
  • ensure_perimeters_infills_order
  • get_extruder_overrides
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower.
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
// Called after all toolchanges on a layer were mark_infill_overridden. There might still be overridable entities,
// that were not actually overridden. If they are part of a dedicated object, printing them with the extruder
// they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through
// them again and make sure we override it.
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders 
// should be used for given copy of this entity. If this extrusion does not have any override, nullptr is returned. Otherwise 
// it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct 
// extruders were not known, so -1 was used as "print as usual"). The resulting vector therefore keeps track of which extrusions 
// are the ones that were overridden and which were not. If the extruder used is overridden, its number is saved as is (zero-based index). 
// Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)

LayerTool -> m_wiping_extrusions

WipingExtrusions& wiping_extrusions() {
        m_wiping_extrusions.set_layer_tools_ptr(this);
        return m_wiping_extrusions;
    }
// Group extrusions by an extruder, then by an object, an island and a region.
std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> by_extruder;
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();

ToolOrdering 		atc_tool_ordering;
WipeTowerData           atc_wipe_tower_data{ atc_tool_ordering };
WipeTower atc_wipe_tower(fff_print.m_config, wipe_volumes, fff_print.m_wipe_tower_data.tool_ordering.first_extruder());
// Lets go through the wipe tower layers and determine pairs of extruder changes for each
// to pass to wipe_tower (so that it can use it for planning the layout of the tower)
                    {
                        unsigned int current_extruder_id = fff_print.m_wipe_tower_data.tool_ordering.all_extruders().back();

                        for (auto& layer_tools : fff_print.m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
                            atc_temp_layer_idx += 1;
                            if (!layer_tools.has_wipe_tower) continue;
                            bool first_layer = &layer_tools == &fff_print.m_wipe_tower_data.tool_ordering.front();
                            wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false);
                            for (const auto extruder_id : layer_tools.extruders) {
                                atc_temp_extruder_idx += 1;
                                if ((first_layer && extruder_id == fff_print.m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
                                    atc_temp_toolchange_idx += 1;
                                    float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id];             // total volume to wipe after this toolchange
                                    // Not all of that can be used for infill purging:
                                    volume_to_wipe -= (float)fff_print.m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
                                    // try to assign some infills/objects for the wiping:
                                    volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(fff_print, current_extruder_id, extruder_id, volume_to_wipe);
                                    // add back the minimal amount toforce on the wipe tower:
                                    volume_to_wipe += (float)fff_print.m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
                                    // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
                                    wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, volume_to_wipe);
                                    current_extruder_id = extruder_id;
                                }
                            }
                            layer_tools.wiping_extrusions().ensure_perimeters_infills_order(fff_print);
                            if (&layer_tools == &fff_print.m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
                                break;
                        }
                    }

                    // Generate the wipe tower layers.
                    fff_print.m_wipe_tower_data.tool_changes.reserve(fff_print.m_wipe_tower_data.tool_ordering.layer_tools().size());
                    wipe_tower.generate(fff_print.m_wipe_tower_data.tool_changes);

class WipingExtrusions


// Object of this class holds information about whether an extrusion is printed immediately
// after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part
// of several copies - this has to be taken into account.
class WipingExtrusions
{
public:
    bool is_anything_overridden() const {   // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
        return something_overridden;
    }

    // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
    typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;

    // This is called from GCode::process_layer - see implementation for further comments:
    const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);

    // This function goes through all infill entities, decides which ones will be used for wiping and
    // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
    float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);

    void ensure_perimeters_infills_order(const Print& print);

    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);
    	this->something_overridable |= out;
    	return out;
    }

    void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }

private:
    int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
    int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;

    // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
    void set_extruder_override(const ExtrusionEntity* entity, 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, size_t copy_id) const {
        auto it = entity_map.find(entity);
        return it == entity_map.end() ? false : it->second[copy_id] != -1;
    }

    std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map;  // to keep track of who prints what
    bool something_overridable = false;
    bool something_overridden = false;
    const LayerTools* m_layer_tools = nullptr;    // so we know which LayerTools object this belongs to
};

// This function goes through all infill entities, decides which ones will be used for wiping and
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);

std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what

float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)

return volume_to_wipe;

// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)

float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
{
    const LayerTools& lt = *m_layer_tools;
    const float min_infill_volume = 0.f; // ignore infill with smaller volume than this

    if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
        return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it

    // we will sort objects so that dedicated for wiping are at the beginning:
    ConstPrintObjectPtrs object_list = print.objects().vector();
    std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; });

    // We will now iterate through
    //  - first the dedicated objects to mark perimeters or infills (depending on infill_first)
    //  - second through the dedicated ones again to mark infills or perimeters (depending on infill_first)
    //  - then all the others to mark infills (in case that !infill_first, we must also check that the perimeter is finished already
    // this is controlled by the following variable:
    bool perimeters_done = false;

    for (int i=0 ; i<(int)object_list.size() + (perimeters_done ? 0 : 1); ++i) {
        if (!perimeters_done && (i==(int)object_list.size() || !object_list[i]->config().wipe_into_objects)) { // we passed the last dedicated object in list
            perimeters_done = true;
            i=-1;   // let's go from the start again
            continue;
        }

        const PrintObject* object = object_list[i];

        // Finds this layer:
        const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
        if (this_layer == nullptr)
        	continue;
        size_t num_of_copies = object->instances().size();

        // iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
        for (unsigned int copy = 0; copy < num_of_copies; ++copy) {
            for (const LayerRegion *layerm : this_layer->regions()) {
                const auto &region = layerm->region();
                if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
                    continue;

                bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill;
                if (print.config().infill_first != perimeters_done || wipe_into_infill_only) {
                    for (const ExtrusionEntity* ee : layerm->fills.entities) {                      // iterate through all infill Collections
                        auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);

                        if (!is_overriddable(*fill, print.config(), *object, region))
                            continue;

                        if (wipe_into_infill_only && ! print.config().infill_first)
                            // In this case we must check that the original extruder is used on this layer before the one we are overridding
                            // (and the perimeters will be finished before the infill is printed):
                            if (!lt.is_extruder_order(lt.perimeter_extruder(region), new_extruder))
                                continue;

                        if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {     // this infill will be used to wipe this extruder
                            set_extruder_override(fill, copy, new_extruder, num_of_copies);
                            if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
                            	// More material was purged already than asked for.
	                            return 0.f;
                        }
                    }
                }

                // Now the same for perimeters - see comments above for explanation:
                if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
                {
                    for (const ExtrusionEntity* ee : layerm->perimeters.entities) {
                        auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
                        if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
                            set_extruder_override(fill, copy, new_extruder, num_of_copies);
                            if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
                            	// More material was purged already than asked for.
	                            return 0.f;
                        }
                    }
                }
            }
        }
    }
	// Some purge remains to be done on the Wipe Tower.
    assert(volume_to_wipe > 0.);
    return volume_to_wipe;
}

NB

// Now the same for perimeters - see comments above for explanation:
                if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
                {
                    for (const ExtrusionEntity* ee : layerm->perimeters.entities) {
                        auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
                        if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
                            set_extruder_override(fill, copy, new_extruder, num_of_copies);
                            if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
                            	// More material was purged already than asked for.
	                            return 0.f;
                        }
                    }
                }