Skip to content

Ch. 7.1 ‐ Wipe Tower

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

The data structure of the wipe tower is shown in the figure below. Based on the Print object data, the corresponding gcode fragment is generated in the ToolChangeResult structure.

Screenshot 2023-03-21 012105

The wipe tower generation depends on the order of the extruders and can be summarized as follows:

// plan tool change
atc_wipe_tower.plan_toolchange(atc_print_z, atc_wiping_layer_height, atc_old_tool, atc_new_tool, atc_wiping_volume);
// generate gcode
output_stream.write(m_wipe_tower->append_tcr(*this, wiping_layer_height, new_tool, print_z));

Related commit: 5a5c2d5

More details are presented below:

void Print::process()
// Print.cpp Slicing process, running at a background thread.
void Print::process()
{...
if (this->set_started(psWipeTower)) {
    m_wipe_tower_data.clear();
    m_tool_ordering.clear();
    if (this->has_wipe_tower()) {
        this->_make_wipe_tower();
    }
    this->set_done(psWipeTower);
}
...}
void Print::_make_wipe_tower()
void Print::_make_wipe_tower()
{
    ...
    WipeTower wipe_tower(m_config, wipe_volumes, 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)
    {
        // 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);
    }
    // Generate the wipe tower layers.
    m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
    wipe_tower.generate(m_wipe_tower_data.tool_changes);
}
void WipeTower::plan_toolchange()
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume)
{
    ...
    // this is an actual toolchange - let's calculate depth to reserve on the wipe tower
    // here we can analyze wiping into infell/object
    m_plan.back().tool_changes.push_back(...)
}

WipeTower.hpp: WipeTower.m_plan stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))

std::vector m_plan
// to store information about tool changes for a given layer
struct WipeTowerInfo{
    struct ToolChange {
        size_t old_tool;
        size_t new_tool;
	float required_depth;
        float ramming_depth;
        float first_wipe_line;
        float wipe_volume;
        ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
            : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
    };
    float z;		// z position of the layer
    float height;	// layer height
    float depth;	// depth of the layer based on all layers above
    float extra_spacing;
    float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
    std::vector<ToolChange> tool_changes;
    WipeTowerInfo(float z_par, float layer_height_par) : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
}
wipe_tower.generate(m_wipe_tower_data.tool_changes)
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
// Resulting ToolChangeResults are appended into vector "result"
void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
{

...

std::vector<WipeTower::ToolChangeResult> layer_result;

result.emplace_back(std::move(layer_result));
...
}

Inserting gcode from WipeTower::ToolChangeResult.gcode

// PrusaSlicer.cpp
print->process();

if (printer_technology == ptFFF) {
    if (m_print_config.option<ConfigOptionBool>("atc_enable_tool_clustering")->value == true) {
        print->layer_batch_labeling();
        fff_print.get_ATC_printing_map().display(fff_print.get_ATC_printing_map().gethead());
        outfile = fff_print.export_batched_gcode(outfile, nullptr, nullptr);
        outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
     } else {
        outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
        outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
     }
}
Batched
std::string Print::export_batched_gcode(...)
{
    std::string path = this->output_filepath(path_template);
    std::unique_ptr<GCode> gcode(new GCode);
    gcode->do_batched_export(this, path.c_str(), result, thumbnail_cb);
    return path.c_str();
}

// gcode->do_batched_export(this, path.c_str(), result, thumbnail_cb);
this->_do_batched_export(*print, file, thumbnail_cb);


// Add each of the object's layers separately. Do all objects for each layer.
this->process_sequential_batched_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file);
// Print all objects with the same print_z together. Sort layers by Z.
this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file);
Regular
std::string Print::export_gcode(...)
{
    std::string path = this->output_filepath(path_template);
    std::unique_ptr<GCode> gcode(new GCode);
    gcode->do_export(this, path.c_str(), result, thumbnail_cb);
    return path.c_str();
}

//gcode->do_export(this, path.c_str(), result, thumbnail_cb);
this->_do_export(*print, file, thumbnail_cb);

// Add each of the object's layers separately. Do all objects for each layer.
this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file);
// Print all objects with the same print_z together. Sort layers by Z.
this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file);

Wipe tower does not work for sequential prints

Non-sequential mode: Sort layers by Z
// non-sequential mode: Sort layers by Z.
// All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (has_wipe_tower && ! layers_to_print.empty()) {
    m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), 
        print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
    file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
    if (print.config().single_extruder_multi_material_priming) {
        file.write(m_wipe_tower->prime(*this));
        // Verify, whether the print overaps the priming extrusions.
        {...}
            }
            print.throw_if_canceled();
     }
     // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
     // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
     // and export G-code into file.
     this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file);
     if (m_wipe_tower)
     // Purge the extruder, pull out the active filament.
     file.write(m_wipe_tower->finalize(*this));
    }
void GCode::process_layers
//GCode.cpp
void GCode::process_layers(
    const Print                                                         &print,
    const ToolOrdering                                                  &tool_ordering,
    const std::vector<const PrintInstance*>                             &print_object_instances_ordering,
    const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>>   &layers_to_print,
    GCodeOutputStream                                                   &output_stream)
{
    size_t layer_to_print_idx = 0;
    const auto generator = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
        [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> 
        GCode::LayerResult {
            const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++];
            const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
            if (m_wipe_tower && layer_tools.has_wipe_tower)
                m_wipe_tower->next_layer();
            return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), 
                &print_object_instances_ordering, size_t(-1));
        });
...
}