Merge remote-tracking branch 'remotes/origin/wipe_tower_improvements'

This commit is contained in:
bubnikv 2018-07-17 09:20:19 +02:00
commit c7f59aca7d
19 changed files with 837 additions and 309 deletions

View file

@ -15,6 +15,24 @@
namespace Slic3r {
// Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
{
if (a==b)
return false;
for (auto extruder : extruders) {
if (extruder == a)
return true;
if (extruder == b)
return false;
}
return false;
}
// For the use case when each object is printed separately
// (print.config.complete_objects is true).
ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material)
@ -48,6 +66,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
// (print.config.complete_objects is false).
ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material)
{
m_print_config_ptr = &print.config;
// Initialize the print layers for all objects and all layers.
coordf_t object_bottom_z = 0.;
{
@ -76,9 +95,10 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->collect_extruder_statistics(prime_multi_material);
}
ToolOrdering::LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z)
LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z)
{
auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), ToolOrdering::LayerTools(print_z - EPSILON));
auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
assert(it_layer_tools != m_layer_tools.end());
coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z);
for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) {
@ -102,7 +122,7 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
coordf_t zmax = zs[i] + EPSILON;
for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1])));
m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr));
i = j;
}
}
@ -134,12 +154,29 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
if (layerm == nullptr)
continue;
const PrintRegion &region = *object.print()->regions[region_id];
if (! layerm->perimeters.entities.empty()) {
layer_tools.extruders.push_back(region.config.perimeter_extruder.value);
bool something_nonoverriddable = true;
if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false;
for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
something_nonoverriddable = true;
break;
}
}
if (something_nonoverriddable)
layer_tools.extruders.push_back(region.config.perimeter_extruder.value);
layer_tools.has_object = true;
}
bool has_infill = false;
bool has_solid_infill = false;
bool something_nonoverriddable = false;
for (const ExtrusionEntity *ee : layerm->fills.entities) {
// fill represents infill extrusions of a single island.
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
@ -148,19 +185,33 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
has_solid_infill = true;
else if (role != erNone)
has_infill = true;
if (m_print_config_ptr) {
if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region))
something_nonoverriddable = true;
}
}
if (something_nonoverriddable || !m_print_config_ptr)
{
if (has_solid_infill)
layer_tools.extruders.push_back(region.config.solid_infill_extruder);
if (has_infill)
layer_tools.extruders.push_back(region.config.infill_extruder);
}
if (has_solid_infill)
layer_tools.extruders.push_back(region.config.solid_infill_extruder);
if (has_infill)
layer_tools.extruders.push_back(region.config.infill_extruder);
if (has_solid_infill || has_infill)
layer_tools.has_object = true;
}
}
// Sort and remove duplicates
for (LayerTools &lt : m_layer_tools)
sort_remove_duplicates(lt.extruders);
for (auto& layer : m_layer_tools) {
// Sort and remove duplicates
sort_remove_duplicates(layer.extruders);
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
if (layer.extruders.empty() && layer.has_object)
layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
}
}
// Reorder extruders to minimize layer changes.
@ -217,6 +268,8 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
}
}
void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
{
if (m_layer_tools.empty())
@ -327,4 +380,250 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
}
}
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies)
{
something_overridden = true;
auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first; // (add and) return iterator
auto& copies_vector = entity_map_it->second;
if (copies_vector.size() < num_of_copies)
copies_vector.resize(num_of_copies, -1);
if (copies_vector[copy_id] != -1)
std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
copies_vector[copy_id] = extruder;
}
// Finds first non-soluble extruder on the layer
int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
return (-1);
}
// Finds last non-soluble extruder on the layer
int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
return (-1);
}
// 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(Print::get_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;
}
// 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 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 (print.config.filament_soluble.get_at(new_extruder))
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill
// we will sort objects so that dedicated for wiping are at the beginning:
PrintObjectPtrs object_list = print.objects;
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 auto& object = object_list[i];
// Finds this layer:
auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers.end())
continue;
const Layer* this_layer = *this_layer_it;
unsigned int num_of_copies = object->_shifted_copies.size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
for (size_t region_id = 0; region_id < object->print()->regions.size(); ++ region_id) {
const auto& region = *object->print()->regions[region_id];
if (!region.config.wipe_into_infill && !object->config.wipe_into_objects)
continue;
if ((!print.config.infill_first ? perimeters_done : !perimeters_done) || (!object->config.wipe_into_objects && region.config.wipe_into_infill)) {
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config, *object, region))
continue;
// What extruder would this normally be printed with?
unsigned int correct_extruder = Print::get_extruder(*fill, region);
if (volume_to_wipe<=0)
continue;
if (!object->config.wipe_into_objects && !print.config.infill_first && region.config.wipe_into_infill)
// 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(region.config.perimeter_extruder - 1, 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);
volume_to_wipe -= fill->total_volume();
}
}
}
// Now the same for perimeters - see comments above for explanation:
if (object->config.wipe_into_objects && (print.config.infill_first ? perimeters_done : !perimeters_done))
{
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->perimeters.entities) {
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config, *object, region))
continue;
if (volume_to_wipe<=0)
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= fill->total_volume();
}
}
}
}
}
}
return std::max(0.f, 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)
{
const LayerTools& lt = *m_layer_tools;
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config);
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config);
for (const PrintObject* object : print.objects) {
// Finds this layer:
auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers.end())
continue;
const Layer* this_layer = *this_layer_it;
unsigned int num_of_copies = object->_shifted_copies.size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
for (size_t region_id = 0; region_id < object->print()->regions.size(); ++ region_id) {
const auto& region = *object->print()->regions[region_id];
if (!region.config.wipe_into_infill && !object->config.wipe_into_objects)
continue;
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config, *object, region)
|| is_entity_overridden(fill, copy) )
continue;
// This infill could have been overridden but was not - unless we do something, it could be
// printed before its perimeter, or not be printed at all (in case its original extruder has
// not been added to LayerTools
// Either way, we will now force-override it with something suitable:
if (print.config.infill_first
|| object->config.wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|| lt.is_extruder_order(region.config.perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config.infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
)
set_extruder_override(fill, copy, (print.config.infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
else {
// In this case we can (and should) leave it to be printed normally.
// Force overriding would mean it gets printed before its perimeter.
}
}
// Now the same for perimeters - see comments above for explanation:
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->perimeters.entities) { // iterate through all perimeter Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config, *object, region)
|| is_entity_overridden(fill, copy) )
continue;
set_extruder_override(fill, copy, (print.config.infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
}
}
}
}
}
// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy
// It also 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 has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden,
// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero).
const std::vector<int>* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies)
{
auto entity_map_it = entity_map.find(entity);
if (entity_map_it == entity_map.end())
entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first;
// Now the entity_map_it should be valid, let's make sure the vector is long enough:
entity_map_it->second.resize(num_of_copies, -1);
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1);
return &(entity_map_it->second);
}
} // namespace Slic3r

View file

@ -9,38 +9,97 @@ namespace Slic3r {
class Print;
class PrintObject;
class LayerTools;
class ToolOrdering
// 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:
struct LayerTools
{
LayerTools(const coordf_t z) :
print_z(z),
has_object(false),
has_support(false),
has_wipe_tower(false),
wipe_tower_partitions(0),
wipe_tower_layer_height(0.) {}
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;
}
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
// This is called from GCode::process_layer - see implementation for further comments:
const std::vector<int>* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies);
coordf_t print_z;
bool has_object;
bool has_support;
// Zero based extruder IDs, ordered to minimize tool switches.
std::vector<unsigned int> extruders;
// Will there be anything extruded on this layer for the wipe tower?
// Due to the support layers possibly interleaving the object layers,
// wipe tower will be disabled for some support only layers.
bool has_wipe_tower;
// Number of wipe tower partitions to support the required number of tool switches
// and to support the wipe tower partitions above this one.
size_t wipe_tower_partitions;
coordf_t wipe_tower_layer_height;
};
// 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 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;
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, unsigned int copy_id, int extruder, unsigned int 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, int copy_id) const {
return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1);
}
std::map<const ExtrusionEntity*, std::vector<int>> entity_map; // to keep track of who prints what
bool something_overridden = false;
const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to
};
class LayerTools
{
public:
LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) :
print_z(z),
has_object(false),
has_support(false),
has_wipe_tower(false),
wipe_tower_partitions(0),
wipe_tower_layer_height(0.) {}
bool operator< (const LayerTools &rhs) const { return print_z - EPSILON < rhs.print_z; }
bool operator==(const LayerTools &rhs) const { return std::abs(print_z - rhs.print_z) < EPSILON; }
bool is_extruder_order(unsigned int a, unsigned int b) const;
coordf_t print_z;
bool has_object;
bool has_support;
// Zero based extruder IDs, ordered to minimize tool switches.
std::vector<unsigned int> extruders;
// Will there be anything extruded on this layer for the wipe tower?
// Due to the support layers possibly interleaving the object layers,
// wipe tower will be disabled for some support only layers.
bool has_wipe_tower;
// Number of wipe tower partitions to support the required number of tool switches
// and to support the wipe tower partitions above this one.
size_t wipe_tower_partitions;
coordf_t wipe_tower_layer_height;
WipingExtrusions& wiping_extrusions() {
m_wiping_extrusions.set_layer_tools_ptr(this);
return m_wiping_extrusions;
}
private:
// This object holds list of extrusion that will be used for extruder wiping
WipingExtrusions m_wiping_extrusions;
};
class ToolOrdering
{
public:
ToolOrdering() {}
// For the use case when each object is printed separately
@ -72,7 +131,7 @@ public:
std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
bool empty() const { return m_layer_tools.empty(); }
const std::vector<LayerTools>& layer_tools() const { return m_layer_tools; }
std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; }
private:
@ -80,17 +139,22 @@ private:
void collect_extruders(const PrintObject &object);
void reorder_extruders(unsigned int last_extruder_id);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
void collect_extruder_statistics(bool prime_multi_material);
void collect_extruder_statistics(bool prime_multi_material);
std::vector<LayerTools> m_layer_tools;
// First printing extruder, including the multi-material priming sequence.
unsigned int m_first_printing_extruder = (unsigned int)-1;
// Final printing extruder.
unsigned int m_last_printing_extruder = (unsigned int)-1;
// All extruders, which extrude some material over m_layer_tools.
std::vector<unsigned int> m_all_printing_extruders;
std::vector<LayerTools> m_layer_tools;
// First printing extruder, including the multi-material priming sequence.
unsigned int m_first_printing_extruder = (unsigned int)-1;
// Final printing extruder.
unsigned int m_last_printing_extruder = (unsigned int)-1;
// All extruders, which extrude some material over m_layer_tools.
std::vector<unsigned int> m_all_printing_extruders;
const PrintConfig* m_print_config_ptr = nullptr;
};
} // namespace SLic3r
#endif /* slic3r_ToolOrdering_hpp_ */

View file

@ -21,7 +21,6 @@ TODO LIST
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include "Analyzer.hpp"
@ -138,7 +137,7 @@ public:
width += m_layer_height * float(1. - M_PI / 4.);
if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos)
m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool));
m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool));
m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool));
}
m_gcode += "G1";
@ -231,6 +230,17 @@ public:
Writer& retract(float e, float f = 0.f)
{ return load(-e, f); }
// Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary)
Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f)
{
float time = std::abs(loading_dist / loading_speed);
float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time);
float feedrate = 60.f * std::hypot(x_speed, loading_speed);
float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time;
return extrude_explicit(end_point, y(), loading_dist, feedrate);
}
// Elevate the extruder head above the current print_z position.
Writer& z_hop(float hop, float f = 0.f)
{
@ -276,12 +286,9 @@ public:
// Set extruder temperature, don't wait by default.
Writer& set_extruder_temp(int temperature, bool wait = false)
{
if (temperature != current_temp) {
char buf[128];
sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature);
m_gcode += buf;
current_temp = temperature;
}
char buf[128];
sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature);
m_gcode += buf;
return *this;
};
@ -399,8 +406,7 @@ private:
int current_temp = -1;
const float m_default_analyzer_line_width;
std::string
set_format_X(float x)
std::string set_format_X(float x)
{
char buf[64];
sprintf(buf, " X%.3f", x);
@ -475,7 +481,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower)
{
this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false);
this->m_current_tool = tools.front();
@ -558,7 +563,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
{
for (const auto &b : m_layer_info->tool_changes)
if ( b.new_tool == tool ) {
wipe_volume = wipe_volumes[b.old_tool][b.new_tool];
wipe_volume = b.wipe_volume;
if (tool == m_layer_info->tool_changes.back().new_tool)
last_change_in_layer = true;
wipe_area = b.required_depth * m_layer_info->extra_spacing;
@ -783,51 +788,44 @@ void WipeTowerPrusaMM::toolchange_Unload(
WipeTower::xy end_of_ramming(writer.x(),writer.y());
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
// Pull the filament end to the BEGINNING of the cooling tube while still moving the print head
float oldx = writer.x();
float turning_point = (!m_left_to_right ? std::max(xl,oldx-15.f) : std::min(xr,oldx+15.f) ); // so it's not too far
float xdist = std::abs(oldx-turning_point);
float edist = -(m_cooling_tube_retraction+m_cooling_tube_length/2.f-42);
// Retraction:
float old_x = writer.x();
float turning_point = (!m_left_to_right ? xl : xr );
float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming
writer.suppress_preview()
.load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * 83 ) // fixed speed after ramming
.load_move_x(oldx ,edist , 60.f * std::hypot(xdist,edist)/std::abs(edist) * m_filpar[m_current_tool].unloading_speed )
.load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * m_filpar[m_current_tool].unloading_speed*0.55f )
.load_move_x(oldx ,-12 , 60.f * std::hypot(xdist,12)/12 * m_filpar[m_current_tool].unloading_speed*0.35f )
.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed
.load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed)
.load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed)
.load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
.travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate
.resume_preview();
if (new_temperature != 0) // Set the extruder temperature, but don't wait.
if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait.
writer.set_extruder_temp(new_temperature, false);
m_old_temperature = new_temperature;
}
// cooling:
writer.suppress_preview();
writer.travel(writer.x(), writer.y() + y_step);
const float start_x = writer.x();
turning_point = ( xr-start_x > start_x-xl ? xr : xl );
const float max_x_dist = 2*std::abs(start_x-turning_point);
const unsigned int N = 4 + std::max(0.f, (m_filpar[m_current_tool].cooling_time-14)/3);
float time = m_filpar[m_current_tool].cooling_time / float(N);
// Cooling:
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
if (number_of_moves > 0) {
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
i = 0;
while (i<N) {
const float speed = std::min(3.4,2.2 + i*0.3 + (i==0 ? 0 : 0.3)); // mm per second: 2.2, 2.8, 3.1, 3.4, 3.4, 3.4, ...
const float e_dist = std::min(speed * time,2*m_cooling_tube_length); // distance to travel
// this move is the last one at this speed or someone set tube_length to zero
if (speed * time < 2*m_cooling_tube_length || m_cooling_tube_length<WT_EPSILON) {
++i;
time = m_filpar[m_current_tool].cooling_time / float(N);
}
else
time -= e_dist / speed; // subtract time this part will really take
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
// as for x, we will make sure the feedrate is at most 2000
float x_dist = (turning_point - WT_EPSILON < xl ? -1.f : 1.f) * std::min(e_dist * (float)sqrt(pow(2000 / (60 * speed), 2) - 1),max_x_dist);
const float feedrate = std::hypot(e_dist, x_dist) / ((e_dist / speed) / 60.f);
writer.cool(start_x+x_dist/2.f,start_x,e_dist/2.f,-e_dist/2.f, feedrate);
}
writer.suppress_preview()
.travel(writer.x(), writer.y() + y_step);
old_x = writer.x();
turning_point = xr-old_x > old_x-xl ? xr : xl;
for (int i=0; i<number_of_moves; ++i) {
float speed = initial_speed + speed_inc * 2*i;
writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
speed += speed_inc;
writer.load_move_x_advanced(old_x, -m_cooling_tube_length, speed);
}
}
// let's wait is necessary
// let's wait is necessary:
writer.wait(m_filpar[m_current_tool].delay);
// we should be at the beginning of the cooling tube again - let's move to parking position:
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
@ -871,16 +869,16 @@ void WipeTowerPrusaMM::toolchange_Load(
float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position
// Load the filament while moving left / right, so the excess material will not create a blob at a single position.
float loading_speed = m_filpar[m_current_tool].loading_speed; // mm/s in e axis
float turning_point = ( oldx-xl < xr-oldx ? xr : xl );
float dist = std::abs(oldx-turning_point);
float edist = m_parking_pos_retraction-50-2; // loading is 2mm shorter that previous retraction, 50mm reserved for acceleration/deceleration
writer.append("; CP TOOLCHANGE LOAD\n")
float edist = m_parking_pos_retraction+m_extra_loading_move;
writer.append("; CP TOOLCHANGE LOAD\n")
.suppress_preview()
.load_move_x(turning_point, 20, 60*std::hypot(dist,20.f)/20.f * loading_speed*0.3f) // Acceleration
.load_move_x(oldx,edist,60*std::hypot(dist,edist)/edist * loading_speed) // Fast phase
.load_move_x(turning_point, 20, 60*std::hypot(dist,20.f)/20.f * loading_speed*0.3f) // Slowing down
.load_move_x(oldx, 10, 60*std::hypot(dist,10.f)/10.f * loading_speed*0.1f) // Super slow
.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration
.load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase
.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down
.load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow
.travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate
.resume_preview();
// Reset the extruder current to the normal value.
@ -1057,7 +1055,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
}
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool,bool brim)
void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume)
{
assert(m_plan.back().z <= z_par + WT_EPSILON ); // refuses to add a layer below the last one
@ -1082,13 +1080,13 @@ void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsi
float ramming_depth = depth;
length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
float first_wipe_line = -length_to_extrude;
length_to_extrude += volume_to_length(wipe_volumes[old_tool][new_tool], m_perimeter_width, layer_height_par);
length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
length_to_extrude = std::max(length_to_extrude,0.f);
depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
depth *= m_extra_spacing;
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth,first_wipe_line));
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
}
@ -1128,7 +1126,7 @@ void WipeTowerPrusaMM::save_on_last_wipe()
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f);
float length_to_wipe = volume_to_length(wipe_volumes[m_layer_info->tool_changes.back().old_tool][m_layer_info->tool_changes.back().new_tool],
float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume,
m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save;
length_to_wipe = std::max(length_to_wipe,0.f);
@ -1145,7 +1143,8 @@ void WipeTowerPrusaMM::save_on_last_wipe()
void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
{
if (m_plan.empty())
return;
return;
m_extra_spacing = 1.f;
@ -1165,8 +1164,6 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
for (auto layer : m_plan)
{
set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
if (m_peters_wipe_tower)
m_wipe_tower_rotation_angle += 90.f;
else

View file

@ -5,6 +5,7 @@
#include <string>
#include <sstream>
#include <utility>
#include <algorithm>
#include "WipeTower.hpp"
@ -43,8 +44,8 @@ public:
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
// wipe_area -- space available for one toolchange in mm
WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
float cooling_tube_length, float parking_pos_retraction, float bridging, const std::vector<float>& wiping_matrix,
unsigned int initial_tool) :
float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) :
m_wipe_tower_pos(x, y),
m_wipe_tower_width(width),
m_wipe_tower_rotation_angle(rotation_angle),
@ -54,20 +55,19 @@ public:
m_cooling_tube_retraction(cooling_tube_retraction),
m_cooling_tube_length(cooling_tube_length),
m_parking_pos_retraction(parking_pos_retraction),
m_extra_loading_move(extra_loading_move),
m_bridging(bridging),
m_current_tool(initial_tool)
{
unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+WT_EPSILON);
for (unsigned int i = 0; i<number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders,wiping_matrix.begin()+(i+1)*number_of_extruders));
}
m_current_tool(initial_tool),
wipe_volumes(wiping_matrix)
{}
virtual ~WipeTowerPrusaMM() {}
// Set the extruder properties.
void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed,
float unloading_speed, float delay, std::string ramming_parameters, float nozzle_diameter)
float unloading_speed, float delay, int cooling_moves, float cooling_initial_speed,
float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
{
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
m_filpar.push_back(FilamentParameters());
@ -78,7 +78,9 @@ public:
m_filpar[idx].loading_speed = loading_speed;
m_filpar[idx].unloading_speed = unloading_speed;
m_filpar[idx].delay = delay;
m_filpar[idx].cooling_time = 14.f; // let's fix it for now, cooling moves will be reworked for 1.41 anyway
m_filpar[idx].cooling_moves = cooling_moves;
m_filpar[idx].cooling_initial_speed = cooling_initial_speed;
m_filpar[idx].cooling_final_speed = cooling_final_speed;
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
@ -95,7 +97,7 @@ public:
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim);
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f);
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
@ -192,11 +194,13 @@ private:
float m_layer_height = 0.f; // Current layer height.
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower.
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
// G-code generator parameters.
float m_cooling_tube_retraction = 0.f;
float m_cooling_tube_length = 0.f;
float m_parking_pos_retraction = 0.f;
float m_extra_loading_move = 0.f;
float m_bridging = 0.f;
bool m_adhesion = true;
@ -211,7 +215,9 @@ private:
float loading_speed = 0.f;
float unloading_speed = 0.f;
float delay = 0.f ;
float cooling_time = 0.f;
int cooling_moves = 0;
float cooling_initial_speed = 0.f;
float cooling_final_speed = 0.f;
float ramming_line_width_multiplicator = 0.f;
float ramming_step_multiplicator = 0.f;
std::vector<float> ramming_speed;
@ -229,14 +235,13 @@ private:
bool m_print_brim = true;
// A fill-in direction (positive Y, negative Y) alternates with each layer.
wipe_shape m_current_shape = SHAPE_NORMAL;
unsigned int m_current_tool;
std::vector<std::vector<float>> wipe_volumes;
unsigned int m_current_tool = 0;
const std::vector<std::vector<float>> wipe_volumes;
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
bool m_left_to_right = true;
float m_extra_spacing = 1.f;
// Calculates extrusion flow needed to produce required line width for given layer height
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
{
@ -247,7 +252,7 @@ private:
// Calculates length of extrusion line to extrude given volume
float volume_to_length(float volume, float line_width, float layer_height) const {
return volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.)));
return std::max(0., volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.))));
}
// Calculates depth for all layers and propagates them downwards
@ -300,8 +305,9 @@ private:
float required_depth;
float ramming_depth;
float first_wipe_line;
ToolChange(unsigned int old,unsigned int newtool,float depth=0.f,float ramming_depth=0.f,float fwl=0.f)
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth},first_wipe_line{fwl} {}
float wipe_volume;
ToolChange(unsigned int old, unsigned int 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