diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 636f98904..b0ebaa9e5 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1888,6 +1888,15 @@ sub load_print_object_toolpaths { Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs); } +# Create 3D thick extrusion lines for wipe tower extrusions. +sub load_wipe_tower_toolpaths { + my ($self, $print, $colors) = @_; + + $self->SetCurrent($self->GetContext) if $self->UseVBOs; + Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) + if ($print->step_done(STEP_WIPE_TOWER)); +} + sub set_toolpaths_range { my ($self, $min_z, $max_z) = @_; $self->volumes->set_range($min_z, $max_z); diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 573f29b1c..b065e4fbe 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -206,6 +206,7 @@ sub load_print { if ($self->IsShown) { # load skirt and brim $self->canvas->load_print_toolpaths($self->print, \@colors); + $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors); foreach my $object (@{$self->print->objects}) { $self->canvas->load_print_object_toolpaths($object, \@colors); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c1dd69399..ea0fee56c 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -44,6 +44,7 @@ sub process { $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; # must come after make_skirt + $self->make_wipe_tower; # time to make some statistics if (0) { @@ -207,19 +208,13 @@ sub make_skirt { $_->generate_support_material for @{$self->objects}; return if $self->step_done(STEP_SKIRT); - $self->set_step_started(STEP_SKIRT); - - # since this method must be idempotent, we clear skirt paths *before* - # checking whether we need to generate them - $self->skirt->clear; - - if (!$self->has_skirt) { - $self->set_step_done(STEP_SKIRT); - return; - } - $self->status_cb->(88, "Generating skirt"); - $self->_make_skirt(); + $self->set_step_started(STEP_SKIRT); + $self->skirt->clear; + if ($self->has_skirt) { + $self->status_cb->(88, "Generating skirt"); + $self->_make_skirt(); + } $self->set_step_done(STEP_SKIRT); } @@ -292,6 +287,27 @@ sub make_brim { $self->set_step_done(STEP_BRIM); } +sub make_wipe_tower { + my $self = shift; + + # prerequisites + $_->make_perimeters for @{$self->objects}; + $_->infill for @{$self->objects}; + $_->generate_support_material for @{$self->objects}; + $self->make_skirt; + $self->make_brim; + + return if $self->step_done(STEP_WIPE_TOWER); + + $self->set_step_started(STEP_WIPE_TOWER); + $self->_clear_wipe_tower; + if ($self->has_wipe_tower) { +# $self->status_cb->(95, "Generating wipe tower"); + $self->_make_wipe_tower; + } + $self->set_step_done(STEP_WIPE_TOWER); +} + # Wrapper around the C++ Slic3r::Print::validate() # to produce a Perl exception without a hang-up on some Strawberry perls. sub validate diff --git a/lib/Slic3r/Print/State.pm b/lib/Slic3r/Print/State.pm index d242e3760..17c614f1b 100644 --- a/lib/Slic3r/Print/State.pm +++ b/lib/Slic3r/Print/State.pm @@ -6,7 +6,7 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(STEP_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL - STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM); + STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM STEP_WIPE_TOWER); our %EXPORT_TAGS = (steps => \@EXPORT_OK); 1; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 52067259e..e4440fb87 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -142,115 +142,79 @@ Wipe::wipe(GCode &gcodegen, bool toolchange) return gcode; } -WipeTowerIntegration::WipeTowerIntegration(const PrintConfig &print_config) : m_brim_done(false) -{ - // Initialize the wipe tower. - auto *wipe_tower = new WipeTowerPrusaMM( - float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value), - float(print_config.wipe_tower_width.value), float(print_config.wipe_tower_per_color_wipe.value)); - //wipe_tower->set_retract(); - //wipe_tower->set_zhop(); - //wipe_tower->set_zhop(); - // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < 4; ++ i) - wipe_tower->set_extruder( - i, - WipeTowerPrusaMM::parse_material(print_config.filament_type.get_at(i).c_str()), - print_config.temperature.get_at(i), - print_config.first_layer_temperature.get_at(i)); - m_impl.reset(wipe_tower); -} - static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) { return Point(scale_(wipe_tower_pt.x - gcodegen.origin().x), scale_(wipe_tower_pt.y - gcodegen.origin().y)); } +std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const +{ + std::string gcode; + + // Move over the wipe tower. + // Retract for a tool change, using the toolchange retract value and setting the priming extra length. + gcode += gcodegen.retract(true); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, tcr.start_pos), + erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + + // Let the tool change be executed by the wipe tower class. + // Inform the G-code writer about the changes done behind its back. + gcode += tcr.gcode; + // Accumulate the elapsed time for the correct calculation of layer cooling. + //FIXME currently disabled as Slic3r PE needs to be updated to differentiate the moves it could slow down + // from the moves it could not. +// gcodegen.m_elapsed_time += tcr.elapsed_time; + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + gcodegen.writer().toolchange(new_extruder_id); + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + + // Prepare a future wipe. + gcodegen.m_wipe.path.points.clear(); + if (new_extruder_id >= 0) { + // Start the wipe at the current position. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left, + tcr.end_pos.y))); + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + return gcode; +} + std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { - bool over_wipe_tower = false; std::string gcode; - - if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id)) { - // Move over the wipe tower. - gcode += this->travel_to(gcodegen, m_impl->tool_change(extruder_id, WipeTower::PURPOSE_MOVE_TO_TOWER).second); - // Let the tool change be executed by the wipe tower class. - //FIXME calculate time at the wipe tower and add it to m_elapsed_time - std::pair code_and_pos = m_impl->tool_change(extruder_id, WipeTower::PURPOSE_EXTRUDE); - // Inform the G-code writer about the changes done behind its back. - gcode += code_and_pos.first; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - gcodegen.writer().toolchange(extruder_id); - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(code_and_pos.second.x, code_and_pos.second.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, code_and_pos.second)); - this->prepare_wipe(gcodegen, code_and_pos.second); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - over_wipe_tower = true; + assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); + if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (m_layer_idx < m_tool_changes.size()) { + assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); + } m_brim_done = true; } - - if (finish_layer && ! m_impl->layer_finished()) { - // Last extruder change on the layer or no extruder change at all. - if (! over_wipe_tower) - gcode += this->travel_to(gcodegen, m_impl->finish_layer(WipeTower::PURPOSE_MOVE_TO_TOWER).second); - // Let the tool change be executed by the wipe tower class. - //FIXME calculate time at the wipe tower and add it to m_elapsed_time - std::pair code_and_pos = m_impl->finish_layer(WipeTower::PURPOSE_EXTRUDE); - // Inform the G-code writer about the changes done behind its back. - gcode += code_and_pos.first; - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(code_and_pos.second.x, code_and_pos.second.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, code_and_pos.second)); - this->prepare_wipe(gcodegen, code_and_pos.second); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - } - return gcode; } // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. -std::string WipeTowerIntegration::finalize(GCode &gcodegen, const Print &print, bool current_layer_full) +std::string WipeTowerIntegration::finalize(GCode &gcodegen) { std::string gcode; - // Unload the current filament over the purge tower. - if (current_layer_full) { - // There is not enough space on the wipe tower to purge the nozzle into. Lift Z to the next layer. - coordf_t new_print_z = gcodegen.writer().get_position().z + print.objects.front()->config.layer_height.value; - gcode += gcodegen.change_layer(new_print_z); - m_impl->set_layer(float(new_print_z), float(print.objects.front()->config.layer_height.value), 0, false, true); - } - gcode += this->tool_change(gcodegen, -1, false); - m_impl.release(); + if (std::abs(gcodegen.writer().get_position().z - m_final_purge.print_z) > EPSILON) + gcode += gcodegen.change_layer(m_final_purge.print_z); + gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; } -std::string WipeTowerIntegration::travel_to(GCode &gcodegen, const WipeTower::xy &dest) -{ - // Retract for a tool change, using the toolchange retract value and setting the priming extra length. - std::string gcode = gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, dest), - erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); - return gcode; -} - -void WipeTowerIntegration::prepare_wipe(GCode &gcodegen, const WipeTower::xy ¤t_position) -{ - gcodegen.m_wipe.path.points.clear(); - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, current_position)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - float l = m_impl->position().x; - float r = l + m_impl->width(); - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(l - current_position.x) < std::abs(r - current_position.x)) ? r : l, - current_position.y))); -} - #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id) inline void write(FILE *file, const std::string &what) @@ -498,7 +462,10 @@ bool GCode::do_export(FILE *file, Print &print) } } else { // Find tool ordering for all the objects at once, and the initial extruder ID. - tool_ordering = ToolOrdering(print, initial_extruder_id); + // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. + tool_ordering = print.m_tool_ordering.empty() ? + ToolOrdering(print, initial_extruder_id) : + print.m_tool_ordering; initial_extruder_id = tool_ordering.first_extruder(); } if (initial_extruder_id == (unsigned int)-1) { @@ -642,35 +609,24 @@ bool GCode::do_export(FILE *file, Print &print) // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. - if (print.config.single_extruder_multi_material.value && print.config.wipe_tower.value && + if (print.has_wipe_tower() && ! tool_ordering.empty() && tool_ordering.front().wipe_tower_partitions > 0) - m_wipe_tower.reset(new WipeTowerIntegration(print.config)); + m_wipe_tower.reset(new WipeTowerIntegration(print.config, print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); // Extrude the layers. for (auto &layer : layers_to_print) { - // layer.second is of type std::vector, - // wher the objects are sorted by their sorted order given by object_indices. const ToolOrdering::LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); - if (layer_tools.has_wipe_tower && m_wipe_tower) { - bool first_layer = &layer == layers_to_print.data(); - m_wipe_tower->set_layer( - layer.first, - first_layer ? - print.objects.front()->config.first_layer_height.get_abs_value(print.objects.front()->config.layer_height.value) : - print.objects.front()->config.layer_height.value, - layer_tools.wipe_tower_partitions, - first_layer, - layer_tools.wipe_tower_partitions == 0 || (&layer_tools == &tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)); - } + if (m_wipe_tower && layer_tools.has_wipe_tower) + m_wipe_tower->next_layer(); this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); } write(file, this->filter(m_cooling_buffer->flush(), true)); + if (m_wipe_tower) + // Purge the extruder, pull out the active filament. + write(file, m_wipe_tower->finalize(*this)); } // write end commands to file - if (m_wipe_tower) - write(file, m_wipe_tower->finalize(*this, print, tool_ordering.back().wipe_tower_partitions > 0 && m_wipe_tower->layer_finished())); - else - write(file, this->retract()); // TODO: process this retract through PressureRegulator in order to discharge fully + write(file, this->retract()); write(file, m_writer.set_fan(false)); writeln(file, m_placeholder_parser.process(print.config.end_gcode)); write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% @@ -1036,7 +992,7 @@ void GCode::process_layer( std::vector> lower_layer_edge_grids(layers.size()); for (unsigned int extruder_id : layer_tools.extruders) { - gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? + gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : this->set_extruder(extruder_id); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 1fca849e2..548b3bcda 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -75,18 +75,36 @@ public: class WipeTowerIntegration { public: - WipeTowerIntegration(const PrintConfig &config); - void set_layer(coordf_t print_z, coordf_t layer_height, size_t max_tool_changes, bool is_first_layer, bool is_last_layer) - { m_impl->set_layer(float(print_z), float(layer_height), max_tool_changes, is_first_layer, is_last_layer); } - bool layer_finished() const { return m_impl->layer_finished(); } + WipeTowerIntegration( + const PrintConfig &print_config, + const std::vector> &tool_changes, + const WipeTower::ToolChangeResult &final_purge) : + m_left(float(print_config.wipe_tower_x.value)), + m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)), + m_tool_changes(tool_changes), + m_final_purge(final_purge), + m_layer_idx(-1), + m_tool_change_idx(0), + m_brim_done(false) {} + + void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); - std::string finalize(GCode &gcodegen, const Print &print, bool current_layer_full); + std::string finalize(GCode &gcodegen); private: - std::string travel_to(GCode &codegen, const WipeTower::xy &dest); - void prepare_wipe(GCode &gcodegen, const WipeTower::xy ¤t_position); - std::unique_ptr m_impl; - bool m_brim_done; + WipeTowerIntegration& operator=(const WipeTowerIntegration&); + std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; + + // Left / right edges of the wipe tower, for the planning of wipe moves. + const float m_left; + const float m_right; + // Reference to cached values at the Printer class. + const std::vector> &m_tool_changes; + const WipeTower::ToolChangeResult &m_final_purge; + // Current layer index. + int m_layer_idx; + int m_tool_change_idx; + bool m_brim_done; }; class GCode { diff --git a/xs/src/libslic3r/GCode/ToolOrdering.cpp b/xs/src/libslic3r/GCode/ToolOrdering.cpp index cfe7b6df0..789b499fc 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.cpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.cpp @@ -1,3 +1,4 @@ +#include "Print.hpp" #include "ToolOrdering.hpp" #include diff --git a/xs/src/libslic3r/GCode/ToolOrdering.hpp b/xs/src/libslic3r/GCode/ToolOrdering.hpp index 64e090b30..81f6e8eaa 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.hpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.hpp @@ -4,10 +4,12 @@ #define slic3r_ToolOrdering_hpp_ #include "libslic3r.h" -#include "Print.hpp" namespace Slic3r { +class Print; +class PrintObject; + class ToolOrdering { public: @@ -47,6 +49,8 @@ public: // (print.config.complete_objects is false). ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1); + void clear() { m_layer_tools.clear(); } + // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. unsigned int first_extruder() const; @@ -61,6 +65,7 @@ public: const LayerTools& front() const { return m_layer_tools.front(); } const LayerTools& back() const { return m_layer_tools.back(); } bool empty() const { return m_layer_tools.empty(); } + const std::vector& layer_tools() const { return m_layer_tools; } private: void initialize_layers(std::vector &zs); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 7237be216..95c6f6f63 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Slic3r { @@ -20,6 +21,8 @@ public: xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } + bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; } + bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; } float x; float y; }; @@ -55,18 +58,50 @@ public: PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, }; + // Extrusion path of the wipe tower, for 3D preview of the generated tool paths. + struct Extrusion + { + Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} + // End position of this extrusion. + xy pos; + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width; + // Current extruder index. + unsigned int tool; + }; + + struct ToolChangeResult + { + // Print heigh of this tool change. + float print_z; + // G-code section to be directly included into the output G-code. + std::string gcode; + // For path preview. + std::vector extrusions; + // Initial position, at which the wipe tower starts its action. + // At this position the extruder is loaded and there is no Z-hop applied. + xy start_pos; + // Last point, at which the normal G-code generator of Slic3r shall continue. + // At this position the extruder is loaded and there is no Z-hop applied. + xy end_pos; + // Time elapsed over this tool change. + // This is useful not only for the print time estimation, but also for the control of layer cooling. + float elapsed_time; + }; + // Returns gcode for toolchange and the end position. // if new_tool == -1, just unload the current filament over the wipe tower. - virtual std::pair tool_change(int new_tool, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult tool_change(int new_tool, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. - virtual std::pair finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const = 0; + virtual bool layer_finished() const = 0; }; }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 4c77bb171..51db1a552 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef __linux #include @@ -25,16 +26,34 @@ public: m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), - m_extrusion_flow(0.f) {} + m_extrusion_flow(0.f), + m_layer_height(0.f), + m_preview_suppressed(false), + m_elapsed_time(0.f) {} - Writer& set_initial_position(const WipeTower::xy &pos) { m_current_pos = pos; return *this; } + Writer& set_initial_position(const WipeTower::xy &pos) { + m_start_pos = pos; + m_current_pos = pos; + return *this; + } + + Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } Writer& set_z(float z) { m_current_z = z; return *this; } + Writer& set_layer_height(float layer_height) + { m_layer_height = layer_height; return *this; } + Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } + // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various + // filament loading and cooling moves from normal extrusion moves. Therefore the writer + // is asked to suppres output of some lines, which look like extrusions. + Writer& suppress_preview() { m_preview_suppressed = true; return *this; } + Writer& resume_preview() { m_preview_suppressed = false; return *this; } + Writer& feedrate(float f) { if (f != m_current_feedrate) @@ -43,24 +62,48 @@ public: } const std::string& gcode() const { return m_gcode; } + const std::vector& extrusions() const { return m_extrusions; } float x() const { return m_current_pos.x; } float y() const { return m_current_pos.y; } + const WipeTower::xy& start_pos() const { return m_start_pos; } const WipeTower::xy& pos() const { return m_current_pos; } + float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. Writer& extrude_explicit(float x, float y, float e, float f = 0.f) { if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) + // Neither extrusion nor a travel move. return *this; + + float dx = x - m_current_pos.x; + float dy = y - m_current_pos.y; + double len = sqrt(dx*dx+dy*dy); + if (! m_preview_suppressed && e > 0.f && len > 0.) { + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width = float(double(e) * m_filament_area / (len * m_layer_height)); + // Correct for the roundings of a squished extrusion. + width += float(m_layer_height * (1. - M_PI / 4.)); + if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos) + m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool)); + } + m_gcode += "G1"; if (x != m_current_pos.x) m_gcode += set_format_X(x); if (y != m_current_pos.y) m_gcode += set_format_Y(y); + if (e != 0.f) m_gcode += set_format_E(e); + if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); + + // Update the elapsed time with a rough estimate. + m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; return *this; } @@ -146,6 +189,7 @@ public: char buf[64]; sprintf(buf, "T%d\n", tool); m_gcode += buf; + m_current_tool = tool; return *this; } @@ -222,11 +266,18 @@ public: Writer& append(const char *text) { m_gcode += text; return *this; } private: + WipeTower::xy m_start_pos; WipeTower::xy m_current_pos; float m_current_z; float m_current_feedrate; + unsigned int m_current_tool; + float m_layer_height; float m_extrusion_flow; + bool m_preview_suppressed; std::string m_gcode; + std::vector m_extrusions; + float m_elapsed_time; + const double m_filament_area = 0.25*M_PI*1.75*1.75; std::string set_format_X(float x) { char buf[64]; @@ -260,6 +311,8 @@ private: m_current_feedrate = f; return buf; } + + Writer& operator=(const Writer &rhs); }; } // namespace PrusaMultiMaterial @@ -287,7 +340,7 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam return INVALID; } -std::pair WipeTowerPrusaMM::tool_change(int tool, Purpose purpose) +WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(int tool, Purpose purpose) { // Either it is the last tool unload, // or there must be a nonzero wipe tower partitions available. @@ -306,10 +359,12 @@ std::pair WipeTowerPrusaMM::tool_change(int tool, Pu PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes) - .comment_material(m_current_material) + .comment_material(m_material[m_current_tool]) .append(";--------------------\n") .speed_override(100); @@ -318,7 +373,7 @@ std::pair WipeTowerPrusaMM::tool_change(int tool, Pu if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { // Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower. - float initial_retract = ((m_current_material == SCAFF) ? 1.f : 0.5f) * m_retract; + float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract; writer // Lift for a Z hop. .z_hop(m_zhop, 7200) // Additional retract on move to tower. @@ -339,7 +394,7 @@ std::pair WipeTowerPrusaMM::tool_change(int tool, Pu // Increase the extruder driver current to allow fast ramming. writer.set_extruder_trimpot(750); // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. - toolchange_Unload(writer, cleaning_box, m_current_material, + toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]); if (tool >= 0) { @@ -379,10 +434,17 @@ std::pair WipeTowerPrusaMM::tool_change(int tool, Pu m_current_wipe_start_y += m_wipe_area; } - return std::pair(writer.gcode(), writer.pos()); + ToolChangeResult result; + result.print_z = this->m_z_pos; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos(); + result.end_pos = writer.pos(); + return result; } -std::pair WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( m_wipe_tower_pos, @@ -393,6 +455,8 @@ std::pair WipeTowerPrusaMM::toolchange_Brim(Purpose writer.set_extrusion_flow(m_extrusion_flow * 1.1f) // Let the writer know the current Z position as a base for Z-hop. .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) .append( ";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); @@ -453,7 +517,14 @@ std::pair WipeTowerPrusaMM::toolchange_Brim(Purpose m_idx_tool_change_in_layer = 0; } - return std::pair(writer.gcode(), writer.pos()); + ToolChangeResult result; + result.print_z = this->m_z_pos; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos(); + result.end_pos = writer.pos(); + return result; } // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. @@ -510,7 +581,8 @@ void WipeTowerPrusaMM::toolchange_Unload( if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr)) std::swap(xl, xr); // Horizontal cooling moves will be performed at the following Y coordinate: - writer.travel(xr, writer.y() + y_step * 0.8f, 7200); + writer.travel(xr, writer.y() + y_step * 0.8f, 7200) + .suppress_preview(); switch (current_material) { case ABS: @@ -541,7 +613,8 @@ void WipeTowerPrusaMM::toolchange_Unload( .cool(xl, xr, 5, -3, 2400); } - writer.flush_planner_queue(); + writer.resume_preview() + .flush_planner_queue(); } // Change the tool, set a speed override for solube and flex materials. @@ -561,7 +634,7 @@ void WipeTowerPrusaMM::toolchange_Change( writer.set_tool(new_tool) .speed_override(speed_override) .flush_planner_queue(); - m_current_material = new_material; + m_current_tool = new_tool; } void WipeTowerPrusaMM::toolchange_Load( @@ -574,10 +647,12 @@ void WipeTowerPrusaMM::toolchange_Load( writer.append("; CP TOOLCHANGE LOAD\n") // Load the filament while moving left / right, // so the excess material will not create a blob at a single position. + .suppress_preview() .load_move_x(xr, 20, 1400) .load_move_x(xl, 40, 3000) .load_move_x(xr, 20, 1600) - .load_move_x(xl, 10, 1000); + .load_move_x(xl, 10, 1000) + .resume_preview(); // Extrude first five lines (just three lines if colorInit is set). writer.extrude(xr, writer.y(), 1600); @@ -634,7 +709,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( writer.set_extrusion_flow(m_extrusion_flow); } -std::pair WipeTowerPrusaMM::finish_layer(Purpose purpose) +WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. @@ -643,6 +718,8 @@ std::pair WipeTowerPrusaMM::finish_layer(Purpose pur PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP EMPTY GRID START\n") // m_num_layer_changes is incremented by set_z, so it is 1 based. @@ -676,7 +753,7 @@ std::pair WipeTowerPrusaMM::finish_layer(Purpose pur } else { // The print head is inside the wipe tower. Rather move to the start of the following extrusion. // writer.set_initial_position(fill_box.ld); - writer.travel(fill_box.ld, 7000); + writer.set_initial_position(fill_box.ld); } if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { @@ -734,7 +811,14 @@ std::pair WipeTowerPrusaMM::finish_layer(Purpose pur m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes; } - return std::pair(writer.gcode(), writer.pos()); + ToolChangeResult result; + result.print_z = this->m_z_pos; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos(); + result.end_pos = writer.pos(); + return result; } }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 58af91311..2889c30e3 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -39,11 +39,13 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // 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 wipe_area) : + WipeTowerPrusaMM(float x, float y, float width, float wipe_area, unsigned int initial_tool) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), m_wipe_area(wipe_area), - m_z_pos(0.f) { + m_z_pos(0.f), + m_current_tool(initial_tool) + { for (size_t i = 0; i < 4; ++ i) { // Extruder specific parameters. m_material[i] = PLA; @@ -51,6 +53,7 @@ public: m_first_layer_temperature[i] = 0; } } + virtual ~WipeTowerPrusaMM() {} // _retract - retract value in mm @@ -81,6 +84,7 @@ public: bool is_last_layer) { m_z_pos = print_z; + m_layer_height = layer_height; m_max_color_changes = max_tool_changes; m_is_first_layer = is_first_layer; m_is_last_layer = is_last_layer; @@ -105,24 +109,24 @@ public: } // Return the wipe tower position. - virtual const xy& position() const { return m_wipe_tower_pos; } + virtual const xy& position() const { return m_wipe_tower_pos; } // Return the wipe tower width. - virtual float width() const { return m_wipe_tower_width; } + virtual float width() const { return m_wipe_tower_width; } // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const { return m_max_color_changes == 0; } + virtual bool finished() const { return m_max_color_changes == 0; } // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. - virtual std::pair tool_change(int new_tool, Purpose purpose); + virtual ToolChangeResult tool_change(int new_tool, Purpose purpose); // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. - virtual std::pair finish_layer(Purpose purpose); + virtual ToolChangeResult finish_layer(Purpose purpose); // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const + virtual bool layer_finished() const { return m_idx_tool_change_in_layer == m_max_color_changes; } private: @@ -143,6 +147,8 @@ private: float m_wipe_area; // Current Z position. float m_z_pos = 0.f; + // Current layer height. + float m_layer_height = 0.f; // Maximum number of color changes per layer. size_t m_max_color_changes = 0; // Is this the 1st layer of the print? If so, print the brim around the waste tower. @@ -172,7 +178,7 @@ private: unsigned int m_idx_tool_change_in_layer = 0; // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; - material_type m_current_material = PLA; + unsigned int m_current_tool = 0; // Current y position at the wipe tower. float m_current_wipe_start_y = 0.f; @@ -210,7 +216,7 @@ private: // Returns gcode for wipe tower brim // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - std::pair toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); + ToolChangeResult toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( PrusaMultiMaterial::Writer &writer, diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 5aefac6f8..ec34f3353 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -31,6 +31,8 @@ public: SurfaceCollection slices; // collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. ExtrusionEntityCollection thin_fills; // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index cd2b22963..50b272608 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -5,6 +5,7 @@ #include "Flow.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" +#include "GCode/WipeTowerPrusaMM.hpp" #include #include #include @@ -117,7 +118,7 @@ Print::invalidate_state_by_config_options(const std::vector // this method only accepts PrintConfig option keys for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { - if (*opt_key == "skirts" + if ( *opt_key == "skirts" || *opt_key == "skirt_height" || *opt_key == "skirt_distance" || *opt_key == "min_skirt_length" @@ -129,12 +130,26 @@ Print::invalidate_state_by_config_options(const std::vector } else if (*opt_key == "nozzle_diameter" || *opt_key == "resolution") { osteps.insert(posSlice); + } else if ( + *opt_key == "complete_objects" + || *opt_key == "filament_type" + || *opt_key == "first_layer_temperature" + || *opt_key == "gcode_flavor" + || *opt_key == "single_extruder_multi_material" + || *opt_key == "spiral_vase" + || *opt_key == "temperature" + || *opt_key == "wipe_tower" + || *opt_key == "wipe_tower_x" + || *opt_key == "wipe_tower_y" + || *opt_key == "wipe_tower_width" + || *opt_key == "wipe_tower_per_color_wipe" + || *opt_key == "z_offset") { + steps.insert(psWipeTower); } else if (*opt_key == "avoid_crossing_perimeters" || *opt_key == "bed_shape" || *opt_key == "bed_temperature" || *opt_key == "bridge_acceleration" || *opt_key == "bridge_fan_speed" - || *opt_key == "complete_objects" || *opt_key == "cooling" || *opt_key == "default_acceleration" || *opt_key == "disable_fan_first_layers" @@ -150,14 +165,11 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "fan_below_layer_time" || *opt_key == "filament_diameter" || *opt_key == "filament_notes" - || *opt_key == "filament_type" || *opt_key == "filament_soluble" || *opt_key == "first_layer_acceleration" || *opt_key == "first_layer_bed_temperature" || *opt_key == "first_layer_speed" - || *opt_key == "first_layer_temperature" || *opt_key == "gcode_comments" - || *opt_key == "gcode_flavor" || *opt_key == "infill_acceleration" || *opt_key == "infill_first" || *opt_key == "layer_gcode" @@ -181,24 +193,15 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "retract_restart_extra_toolchange" || *opt_key == "retract_speed" || *opt_key == "deretract_speed" - || *opt_key == "single_extruder_multi_material" || *opt_key == "slowdown_below_layer_time" - || *opt_key == "spiral_vase" || *opt_key == "standby_temperature_delta" || *opt_key == "start_gcode" - || *opt_key == "temperature" || *opt_key == "threads" || *opt_key == "toolchange_gcode" || *opt_key == "travel_speed" || *opt_key == "use_firmware_retraction" || *opt_key == "use_relative_e_distances" || *opt_key == "wipe" - || *opt_key == "wipe_tower" - || *opt_key == "wipe_tower_x" - || *opt_key == "wipe_tower_y" - || *opt_key == "wipe_tower_width" - || *opt_key == "wipe_tower_per_color_wipe" - || *opt_key == "z_offset" || *opt_key == "max_volumetric_extrusion_rate_slope_negative" || *opt_key == "max_volumetric_extrusion_rate_slope_positive") { // these options only affect G-code export, so nothing to invalidate @@ -208,6 +211,7 @@ Print::invalidate_state_by_config_options(const std::vector osteps.insert(posSupportMaterial); steps.insert(psSkirt); steps.insert(psBrim); + steps.insert(psWipeTower); } else { // for legacy, if we can't handle this option let's invalidate all steps return this->invalidate_all_steps(); @@ -233,23 +237,22 @@ Print::invalidate_step(PrintStep step) bool invalidated = this->state.invalidate(step); // propagate to dependent steps - if (step == psSkirt) { + if (step == psSkirt) this->invalidate_step(psBrim); - } return invalidated; } -bool -Print::invalidate_all_steps() +bool Print::invalidate_all_steps() { // make a copy because when invalidating steps the iterators are not working anymore std::set steps = this->state.started; bool invalidated = false; - for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { - if (this->invalidate_step(*step)) invalidated = true; - } + for (PrintStep step : steps) + if (this->invalidate_step(step)) + invalidated = true; + return invalidated; } @@ -369,6 +372,7 @@ void Print::add_model_object(ModelObject* model_object, int idx) // invalidate steps this->invalidate_step(psSkirt); this->invalidate_step(psBrim); + this->invalidate_step(psWipeTower); } } @@ -991,15 +995,119 @@ void Print::_make_skirt() this->skirt.reverse(); } -std::string -Print::output_filename() +// Wipe tower support. +bool Print::has_wipe_tower() +{ + return + this->config.single_extruder_multi_material.value && + ! this->config.spiral_vase.value && + this->config.wipe_tower.value && + this->config.nozzle_diameter.values.size() > 1; +} + +void Print::_clear_wipe_tower() +{ + m_tool_ordering.clear(); + m_wipe_tower_tool_changes.clear(); + m_wipe_tower_final_purge.reset(nullptr); +} + +void Print::_make_wipe_tower() +{ + this->_clear_wipe_tower(); + if (! this->has_wipe_tower()) + return; + + m_tool_ordering = ToolOrdering(*this, (unsigned int)-1); + unsigned int initial_extruder_id = m_tool_ordering.first_extruder(); + if (initial_extruder_id == (unsigned int)-1) + return; + + // Initialize the wipe tower. + WipeTowerPrusaMM wipe_tower( + float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), + float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), + initial_extruder_id); + + //wipe_tower.set_retract(); + //wipe_tower.set_zhop(); + //wipe_tower.set_zhop(); + + // Set the extruder & material properties at the wipe tower object. + for (size_t i = 0; i < 4; ++ i) + wipe_tower.set_extruder( + i, + WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()), + this->config.temperature.get_at(i), + this->config.first_layer_temperature.get_at(i)); + + // Generate the wipe tower layers. + m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); + unsigned int current_extruder_id = initial_extruder_id; + for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { + if (! layer_tools.has_wipe_tower) + // This is a support only layer, or the wipe tower does not reach to this height. + continue; + bool first_layer = &layer_tools == &m_tool_ordering.front(); + bool last_layer = &layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0; + wipe_tower.set_layer( + float(layer_tools.print_z), + float(first_layer ? + this->objects.front()->config.first_layer_height.get_abs_value(this->objects.front()->config.layer_height.value) : + this->objects.front()->config.layer_height.value), + layer_tools.wipe_tower_partitions, + first_layer, + last_layer); + std::vector tool_changes; + for (unsigned int extruder_id : layer_tools.extruders) + if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) { + tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, WipeTower::PURPOSE_EXTRUDE)); + current_extruder_id = extruder_id; + } + if (! wipe_tower.layer_finished()) { + tool_changes.emplace_back(wipe_tower.finish_layer(WipeTower::PURPOSE_EXTRUDE)); + if (tool_changes.size() > 1) { + // Merge the two last tool changes into one. + WipeTower::ToolChangeResult &tc1 = tool_changes[tool_changes.size() - 2]; + WipeTower::ToolChangeResult &tc2 = tool_changes.back(); + if (tc1.end_pos != tc2.start_pos) { + // Add a travel move from tc1.end_pos to tc2.start_pos. + char buf[2048]; + sprintf(buf, "G1 X%.3f Y%.3f F7200\n", tc2.start_pos.x, tc2.start_pos.y); + tc1.gcode += buf; + } + tc1.gcode += tc2.gcode; + append(tc1.extrusions, tc2.extrusions); + tc1.end_pos = tc2.end_pos; + tool_changes.pop_back(); + } + } + m_wipe_tower_tool_changes.emplace_back(std::move(tool_changes)); + if (last_layer) + break; + } + + // Tower is printed to the top and it has no empty space for the final extruder purge. + bool tower_full = m_tool_ordering.back().wipe_tower_partitions > 0 && wipe_tower.layer_finished(); + coordf_t last_print_z = m_tool_ordering.back().print_z; + + // Unload the current filament over the purge tower. + if (tower_full) { + // There is not enough space on the wipe tower to purge the nozzle into. Lift Z to the next layer. + coordf_t layer_height = this->objects.front()->config.layer_height.value; + wipe_tower.set_layer(float(last_print_z + layer_height), float(layer_height), 0, false, true); + } + m_wipe_tower_final_purge = Slic3r::make_unique( + wipe_tower.tool_change(-1, WipeTower::PURPOSE_EXTRUDE)); +} + +std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); return this->placeholder_parser.process(this->config.output_filename_format.value); } -std::string -Print::output_filepath(const std::string &path) +std::string Print::output_filepath(const std::string &path) { // if we were supplied no path, generate an automatic one based on our first object's input file if (path.empty()) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 9ad45bbcd..01180863f 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -13,6 +13,8 @@ #include "Model.hpp" #include "PlaceholderParser.hpp" #include "Slicing.hpp" +#include "GCode/ToolOrdering.hpp" +#include "GCode/WipeTower.hpp" namespace Slic3r { @@ -22,7 +24,7 @@ class ModelObject; // Print step IDs for keeping track of the print state. enum PrintStep { - psSkirt, psBrim, + psSkirt, psBrim, psWipeTower }; enum PrintObjectStep { posSlice, posPerimeters, posPrepareInfill, @@ -258,6 +260,18 @@ public: void auto_assign_extruders(ModelObject* model_object) const; void _make_skirt(); + + // Wipe tower support. + bool has_wipe_tower(); + void _clear_wipe_tower(); + void _make_wipe_tower(); + // Tool ordering of a non-sequential print has to be known to calculate the wipe tower. + // Cache it here, so it does not need to be recalculated during the G-code generation. + ToolOrdering m_tool_ordering; + // Cache of tool changes per print layer. + std::vector> m_wipe_tower_tool_changes; + std::unique_ptr m_wipe_tower_final_purge; + std::string output_filename(); std::string output_filepath(const std::string &path); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 601a4520a..5eb08a833 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -316,17 +316,21 @@ PrintObject::invalidate_step(PrintObjectStep step) this->invalidate_step(posPrepareInfill); this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); + this->_print->invalidate_step(psWipeTower); } else if (step == posPrepareInfill) { this->invalidate_step(posInfill); } else if (step == posInfill) { this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); + this->_print->invalidate_step(psWipeTower); } else if (step == posSlice) { this->invalidate_step(posPerimeters); this->invalidate_step(posSupportMaterial); + this->_print->invalidate_step(psWipeTower); } else if (step == posSupportMaterial) { this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); + this->_print->invalidate_step(psWipeTower); } return invalidated; @@ -1055,7 +1059,7 @@ void PrintObject::_slice() { BOOST_LOG_TRIVIAL(info) << "Slicing objects..."; -#if 0 +#if 1 // Disable parallelization for debugging purposes. static tbb::task_scheduler_init *tbb_init = nullptr; tbb_init = new tbb::task_scheduler_init(1); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 591a584ed..03fba8ecc 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -962,4 +962,148 @@ void _3DScene::_load_print_object_toolpaths( BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; } +void _3DScene::_load_wipe_tower_toolpaths( + const Print *print, + GLVolumeCollection *volumes, + const std::vector &tool_colors_str, + bool use_VBOs) +{ + std::vector tool_colors = parse_colors(tool_colors_str); + + struct Ctxt + { + const Print *print; + const std::vector *tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max () { return 131072; } // 3.15MB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + } ctxt; + + ctxt.print = print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t n_layers = print->m_wipe_tower_tool_changes.size(); + size_t grain_size = std::max(n_layers / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + volumes->volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = volumes->volumes.size(); + std::vector volumes_per_thread(n_layers); + tbb::parallel_for( + tbb::blocked_range(0, n_layers, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + // Bounding box of this slab of a wipe tower. + BoundingBoxf3 bbox; + bbox.min = Pointf3( + ctxt.print->config.wipe_tower_x.value - 10., + ctxt.print->config.wipe_tower_y.value - 10., + ctxt.print->m_wipe_tower_tool_changes[range.begin()].front().print_z - 3.); + bbox.max = Pointf3( + ctxt.print->config.wipe_tower_x.value + ctxt.print->config.wipe_tower_width.value + 10., + ctxt.print->config.wipe_tower_y.value + ctxt.print->config.wipe_tower_per_color_wipe.value * + ctxt.print->m_tool_ordering.layer_tools()[range.begin()].wipe_tower_partitions + 10., + ctxt.print->m_wipe_tower_tool_changes[range.end() - 1].front().print_z + 0.1); + std::vector vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++ i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } else + vols = { new_volume(ctxt.color_support()) }; + for (size_t i = 0; i < vols.size(); ++ i) { + GLVolume &volume = *vols[i]; + volume.bounding_box = bbox; + volume.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + const std::vector &layer = ctxt.print->m_wipe_tower_tool_changes[idx_layer]; + coordf_t layer_height = (idx_layer == 0) ? + ctxt.print->objects.front()->config.first_layer_height.get_abs_value(ctxt.print->objects.front()->config.layer_height.value) : + ctxt.print->objects.front()->config.layer_height.value; + for (size_t i = 0; i < vols.size(); ++ i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.push_back(layer.front().print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++ i; + continue; + } + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++ j) ; + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++ j) ; + size_t n_lines = j - i; + Lines lines; + std::vector widths; + std::vector heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, layer_height); + for (; i < j; ++ i) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + assert(e.width > 0.f); + const WipeTower::Extrusion &e_prev = *(&e - 1); + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + widths.emplace_back(e.width); + } + thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.color_by_tool() ? e.tool : 0]); + } + } + } + for (size_t i = 0; i < vols.size(); ++ i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + vol_new.bounding_box = bbox; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + for (size_t i = 0; i < vols.size(); ++ i) + vols[i]->indexed_vertex_array.shrink_to_fit(); + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + volumes->volumes.erase( + std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + volumes->volumes.end()); + for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) + volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; +} + } diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 1996ca965..ec1b43ffe 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -354,6 +354,13 @@ public: GLVolumeCollection *volumes, const std::vector &tool_colors, bool use_VBOs); + + + static void _3DScene::_load_wipe_tower_toolpaths( + const Print *print, + GLVolumeCollection *volumes, + const std::vector &tool_colors_str, + bool use_VBOs); }; } diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 06d4b8c19..29fa30115 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -135,4 +135,13 @@ _load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs) CODE: _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0); +void +_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs) + Print *print; + GLVolumeCollection *volumes; + std::vector tool_colors; + int use_VBOs; + CODE: + _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + %} diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 2a60f101b..2aa4cd59a 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -19,6 +19,7 @@ _constant() STEP_SUPPORTMATERIAL = posSupportMaterial STEP_SKIRT = psSkirt STEP_BRIM = psBrim + STEP_WIPE_TOWER = psWipeTower PROTOTYPE: CODE: RETVAL = ix; @@ -238,6 +239,11 @@ _constant() Clone skirt_flow(); void _make_skirt(); + + bool has_wipe_tower(); + void _clear_wipe_tower(); + void _make_wipe_tower(); + %{ double