From 6ff9ff03dbc6b11ac26eae0dbf93262aed33ab0b Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 4 Sep 2023 22:25:56 +0800 Subject: [PATCH] wip --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 26 +- src/libslic3r/GCode/WipeTower.hpp | 8 +- src/libslic3r/GCode/WipeTower2.cpp | 1634 +++++++++++++++++++++++++ src/libslic3r/GCode/WipeTower2.hpp | 322 +++++ src/libslic3r/Preset.cpp | 14 +- src/libslic3r/Print.cpp | 218 +++- src/libslic3r/Print.hpp | 99 +- src/libslic3r/PrintConfig.cpp | 246 +++- src/libslic3r/PrintConfig.hpp | 38 +- src/slic3r/CMakeLists.txt | 3 +- src/slic3r/GUI/ConfigManipulation.cpp | 7 +- src/slic3r/GUI/Plater.cpp | 3 +- src/slic3r/GUI/RammingChart.cpp | 292 +++++ src/slic3r/GUI/RammingChart.hpp | 120 ++ src/slic3r/GUI/Tab.cpp | 48 + src/slic3r/GUI/WipeTowerDialog.cpp | 154 ++- src/slic3r/GUI/WipeTowerDialog.hpp | 30 + 18 files changed, 3173 insertions(+), 91 deletions(-) create mode 100644 src/libslic3r/GCode/WipeTower2.cpp create mode 100644 src/libslic3r/GCode/WipeTower2.hpp create mode 100644 src/slic3r/GUI/RammingChart.cpp create mode 100644 src/slic3r/GUI/RammingChart.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d8cf96c547..76b6c3061c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -142,6 +142,8 @@ set(lisbslic3r_sources GCode/ToolOrdering.hpp GCode/WipeTower.cpp GCode/WipeTower.hpp + GCode/WipeTower2.cpp + GCode/WipeTower2.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp GCode/AvoidCrossingPerimeters.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2472fe45cc..750282f1e0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -438,7 +438,7 @@ static std::vector get_path_of_change_filament(const Print& print) gcode += gcodegen.writer().unretract(); } - + // Process the end filament gcode. std::string end_filament_gcode_str; if (gcodegen.writer().extruder() != nullptr) { @@ -707,12 +707,12 @@ static std::vector get_path_of_change_filament(const Print& print) std::string WipeTowerIntegration::prime(GCode& gcodegen) { std::string gcode; -#if 0 - for (const WipeTower::ToolChangeResult& tcr : m_priming) { - if (! tcr.extrusions.empty()) - gcode += append_tcr(gcodegen, tcr, tcr.new_tool); - } -#endif + // if (!gcodegen.is_BBL_Printer()) { + // for (const WipeTower::ToolChangeResult &tcr : m_priming) { + // if (!tcr.extrusions.empty()) + // gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + // } + // } return gcode; } @@ -756,12 +756,12 @@ static std::vector get_path_of_change_filament(const Print& print) std::string WipeTowerIntegration::finalize(GCode& gcodegen) { std::string gcode; - // BBS -#if 0 - if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) - gcode += gcodegen.change_layer(m_final_purge.print_z); - gcode += append_tcr(gcodegen, m_final_purge, -1); -#endif + // if (!gcodegen.is_BBL_Printer()) { + // if (std::abs(gcodegen.writer().get_position()(2) - 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; } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index dec8887019..2c2d38ba94 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -93,6 +93,7 @@ public: } return e_length; } + bool force_travel = false; }; struct box_coordinates @@ -165,6 +166,9 @@ public: } } + void set_wipe_volume(std::vector>& wiping_matrix) { + wipe_volumes = wiping_matrix; + } // Switch to a next layer. void set_layer( @@ -338,8 +342,8 @@ private: // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; size_t m_current_tool = 0; - // BBS - //const std::vector> wipe_volumes; + // Orca: support mmu wipe tower + std::vector> wipe_volumes; const float m_wipe_volume; float m_depth_traversed = 0.f; // Current y position at the wipe tower. diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp new file mode 100644 index 0000000000..443d2f6699 --- /dev/null +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -0,0 +1,1634 @@ +///|/ Copyright (c) Prusa Research 2017 - 2023 Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966 +///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill +///|/ Copyright (c) 2020 Paul Arden @ardenpm +///|/ Copyright (c) 2019 Thomas Moore +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "WipeTower2.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "ClipperUtils.hpp" +#include "GCodeProcessor.hpp" +#include "BoundingBox.hpp" +#include "LocalesUtils.hpp" +#include "Geometry.hpp" +#include "PrintConfig.hpp" +#include "Surface.hpp" +#include "Fill/FillRectilinear.hpp" + +#include + + +namespace Slic3r +{ + +class WipeTowerWriter2 +{ +public: + WipeTowerWriter2(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : + m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), + m_current_z(0.f), + m_current_feedrate(0.f), + m_layer_height(layer_height), + m_extrusion_flow(0.f), + m_preview_suppressed(false), + m_elapsed_time(0.f), +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_default_analyzer_line_width(line_width), +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_gcode_flavor(flavor), + m_filpar(filament_parameters) + { + // adds tag for analyzer: + std::ostringstream str; + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << ExtrusionEntity::role_to_string(erWipeTower) << "\n"; + m_gcode += str.str(); + change_analyzer_line_width(line_width); + } + + WipeTowerWriter2& change_analyzer_line_width(float line_width) { + // adds tag for analyzer: + std::stringstream str; + str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) << line_width << "\n"; + m_gcode += str.str(); + return *this; + } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter2& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for processor: + std::stringstream str; + str << ";" << GCodeProcessor::Mm3_Per_Mm_Tag << mm3_per_mm << "\n"; + m_gcode += str.str(); + return *this; + } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + WipeTowerWriter2& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + m_wipe_tower_width = width; + m_wipe_tower_depth = depth; + m_internal_angle = internal_angle; + m_start_pos = this->rotate(pos); + m_current_pos = pos; + return *this; + } + + WipeTowerWriter2& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; } + + WipeTowerWriter2& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; } + + WipeTowerWriter2& set_z(float z) + { m_current_z = z; return *this; } + + WipeTowerWriter2& set_extrusion_flow(float flow) + { m_extrusion_flow = flow; return *this; } + + WipeTowerWriter2& set_y_shift(float shift) { + m_current_pos.y() -= shift-m_y_shift; + m_y_shift = shift; + return (*this); + } + + WipeTowerWriter2& disable_linear_advance() { + if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware) + m_gcode += (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n"); + else if (m_gcode_flavor == gcfKlipper) + m_gcode += "SET_PRESSURE_ADVANCE ADVANCE=0\n"; + else + m_gcode += "M900 K0\n"; + 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. +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter2& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } + WipeTowerWriter2& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } +#else + WipeTowerWriter2& suppress_preview() { m_preview_suppressed = true; return *this; } + WipeTowerWriter2& resume_preview() { m_preview_suppressed = false; return *this; } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + WipeTowerWriter2& feedrate(float f) + { + if (f != m_current_feedrate) { + m_gcode += "G1" + set_format_F(f) + "\n"; + m_current_feedrate = f; + } + return *this; + } + + 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 Vec2f& pos() const { return m_current_pos; } + const Vec2f start_pos_rotated() const { return m_start_pos; } + const Vec2f pos_rotated() const { return this->rotate(m_current_pos); } + float elapsed_time() const { return m_elapsed_time; } + float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } + + // Extrude with an explicitely provided amount of extrusion. + WipeTowerWriter2& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { + 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(); + float len = std::sqrt(dx*dx+dy*dy); + if (record_length) + m_used_filament_length += e; + + // Now do the "internal rotation" with respect to the wipe tower center + Vec2f rotated_current_pos(this->pos_rotated()); + Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go + + if (! m_preview_suppressed && e > 0.f && len > 0.f) { +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + change_analyzer_mm3_per_mm(len, e); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + // 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 = e * m_filpar[0].filament_area / (len * m_layer_height); + // Correct for the roundings of a squished extrusion. + 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(rot, width, m_current_tool)); + } + + m_gcode += "G1"; + if (std::abs(rot.x() - rotated_current_pos.x()) > (float)EPSILON) + m_gcode += set_format_X(rot.x()); + + if (std::abs(rot.y() - rotated_current_pos.y()) > (float)EPSILON) + m_gcode += set_format_Y(rot.y()); + + + if (e != 0.f) + m_gcode += set_format_E(e); + + if (f != 0.f && f != m_current_feedrate) { + if (limit_volumetric_flow) { + float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); + f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + } + m_gcode += set_format_F(f); + } + + // Append newline if at least one of X,Y,E,F was changed. + // Otherwise, remove the "G1". + if (! boost::ends_with(m_gcode, "G1")) + m_gcode += "\n"; + else + m_gcode.erase(m_gcode.end()-2, m_gcode.end()); + + m_current_pos.x() = x; + m_current_pos.y() = y; + + // Update the elapsed time with a rough estimate. + m_elapsed_time += ((len == 0.f) ? std::abs(e) : len) / m_current_feedrate * 60.f; + return *this; + } + + WipeTowerWriter2& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } + + // Travel to a new XY position. f=0 means use the current value. + WipeTowerWriter2& travel(float x, float y, float f = 0.f) + { return extrude_explicit(x, y, 0.f, f); } + + WipeTowerWriter2& travel(const Vec2f &dest, float f = 0.f) + { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } + + // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. + WipeTowerWriter2& extrude(float x, float y, float f = 0.f) + { + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); + return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); + } + + WipeTowerWriter2& extrude(const Vec2f &dest, const float f = 0.f) + { return extrude(dest.x(), dest.y(), f); } + + WipeTowerWriter2& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) + { + Vec2f corners[4]; + corners[0] = ld; + corners[1] = ld + Vec2f(width,0.f); + corners[2] = ld + Vec2f(width,height); + corners[3] = ld + Vec2f(0.f,height); + int index_of_closest = 0; + if (x()-ld.x() > ld.x()+width-x()) // closer to the right + index_of_closest = 1; + if (y()-ld.y() > ld.y()+height-y()) // closer to the top + index_of_closest = (index_of_closest==0 ? 3 : 2); + + travel(corners[index_of_closest].x(), y()); // travel to the closest corner + travel(x(),corners[index_of_closest].y()); + + int i = index_of_closest; + do { + ++i; + if (i==4) i=0; + extrude(corners[i], f); + } while (i != index_of_closest); + return (*this); + } + + WipeTowerWriter2& rectangle(const WipeTower::box_coordinates& box, const float f = 0.f) + { + rectangle(Vec2f(box.ld.x(), box.ld.y()), + box.ru.x() - box.lu.x(), + box.ru.y() - box.rd.y(), f); + return (*this); + } + + WipeTowerWriter2& load(float e, float f = 0.f) + { + if (e == 0.f && (f == 0.f || f == m_current_feedrate)) + return *this; + m_gcode += "G1"; + if (e != 0.f) + m_gcode += set_format_E(e); + if (f != 0.f && f != m_current_feedrate) + m_gcode += set_format_F(f); + m_gcode += "\n"; + return *this; + } + + WipeTowerWriter2& 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) + WipeTowerWriter2& 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); // time that the move must take + float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_speed = x_distance / time; // x-speed to do it in that time + + if (x_speed > max_x_speed) { + // Necessary x_speed is too high - we must shorten the distance to achieve max_x_speed and still respect the time. + x_distance = max_x_speed * time; + x_speed = max_x_speed; + } + + float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; + return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); + } + + // Elevate the extruder head above the current print_z position. + WipeTowerWriter2& z_hop(float hop, float f = 0.f) + { + m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); + if (f != 0 && f != m_current_feedrate) + m_gcode += set_format_F(f); + m_gcode += "\n"; + return *this; + } + + // Lower the extruder head back to the current print_z position. + WipeTowerWriter2& z_hop_reset(float f = 0.f) + { return z_hop(0, f); } + + // Move to x1, +y_increment, + // extrude quickly amount e to x2 with feed f. + WipeTowerWriter2& ram(float x1, float x2, float dy, float e0, float e, float f) + { + extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, false); + extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, false); + return *this; + } + + // Let the end of the pulled out filament cool down in the cooling tube + // by moving up and down and moving the print head left / right + // at the current Y position to spread the leaking material. + WipeTowerWriter2& cool(float x1, float x2, float e1, float e2, float f) + { + extrude_explicit(x1, m_current_pos.y(), e1, f, false, false); + extrude_explicit(x2, m_current_pos.y(), e2, false, false); + return *this; + } + + WipeTowerWriter2& set_tool(size_t tool) + { + m_current_tool = tool; + return *this; + } + + // Set extruder temperature, don't wait by default. + WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false) + { + m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n"; + return *this; + } + + // Wait for a period of time (seconds). + WipeTowerWriter2& wait(float time) + { + if (time==0.f) + return *this; + m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n"; + return *this; + } + + // Set speed factor override percentage. + WipeTowerWriter2& speed_override(int speed) + { + m_gcode += "M220 S" + std::to_string(speed) + "\n"; + return *this; + } + + // Let the firmware back up the active speed override value. + WipeTowerWriter2& speed_override_backup() + { + // This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114) + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) + m_gcode += "M220 B\n"; + return *this; + } + + // Let the firmware restore the active speed override value. + WipeTowerWriter2& speed_override_restore() + { + if (m_gcode_flavor == gcfMarlinLegacy || m_gcode_flavor == gcfMarlinFirmware) + m_gcode += "M220 R\n"; + return *this; + } + + // Set digital trimpot motor + WipeTowerWriter2& set_extruder_trimpot(int current) + { + if (m_gcode_flavor == gcfKlipper) + return *this; + if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware) + m_gcode += "M906 E"; + else + m_gcode += "M907 E"; + m_gcode += std::to_string(current) + "\n"; + return *this; + } + + WipeTowerWriter2& flush_planner_queue() + { + m_gcode += "G4 S0\n"; + return *this; + } + + // Reset internal extruder counter. + WipeTowerWriter2& reset_extruder() + { + m_gcode += "G92 E0\n"; + return *this; + } + + WipeTowerWriter2& comment_with_value(const char *comment, int value) + { + m_gcode += std::string(";") + comment + std::to_string(value) + "\n"; + return *this; + } + + + WipeTowerWriter2& set_fan(unsigned speed) + { + if (speed == m_last_fan_speed) + return *this; + if (speed == 0) + m_gcode += "M107\n"; + else + m_gcode += "M106 S" + std::to_string(unsigned(255.0 * speed / 100.0)) + "\n"; + m_last_fan_speed = speed; + return *this; + } + + WipeTowerWriter2& append(const std::string& text) { m_gcode += text; return *this; } + + const std::vector& wipe_path() const + { + return m_wipe_path; + } + + WipeTowerWriter2& add_wipe_point(const Vec2f& pt) + { + m_wipe_path.push_back(rotate(pt)); + return *this; + } + + WipeTowerWriter2& add_wipe_point(float x, float y) + { + return add_wipe_point(Vec2f(x, y)); + } + +private: + Vec2f m_start_pos; + Vec2f m_current_pos; + std::vector m_wipe_path; + float m_current_z; + float m_current_feedrate; + size_t 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; + float m_internal_angle = 0.f; + float m_y_shift = 0.f; + float m_wipe_tower_width = 0.f; + float m_wipe_tower_depth = 0.f; + unsigned m_last_fan_speed = 0; + int current_temp = -1; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + const float m_default_analyzer_line_width; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + float m_used_filament_length = 0.f; + GCodeFlavor m_gcode_flavor; + const std::vector& m_filpar; + + std::string set_format_X(float x) + { + m_current_pos.x() = x; + return " X" + Slic3r::float_to_string_decimal_point(x, 3); + } + + std::string set_format_Y(float y) { + m_current_pos.y() = y; + return " Y" + Slic3r::float_to_string_decimal_point(y, 3); + } + + std::string set_format_Z(float z) { + return " Z" + Slic3r::float_to_string_decimal_point(z, 3); + } + + std::string set_format_E(float e) { + return " E" + Slic3r::float_to_string_decimal_point(e, 4); + } + + std::string set_format_F(float f) { + char buf[64]; + sprintf(buf, " F%d", int(floor(f + 0.5f))); + m_current_feedrate = f; + return buf; + } + + WipeTowerWriter2& operator=(const WipeTowerWriter2 &rhs); + + // Rotate the point around center of the wipe tower about given angle (in degrees) + Vec2f rotate(Vec2f pt) const + { + pt.x() -= m_wipe_tower_width / 2.f; + pt.y() += m_y_shift - m_wipe_tower_depth / 2.f; + double angle = m_internal_angle * float(M_PI/180.); + double c = cos(angle); + double s = sin(angle); + return Vec2f(float(pt.x() * c - pt.y() * s) + m_wipe_tower_width / 2.f, float(pt.x() * s + pt.y() * c) + m_wipe_tower_depth / 2.f); + } + +}; // class WipeTowerWriter2 + + + +WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, + bool priming, + size_t old_tool) const +{ + WipeTower::ToolChangeResult result; + result.priming = priming; + result.initial_tool = int(old_tool); + result.new_tool = int(m_current_tool); + result.print_z = m_z_pos; + result.layer_height = m_layer_height; + result.elapsed_time = writer.elapsed_time(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = priming ? writer.pos() : writer.pos_rotated(); + result.gcode = std::move(writer.gcode()); + result.extrusions = std::move(writer.extrusions()); + result.wipe_path = std::move(writer.wipe_path()); + return result; +} + + + +WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector>& wiping_matrix, size_t initial_tool) : + m_semm(config.single_extruder_multi_material.value), + m_wipe_tower_pos(config.wipe_tower_x.get_at(plate_idx), config.wipe_tower_y.get_at(plate_idx)), + m_wipe_tower_width(float(config.prime_tower_width)), + m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)), + m_wipe_tower_brim_width(float(config.prime_tower_brim_width)), + m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)), + m_extra_spacing(float(config.wipe_tower_extra_spacing/100.)), + m_y_shift(0.f), + m_z_pos(0.f), + m_bridging(float(config.wipe_tower_bridging)), + m_no_sparse_layers(config.wipe_tower_no_sparse_layers), + m_gcode_flavor(config.gcode_flavor), + m_travel_speed(config.travel_speed), + m_infill_speed(default_region_config.sparse_infill_speed), + m_perimeter_speed(default_region_config.inner_wall_speed), + m_current_tool(initial_tool), + wipe_volumes(wiping_matrix) +{ + // Read absolute value of first layer speed, if given as percentage, + // it is taken over following default. Speeds from config are not + // easily accessible here. + const float default_speed = 60.f; + m_first_layer_speed = config.initial_layer_speed; + if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it. + m_first_layer_speed = default_speed / 2.f; + + // Autospeed may be used... + if (m_infill_speed == 0.f) + m_infill_speed = 80.f; + if (m_perimeter_speed == 0.f) + m_perimeter_speed = 80.f; + + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_cooling_tube_retraction = float(config.cooling_tube_retraction); + m_cooling_tube_length = float(config.cooling_tube_length); + m_parking_pos_retraction = float(config.parking_pos_retraction); + m_extra_loading_move = float(config.extra_loading_move); + m_set_extruder_trimpot = config.high_current_on_filament_swap; + } + + // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. + const std::vector& bed_points = config.printable_area.values; + BoundingBoxf bb(bed_points); + m_bed_width = float(bb.size().x()); + m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed); + + if (m_bed_shape == CircularBed) { + // this may still be a custom bed, check that the points are roughly on a circle + double r2 = std::pow(m_bed_width/2., 2.); + double lim2 = std::pow(m_bed_width/10., 2.); + Vec2d center = bb.center(); + for (const Vec2d& pt : bed_points) + if (std::abs(std::pow(pt.x()-center.x(), 2.) + std::pow(pt.y()-center.y(), 2.) - r2) > lim2) { + m_bed_shape = CustomBed; + break; + } + } + + m_bed_bottom_left = m_bed_shape == RectangularBed + ? Vec2f(bed_points.front().x(), bed_points.front().y()) + : Vec2f::Zero(); +} + + + +void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) +{ + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = config.filament_type.get_at(idx); + m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx); + m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx); + m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx); + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_filpar[idx].loading_speed = float(config.filament_loading_speed.get_at(idx)); + m_filpar[idx].loading_speed_start = float(config.filament_loading_speed_start.get_at(idx)); + m_filpar[idx].unloading_speed = float(config.filament_unloading_speed.get_at(idx)); + m_filpar[idx].unloading_speed_start = float(config.filament_unloading_speed_start.get_at(idx)); + m_filpar[idx].delay = float(config.filament_toolchange_delay.get_at(idx)); + m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); + m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx)); + m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx)); + } + + m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point + float nozzle_diameter = float(config.nozzle_diameter.get_at(idx)); + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx)); + if (max_vol_speed!= 0.f) + m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + if (m_semm) { + std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); + // ramming_speed now contains speeds to be used for every 0.25s piece of the ramming line. + // This allows to have the ramming flow variable. The 0.25s value is how it is saved in config + // and the same time step has to be used when the ramming is performed. + } else { + // We will use the same variables internally, but the correspondence to the configuration options will be different. + float vol = config.filament_multitool_ramming_volume.get_at(idx); + float flow = config.filament_multitool_ramming_flow.get_at(idx); + m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx); + m_filpar[idx].ramming_line_width_multiplicator = 2.; + m_filpar[idx].ramming_step_multiplicator = 1.; + + // Now the ramming speed vector. In this case it contains just one value (flow). + // The time is calculated and saved separately. This is here so that the MM ramming + // is not limited by the 0.25s granularity - it is not possible to create a SEMM-style + // ramming_speed vector that would respect both the volume and flow (because of + // rounding issues with small volumes and high flow). + m_filpar[idx].ramming_speed.push_back(flow); + m_filpar[idx].multitool_ramming_time = vol/flow; + } + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later +} + + + +// Returns gcode to prime the nozzles at the front edge of the print bed. +std::vector WipeTower2::prime( + // print_z of the first layer. + float initial_layer_print_height, + // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. + const std::vector &tools, + // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. + // 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(initial_layer_print_height, initial_layer_print_height, tools.size(), true, false); + m_current_tool = tools.front(); + + // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210]. + // Due to the XYZ calibration, this working space may shrink slightly from all directions, + // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. +// WipeTower::box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); + + float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); + WipeTower::box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + if (m_bed_shape == CircularBed) { + cleaning_box = WipeTower::box_coordinates(Vec2f(0.f, 0.f), prime_section_width, 100.f); + float total_width_half = tools.size() * prime_section_width / 2.f; + cleaning_box.translate(-total_width_half, -std::sqrt(std::max(0.f, std::pow(m_bed_width/2, 2.f) - std::pow(1.05f * total_width_half, 2.f)))); + } + else + cleaning_box.translate(m_bed_bottom_left); + + std::vector results; + + // Iterate over all priming toolchanges and push respective ToolChangeResults into results vector. + for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + size_t old_tool = m_current_tool; + + WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool); + + // This is the first toolchange - initiate priming + if (idx_tool == 0) { + writer.append(";--------------------\n" + "; CP PRIMING START\n") + .append(";--------------------\n") + .speed_override_backup() + .speed_override(100) + .set_initial_position(Vec2f::Zero()) // Always move to the starting position + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + } + else + writer.set_initial_position(results.back().end_pos); + + + unsigned int tool = tools[idx_tool]; + m_left_to_right = true; + toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. + toolchange_Load(writer, cleaning_box); // Prime the tool. + if (idx_tool + 1 == tools.size()) { + // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. + toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]); + } else { + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); + toolchange_Wipe(writer, cleaning_box , 20.f); + WipeTower::box_coordinates box = cleaning_box; + box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); + toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); + cleaning_box.translate(prime_section_width, 0.f); + writer.travel(cleaning_box.ld, 7200); + } + ++ m_num_tool_changes; + + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + // This is the last priming toolchange - finish priming + if (idx_tool+1 == tools.size()) { + // Reset the extruder current to a normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + writer.speed_override_restore() + .feedrate(m_travel_speed * 60.f) + .flush_planner_queue() + .reset_extruder() + .append("; CP PRIMING END\n" + ";------------------\n" + "\n\n"); + } + + results.emplace_back(construct_tcr(writer, true, old_tool)); + } + + m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear + // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) + + return results; +} + +WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) +{ + size_t old_tool = m_current_tool; + + float wipe_area = 0.f; + float wipe_volume = 0.f; + + // Finds this toolchange info + if (tool != (unsigned int)(-1)) + { + for (const auto &b : m_layer_info->tool_changes) + if ( b.new_tool == tool ) { + wipe_volume = b.wipe_volume; + wipe_area = b.required_depth * m_layer_info->extra_spacing; + break; + } + } + else { + // Otherwise we are going to Unload only. And m_layer_info would be invalid. + } + + WipeTower::box_coordinates cleaning_box( + Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + m_wipe_tower_width - m_perimeter_width, + (tool != (unsigned int)(-1) ? wipe_area+m_depth_traversed-0.5f*m_perimeter_width + : m_wipe_tower_depth-m_perimeter_width)); + + WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) + .append(";--------------------\n" + "; CP TOOLCHANGE START\n") + .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + + if (tool != (unsigned)(-1)) + writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) + .append(";--------------------\n"); + + writer.speed_override_backup(); + writer.speed_override(100); + + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + // Increase the extruder driver current to allow fast ramming. + if (m_set_extruder_trimpot) + 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. + if (tool != (unsigned int)-1){ // This is not the last change. + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, + is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. + toolchange_Load(writer, cleaning_box); + writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road + toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + ++ m_num_tool_changes; + } else + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); + + m_depth_traversed += wipe_area; + + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. + writer.speed_override_restore(); + writer.feedrate(m_travel_speed * 60.f) + .flush_planner_queue() + .reset_extruder() + .append("; CP TOOLCHANGE END\n" + ";------------------\n" + "\n\n"); + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + return construct_tcr(writer, false, old_tool); +} + + +// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. +void WipeTower2::toolchange_Unload( + WipeTowerWriter2 &writer, + const WipeTower::box_coordinates &cleaning_box, + const std::string& current_material, + const int new_temperature) +{ + float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; + + const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness + const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm + + const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f); + + writer.append("; CP TOOLCHANGE UNLOAD\n") + .change_analyzer_line_width(line_width); + + unsigned i = 0; // iterates through ramming_speed + m_left_to_right = true; // current direction of ramming + float remaining = xr - xl ; // keeps track of distance to the next turnaround + float e_done = 0; // measures E move done from each segment + + const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming; + + if (do_ramming) { + writer.travel(ramming_start_pos); // move to starting position + writer.disable_linear_advance(); + } + else + writer.set_position(ramming_start_pos); + + // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: + if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { + + // this is y of the center of previous sparse infill border + float sparse_beginning_y = 0.f; + if (m_current_shape == SHAPE_REVERSED) + sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth()) + - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ; + else + sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width; + + float sum_of_depths = 0.f; + for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange + if (tch.old_tool == m_current_tool) { + sum_of_depths += tch.ramming_depth; + float ramming_end_y = sum_of_depths; + ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line + + if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || + (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) + { + writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y()); + remaining -= tch.first_wipe_line-1.f*m_perimeter_width; + } + break; + } + sum_of_depths += tch.required_depth; + } + } + + + // now the ramming itself: + while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) + { + // The time step is different for SEMM ramming and the MM ramming. See comments in set_extruder() for details. + const float time_step = m_semm ? 0.25f : m_filpar[m_current_tool].multitool_ramming_time; + + const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * time_step, line_width, m_layer_height); + const float e = m_filpar[m_current_tool].ramming_speed[i] * time_step / filament_area(); // transform volume per sec to E move; + const float dist = std::min(x - e_done, remaining); // distance to travel for either the next time_step, or to the next turnaround + const float actual_time = dist/x * time_step; + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0.f, 0.f, e * (dist / x), dist / (actual_time / 60.f)); + remaining -= dist; + + if (remaining < WT_EPSILON) { // we reached a turning point + writer.travel(writer.x(), writer.y() + y_step, 7200); + m_left_to_right = !m_left_to_right; + remaining = xr - xl; + } + e_done += dist; // subtract what was actually done + if (e_done > x - WT_EPSILON) { // current segment finished + ++i; + e_done = 0; + } + } + Vec2f 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 + + // Retraction: + float old_x = writer.x(); + float turning_point = (!m_left_to_right ? xl : xr ); + if (m_semm && (m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0)) { + 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() + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + .resume_preview(); + } + // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should + // be already set and there is no need to change anything. Also, the temperature could be changed + // for wrong extruder. + if (m_semm) { + if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait. + // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) + // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). + writer.set_extruder_temp(new_temperature, false); + m_old_temperature = new_temperature; + } + } + + // Cooling: + const int& number_of_moves = m_filpar[m_current_tool].cooling_moves; + if (m_semm && 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; + + float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f); + + 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 cleaning_box.lu.y()-0.5f*m_perimeter_width) + break; // in case next line would not fit + + traversed_x -= writer.x(); + x_to_wipe -= std::abs(traversed_x); + if (x_to_wipe < WT_EPSILON) { + writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200); + break; + } + // stepping to the next line: + writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*m_perimeter_width, writer.y() + dy); + m_left_to_right = !m_left_to_right; + } + + // We may be going back to the model - wipe the nozzle. If this is followed + // by finish_layer, this wipe path will be overwritten. + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x(), writer.y() - dy) + .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); + + if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) + m_left_to_right = !m_left_to_right; + + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. +} + + + + +WipeTower::ToolChangeResult WipeTower2::finish_layer() +{ + assert(! this->layer_finished()); + m_current_layer_finished = true; + + size_t old_tool = m_current_tool; + + WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); + + + // Slow down on the 1st layer. + bool first_layer = is_first_layer(); + float feedrate = first_layer ? m_first_layer_speed * 60.f : m_infill_speed * 60.f; + float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); + WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); + + + writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel + m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); + + bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON; + WipeTower::box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), + m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + + // inner perimeter of the sparse section, if there is space for it: + if (fill_box.ru.y() - fill_box.rd.y() > m_perimeter_width - WT_EPSILON) + writer.rectangle(fill_box.ld, fill_box.rd.x()-fill_box.ld.x(), fill_box.ru.y()-fill_box.rd.y(), feedrate); + + // we are in one of the corners, travel to ld along the perimeter: + if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); + if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); + + // Extrude infill to support the material to be printed above. + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + float left = fill_box.lu.x() + 2*m_perimeter_width; + float right = fill_box.ru.x() - 2 * m_perimeter_width; + if (dy > m_perimeter_width) + { + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); + + // Is there a soluble filament wiped/rammed at the next layer? + // If so, the infill should not be sparse. + bool solid_infill = m_layer_info+1 == m_plan.end() + ? false + : std::any_of((m_layer_info+1)->tool_changes.begin(), + (m_layer_info+1)->tool_changes.end(), + [this](const WipeTowerInfo::ToolChange& tch) { + return m_filpar[tch.new_tool].is_soluble + || m_filpar[tch.old_tool].is_soluble; + }); + solid_infill |= first_layer && m_adhesion; + + if (solid_infill) { + float sparse_factor = 1.5f; // 1=solid, 2=every other line, etc. + if (first_layer) { // the infill should touch perimeters + left -= m_perimeter_width; + right += m_perimeter_width; + sparse_factor = 1.f; + } + float y = fill_box.ld.y() + m_perimeter_width; + int n = dy / (m_perimeter_width * sparse_factor); + float spacing = (dy-m_perimeter_width)/(n-1); + int i=0; + for (i=0; i Polygon { + const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth, m_wipe_tower_cone_angle); + + double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z; // the former should actually work in both cases, but let's stay on the safe side (the 2.6.0 is close) + + double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle/2.f)) * (m_wipe_tower_height - z); + Vec2f center = (wt_box.lu + wt_box.rd) / 2.; + double w = wt_box.lu.y() - wt_box.ld.y(); + enum Type { + Arc, + Corner, + ArcStart, + ArcEnd + }; + + // First generate vector of annotated point which form the boundary. + std::vector> pts = {{wt_box.ru, Corner}}; + if (double alpha_start = std::asin((0.5*w)/r); ! std::isnan(alpha_start) && r > 0.5*w+0.01) { + for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 40.) + pts.emplace_back(Vec2f(center.x() + r*std::cos(alpha)/support_scale, center.y() + r*std::sin(alpha)), alpha == alpha_start ? ArcStart : Arc); + pts.back().second = ArcEnd; + } + pts.emplace_back(wt_box.lu, Corner); + pts.emplace_back(wt_box.ld, Corner); + for (int i=int(pts.size())-3; i>0; --i) + pts.emplace_back(Vec2f(pts[i].first.x(), 2*center.y()-pts[i].first.y()), i == int(pts.size())-3 ? ArcStart : i == 1 ? ArcEnd : Arc); + pts.emplace_back(wt_box.rd, Corner); + + // Create a Polygon from the points. + Polygon poly; + for (const auto& [pt, tag] : pts) + poly.points.push_back(Point::new_scale(pt)); + + // Prepare polygons to be filled by infill. + Polylines polylines; + if (infill_cone && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing) { + ExPolygons infill_areas; + ExPolygon wt_contour(poly); + Polygon wt_rectangle(Points{Point::new_scale(wt_box.ld), Point::new_scale(wt_box.rd), Point::new_scale(wt_box.ru), Point::new_scale(wt_box.lu)}); + wt_rectangle = offset(wt_rectangle, scale_(-spacing/2.)).front(); + wt_contour = offset_ex(wt_contour, scale_(-spacing/2.)).front(); + infill_areas = diff_ex(wt_contour, wt_rectangle); + if (infill_areas.size() == 2) { + ExPolygon& bottom_expoly = infill_areas.front().contour.points.front().y() < infill_areas.back().contour.points.front().y() ? infill_areas[0] : infill_areas[1]; + std::unique_ptr filler(Fill::new_from_type(ipMonotonicLine)); + filler->angle = Geometry::deg2rad(45.f); + filler->spacing = spacing; + FillParams params; + params.density = 1.f; + Surface surface(stBottom, bottom_expoly); + filler->bounding_box = get_extents(bottom_expoly); + polylines = filler->fill_surface(&surface, params); + if (! polylines.empty()) { + if (polylines.front().points.front().x() > polylines.back().points.back().x()) { + std::reverse(polylines.begin(), polylines.end()); + for (Polyline& p : polylines) + p.reverse(); + } + } + } + } + + // Find the closest corner and travel to it. + int start_i = 0; + double min_dist = std::numeric_limits::max(); + for (int i=0; i() - center)); + for (size_t i=0; i() - center)); + } + writer.travel(pts[i].first); + } + } + if (++i == int(pts.size())) + i = 0; + } + writer.extrude(pts[start_i].first, feedrate); + return poly; + }; + + feedrate = first_layer ? m_first_layer_speed * 60.f : m_perimeter_speed * 60.f; + + // outer contour (always) + bool infill_cone = first_layer && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing; + Polygon poly = supported_rectangle(wt_box, feedrate, infill_cone); + + + // brim (first layer only) + if (first_layer) { + size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; + + for (size_t i = 0; i < loops_num; ++ i) { + poly = offset(poly, scale_(spacing)).front(); + int cp = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); + writer.travel(unscale(poly.points[cp]).cast()); + for (int i=cp+1; true; ++i ) { + if (i==int(poly.points.size())) + i = 0; + writer.extrude(unscale(poly.points[i]).cast()); + if (i == cp) + break; + } + } + + // Save actual brim width to be later passed to the Print object, which will use it + // for skirt calculation and pass it to GLCanvas for precise preview box + m_wipe_tower_brim_width_real = loops_num * spacing; + } + + // Now prepare future wipe. + int i = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); + writer.add_wipe_point(writer.pos()); + writer.add_wipe_point(unscale(poly.points[i==0 ? int(poly.points.size())-1 : i-1]).cast()); + + // Ask our writer about how much material was consumed. + // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. + if (! m_no_sparse_layers || toolchanges_on_layer || first_layer) { + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + m_current_height += m_layer_info->height; + } + + return construct_tcr(writer, false, old_tool); +} + +// Static method to get the radius and x-scaling of the stabilizing cone base. +std::pair WipeTower2::get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg) +{ + double R = std::tan(Geometry::deg2rad(angle_deg/2.)) * height; + double fake_width = 0.66 * width; + double diag = std::hypot(fake_width / 2., depth / 2.); + double support_scale = 1.; + if (R > diag) { + double w = fake_width; + double sin = 0.5 * depth / diag; + double tan = depth / w; + double t = (R - diag) * sin; + support_scale = (w / 2. + t / tan + t * tan) / (w / 2.); + } + return std::make_pair(R, support_scale); +} + +// Static method to extract wipe_volumes[from][to] from the configuration. +std::vector> WipeTower2::extract_wipe_volumes(const PrintConfig& config) +{ + // Get wiping matrix to get number of extruders and convert vector to vector: + std::vector wiping_matrix(cast(config.flush_volumes_matrix.values)); + + // The values shall only be used when SEMM is enabled. The purging for other printers + // is determined by filament_minimal_purge_on_wipe_tower. + if (! config.single_extruder_multi_material.value) + std::fill(wiping_matrix.begin(), wiping_matrix.end(), 0.f); + + // Extract purging volumes for each extruder pair: + std::vector> wipe_volumes; + const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON); + for (unsigned int i = 0; i(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders)); + + // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. + for (unsigned int i = 0; i(wipe_volumes[i][j], config.filament_minimal_purge_on_wipe_tower.get_at(j)); + + return wipe_volumes; +} + +// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box +void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, + unsigned int new_tool, float wipe_volume) +{ + assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one + + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first + m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + + if (m_first_layer_idx == size_t(-1) && (! m_no_sparse_layers || old_tool != new_tool || m_plan.size() == 1)) + m_first_layer_idx = m_plan.size() - 1; + + if (old_tool == new_tool) // new layer without toolchanges - we are done + return; + + // this is an actual toolchange - let's calculate depth to reserve on the wipe tower + float depth = 0.f; + float width = m_wipe_tower_width - 3*m_perimeter_width; + float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f), + m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, + layer_height_par); + depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator); + 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_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, wipe_volume)); +} + + + +void WipeTower2::plan_tower() +{ + // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards + m_wipe_tower_depth = 0.f; + for (auto& layer : m_plan) + layer.depth = 0.f; + m_wipe_tower_height = m_plan.empty() ? 0.f : m_plan.back().z; + m_current_height = 0.f; + + for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) + { + float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); + m_plan[layer_index].depth = this_layer_depth; + + if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) + m_wipe_tower_depth = this_layer_depth + m_perimeter_width; + + for (int i = layer_index - 1; i >= 0 ; i--) + { + if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width ) + m_plan[i].depth = this_layer_depth; + } + } +} + +void WipeTower2::save_on_last_wipe() +{ + for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); + if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer + continue; + + // Which toolchange will finish_layer extrusions be subtracted from? + int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes); + + for (int i=0; itool_changes.size()); ++i) { + auto& toolchange = m_layer_info->tool_changes[i]; + tool_change(toolchange.new_tool); + + if (i == idx) { + float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float length_to_save = finish_layer().total_extrusion_length_in_plane(); + float length_to_wipe = volume_to_length(toolchange.wipe_volume, + m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save; + + length_to_wipe = std::max(length_to_wipe,0.f); + float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; + + toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; + } + } + } +} + + +// Return index of first toolchange that switches to non-soluble extruder +// ot -1 if there is no such toolchange. +int WipeTower2::first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const +{ + for (size_t idx=0; idx> &result) +{ + if (m_plan.empty()) + return; + + plan_tower(); + for (int i=0;i<5;++i) { + save_on_last_wipe(); + plan_tower(); + } + + m_layer_info = m_plan.begin(); + m_current_height = 0.f; + + // we don't know which extruder to start with - we'll set it according to the first toolchange + for (const auto& layer : m_plan) { + if (!layer.tool_changes.empty()) { + m_current_tool = layer.tool_changes.front().old_tool; + break; + } + } + + for (auto& used : m_used_filament_length) // reset used filament stats + used = 0.f; + + m_old_temperature = -1; // reset last temperature written in the gcode + + std::vector layer_result; + for (const WipeTower2::WipeTowerInfo& layer : m_plan) + { + set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); + m_internal_rotation += 180.f; + + if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) + m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; + + int idx = first_toolchange_to_nonsoluble(layer.tool_changes); + WipeTower::ToolChangeResult finish_layer_tcr; + + if (idx == -1) { + // if there is no toolchange switching to non-soluble, finish layer + // will be called at the very beginning. That's the last possibility + // where a nonsoluble tool can be. + finish_layer_tcr = finish_layer(); + } + + for (int i=0; i> WipeTower2::get_z_and_depth_pairs() const +{ + std::vector> out = {{0.f, m_wipe_tower_depth}}; + for (const WipeTowerInfo& wti : m_plan) { + assert(wti.depth < wti.depth + WT_EPSILON); + if (wti.depth < out.back().second - WT_EPSILON) + out.emplace_back(wti.z, wti.depth); + } + if (out.back().first < m_wipe_tower_height - WT_EPSILON) + out.emplace_back(m_wipe_tower_height, 0.f); + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp new file mode 100644 index 0000000000..f7de97c444 --- /dev/null +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -0,0 +1,322 @@ +///|/ Copyright (c) Prusa Research 2017 - 2023 Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef WipeTower2_ +#define WipeTower2_ + +#include +#include +#include +#include +#include + +#include "libslic3r/Point.hpp" +#include "WipeTower.hpp" +namespace Slic3r +{ + +class WipeTowerWriter2; +class PrintRegionConfig; + +class WipeTower2 +{ +public: + static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; } + static std::pair get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg); + static std::vector> extract_wipe_volumes(const PrintConfig& config); + + + // Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2. + // WipeTowerWriter2 is moved from ! + WipeTower::ToolChangeResult construct_tcr(WipeTowerWriter2& writer, + bool priming, + size_t old_tool) const; + + // x -- x coordinates of wipe tower in mm ( left bottom corner ) + // 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 + WipeTower2(const PrintConfig& config, + const PrintRegionConfig& default_region_config, + int plate_idx, Vec3d plate_origin, + const std::vector>& wiping_matrix, + size_t initial_tool); + + + // Set the extruder properties. + void set_extruder(size_t idx, const PrintConfig& config); + + // 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, float wipe_volume = 0.f); + + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" + void generate(std::vector> &result); + + float get_depth() const { return m_wipe_tower_depth; } + std::vector> get_z_and_depth_pairs() const; + float get_brim_width() const { return m_wipe_tower_brim_width_real; } + float get_wipe_tower_height() const { return m_wipe_tower_height; } + + + + + + // Switch to a next layer. + void set_layer( + // Print height of this layer. + float print_z, + // Layer height, used to calculate extrusion the rate. + float layer_height, + // Maximum number of tool changes on this layer or the layers below. + size_t max_tool_changes, + // Is this the first layer of the print? In that case print the brim first. (OBSOLETE) + bool /*is_first_layer*/, + // Is this the last layer of the waste tower? + bool is_last_layer) + { + m_z_pos = print_z; + m_layer_height = layer_height; + m_depth_traversed = 0.f; + m_current_layer_finished = false; + + + // Advance m_layer_info iterator, making sure we got it right + while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) + ++m_layer_info; + + m_current_shape = (! this->is_first_layer() && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; + if (this->is_first_layer()) { + m_num_layer_changes = 0; + m_num_tool_changes = 0; + } else + ++ m_num_layer_changes; + + // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: + m_extrusion_flow = extrusion_flow(layer_height); + } + + // Return the wipe tower position. + const Vec2f& position() const { return m_wipe_tower_pos; } + // Return the wipe tower width. + float width() const { return m_wipe_tower_width; } + // The wipe tower is finished, there should be no more tool changes or wipe tower prints. + bool finished() const { return m_max_color_changes == 0; } + + // Returns gcode to prime the nozzles at the front edge of the print bed. + std::vector prime( + // print_z of the first layer. + float first_layer_height, + // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. + const std::vector &tools, + // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. + // If false, the last priming are will be large enough to wipe the last extruder sufficiently. + bool last_wipe_inside_wipe_tower); + + // Returns gcode for a toolchange and a final print head position. + // On the first layer, extrude a brim around the future wipe tower first. + WipeTower::ToolChangeResult tool_change(size_t new_tool); + + // Fill the unfilled space with a sparse infill. + // Call this method only if layer_finished() is false. + WipeTower::ToolChangeResult finish_layer(); + + // Is the current layer finished? + bool layer_finished() const { + return m_current_layer_finished; + } + + std::vector get_used_filament() const { return m_used_filament_length; } + int get_number_of_toolchanges() const { return m_num_tool_changes; } + + struct FilamentParameters { + std::string material = "PLA"; + bool is_soluble = false; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float loading_speed_start = 0.f; + float unloading_speed = 0.f; + float unloading_speed_start = 0.f; + float delay = 0.f ; + int cooling_moves = 0; + float cooling_initial_speed = 0.f; + float cooling_final_speed = 0.f; + float ramming_line_width_multiplicator = 1.f; + float ramming_step_multiplicator = 1.f; + float max_e_speed = std::numeric_limits::max(); + std::vector ramming_speed; + float nozzle_diameter; + float filament_area; + bool multitool_ramming; + float multitool_ramming_time = 0.f; + }; + +private: + enum wipe_shape // A fill-in direction + { + SHAPE_NORMAL = 1, + SHAPE_REVERSED = -1 + }; + + const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust + const float WT_EPSILON = 1e-3f; + float filament_area() const { + return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point + } + + + bool m_semm = true; // Are we using a single extruder multimaterial printer? + Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + float m_wipe_tower_width; // Width of the wipe tower. + float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_height = 0.f; + float m_wipe_tower_cone_angle = 0.f; + float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config + float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation + float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_internal_rotation = 0.f; + float m_y_shift = 0.f; // y shift passed to writer + float m_z_pos = 0.f; // Current Z position. + float m_layer_height = 0.f; // Current layer height. + size_t m_max_color_changes = 0; // Maximum number of color changes per layer. + 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) + float m_travel_speed = 0.f; + float m_infill_speed = 0.f; + float m_perimeter_speed = 0.f; + float m_first_layer_speed = 0.f; + size_t m_first_layer_idx = size_t(-1); + + // 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_no_sparse_layers = false; + bool m_set_extruder_trimpot = false; + bool m_adhesion = true; + GCodeFlavor m_gcode_flavor; + + // Bed properties + enum { + RectangularBed, + CircularBed, + CustomBed + } m_bed_shape; + float m_bed_width; // width of the bed bounding box + Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds) + + float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. + + // Extruder specific parameters. + std::vector m_filpar; + + // State of the wipe tower generator. + unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. + unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. + ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. + bool m_print_brim = true; + // A fill-in direction (positive Y, negative Y) alternates with each layer. + wipe_shape m_current_shape = SHAPE_NORMAL; + size_t m_current_tool = 0; + const std::vector> wipe_volumes; + + float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_current_layer_finished = false; + bool m_left_to_right = true; + float m_extra_spacing = 1.f; + + bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } + + // 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 + { + if ( layer_height < 0 ) + return m_extrusion_flow; + return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); + } + + // Calculates length of extrusion line to extrude given volume + float volume_to_length(float volume, float line_width, float layer_height) const { + return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); + } + + // Calculates depth for all layers and propagates them downwards + void plan_tower(); + + // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental + void make_wipe_tower_square(); + + // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe + void save_on_last_wipe(); + + + // to store information about tool changes for a given layer + struct WipeTowerInfo{ + struct ToolChange { + size_t old_tool; + size_t new_tool; + float required_depth; + float ramming_depth; + float first_wipe_line; + float wipe_volume; + ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f) + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {} + }; + float z; // z position of the layer + float height; // layer height + float depth; // depth of the layer based on all layers above + float extra_spacing; + float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } + + std::vector tool_changes; + + WipeTowerInfo(float z_par, float layer_height_par) + : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} + }; + + std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) + std::vector::iterator m_layer_info = m_plan.end(); + + // This sums height of all extruded layers, not counting the layers which + // will be later removed when the "no_sparse_layers" is used. + float m_current_height = 0.f; + + // Stores information about used filament length per extruder: + std::vector m_used_filament_length; + + // Return index of first toolchange that switches to non-soluble extruder + // ot -1 if there is no such toolchange. + int first_toolchange_to_nonsoluble( + const std::vector& tool_changes) const; + + void toolchange_Unload( + WipeTowerWriter2 &writer, + const WipeTower::box_coordinates &cleaning_box, + const std::string& current_material, + const int new_temperature); + + void toolchange_Change( + WipeTowerWriter2 &writer, + const size_t new_tool, + const std::string& new_material); + + void toolchange_Load( + WipeTowerWriter2 &writer, + const WipeTower::box_coordinates &cleaning_box); + + void toolchange_Wipe( + WipeTowerWriter2 &writer, + const WipeTower::box_coordinates &cleaning_box, + float wipe_volume); +}; + + + + +} // namespace Slic3r + +#endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f8ac1ab4cb..f9e3ac6303 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -765,7 +765,9 @@ static std::vector s_Preset_print_options { "sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim", "tree_support_brim_width", "gcode_comments", "gcode_label_objects", "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max", - "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes" ,"initial_layer_min_bead_width" + "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes", + "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder", "wiping_volumes_extruders","wipe_tower_bridging", "single_extruder_multi_material_priming", + "wipe_tower_rotation_angle" }; @@ -790,7 +792,11 @@ static std::vector s_Preset_filament_options { "filament_wipe_distance", "additional_cooling_fan_speed", "bed_temperature_difference", "nozzle_temperature_range_low", "nozzle_temperature_range_high", //SoftFever - "enable_pressure_advance", "pressure_advance","chamber_temperature", "filament_shrink", "support_material_interface_fan_speed", "filament_notes" /*,"filament_seam_gap"*/ + "enable_pressure_advance", "pressure_advance","chamber_temperature", "filament_shrink", "support_material_interface_fan_speed", "filament_notes" /*,"filament_seam_gap"*/, + "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", + "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", + "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", + "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", }; static std::vector s_Preset_machine_limits_options { @@ -817,7 +823,9 @@ static std::vector s_Preset_printer_options { "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", - "use_firmware_retraction", "use_relative_e_distances", "printer_notes"}; + "use_firmware_retraction", "use_relative_e_distances", "printer_notes", + "cooling_tube_retraction", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move"}; static std::vector s_Preset_sla_print_options { "layer_height", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2691856de2..aaf9180865 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -12,6 +12,7 @@ #include "Thread.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/WipeTower2.hpp" #include "Utils.hpp" #include "PrintConfig.hpp" #include "Model.hpp" @@ -172,7 +173,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "gcode_comments", "gcode_label_objects", "exclude_object", - "support_material_interface_fan_speed" + "support_material_interface_fan_speed", + "single_extruder_multi_material_priming" }; static std::unordered_set steps_ignore; @@ -216,10 +218,23 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "nozzle_temperature_initial_layer" || opt_key == "filament_minimal_purge_on_wipe_tower" || opt_key == "filament_max_volumetric_speed" + || opt_key == "filament_loading_speed" + || opt_key == "filament_loading_speed_start" + || opt_key == "filament_unloading_speed" + || opt_key == "filament_unloading_speed_start" + || opt_key == "filament_toolchange_delay" + || opt_key == "filament_cooling_moves" + || opt_key == "filament_minimal_purge_on_wipe_tower" + || opt_key == "filament_cooling_initial_speed" + || opt_key == "filament_cooling_final_speed" + || opt_key == "filament_ramming_parameters" + || opt_key == "filament_multitool_ramming" + || opt_key == "filament_multitool_ramming_volume" + || opt_key == "filament_multitool_ramming_flow" + || opt_key == "filament_max_volumetric_speed" || opt_key == "gcode_flavor" || opt_key == "single_extruder_multi_material" || opt_key == "nozzle_temperature" - // BBS || opt_key == "cool_plate_temp" || opt_key == "eng_plate_temp" || opt_key == "hot_plate_temp" @@ -228,7 +243,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "prime_tower_width" || opt_key == "prime_tower_brim_width" || opt_key == "first_layer_print_sequence" - //|| opt_key == "wipe_tower_bridging" + || opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "flush_volumes_matrix" || opt_key == "prime_volume" @@ -239,7 +254,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "travel_speed_z" || opt_key == "initial_layer_speed" || opt_key == "initial_layer_travel_speed" - || opt_key == "slow_down_layers") { + || opt_key == "slow_down_layers" + || opt_key == "wipe_tower_cone_angle" + || opt_key == "wipe_tower_extra_spacing" + || opt_key == "wipe_tower_extruder" + || opt_key == "wiping_volumes_extruders" + ) { //|| opt_key == "z_offset") { steps.emplace_back(psWipeTower); steps.emplace_back(psSkirtBrim); @@ -2161,16 +2181,37 @@ const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const { // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. if (! is_step_done(psWipeTower) && filaments_cnt !=0) { - // BBS - double width = m_config.prime_tower_width; - double layer_height = 0.2; // hard code layer height - double wipe_volume = m_config.prime_volume; - if (filaments_cnt == 1 && enable_timelapse_print()) { - const_cast(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width); - } else { - const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); + if (is_BBL_printer()) { // BBS + double width = m_config.prime_tower_width; + double layer_height = 0.2; // hard code layer height + double wipe_volume = m_config.prime_volume; + if (filaments_cnt == 1 && enable_timelapse_print()) { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width); + } else { + const_cast(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width); + } + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + } + else{ + // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. + const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; + + // Calculating depth should take into account currently set wiping volumes. + // For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower) + // and it worked well enough. Let's try to do slightly better by accounting for the purging volumes. + std::vector> wipe_volumes = WipeTower2::extract_wipe_volumes(m_config); + std::vector max_wipe_volumes; + for (const std::vector &v : wipe_volumes) + max_wipe_volumes.emplace_back(*std::max_element(v.begin(), v.end())); + float maximum = std::accumulate(max_wipe_volumes.begin(), max_wipe_volumes.end(), 0.f); + maximum = maximum * filaments_cnt / max_wipe_volumes.size(); + + float width = float(m_config.prime_tower_width); + float layer_height = 0.2f; // just assume fixed value, it will still be better than before. + + const_cast(this)->m_wipe_tower_data.depth = (maximum / layer_height) / width; + const_cast(this)->m_wipe_tower_data.height = -1.f; // unknown yet } - const_cast(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width; } return m_wipe_tower_data; @@ -2241,61 +2282,144 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - // BBS: in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume. - WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_config.prime_volume, m_wipe_tower_data.tool_ordering.first_extruder(), - m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z); + if(true || is_BBL_printer()) { + WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_config.prime_volume, m_wipe_tower_data.tool_ordering.first_extruder(), + m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z); + // wipe_tower.set_retract(); + // wipe_tower.set_zhop(); + + // Set the extruder & material properties at the wipe tower object. + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_tower.set_extruder(i, m_config); + + // BBS: remove priming logic + // m_wipe_tower_data.priming = Slic3r::make_unique>( + // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + + // Lets go through the wipe tower layers and determine pairs of extruder changes for each + // to pass to wipe_tower (so that it can use it for planning the layout of the tower) + { + // BBS: priming logic is removed, so get the initial extruder by first_extruder() + unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder(); + for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers + if (!layer_tools.has_wipe_tower) + continue; + bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); + wipe_tower.plan_toolchange((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height, current_extruder_id, + current_extruder_id); + + for (const auto extruder_id : layer_tools.extruders) { + // BBS: priming logic is removed, so no need to do toolchange for first extruder + if (/*(first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || */ extruder_id != + current_extruder_id) { + float volume_to_purge = wipe_volumes[current_extruder_id][extruder_id]; + volume_to_purge *= m_config.flush_multiplier; + + // Not all of that can be used for infill purging: + // volume_to_purge -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + + // try to assign some infills/objects for the wiping: + volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, + volume_to_purge); + + // add back the minimal amount toforce on the wipe tower: + // volume_to_purge += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + + // request a toolchange at the wipe tower with at least volume_to_wipe purging amount + wipe_tower.plan_toolchange((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height, + current_extruder_id, extruder_id, volume_to_purge, volume_to_purge); + current_extruder_id = extruder_id; + } + } + layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + + // if enable timelapse, slice all layer + if (enable_timelapse_print()) { + if (layer_tools.wipe_tower_partitions == 0) + wipe_tower.set_last_layer_extruder_fill(false); + continue; + } + + if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) + break; + } + } + + // Generate the wipe tower layers. + m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); + wipe_tower.generate(m_wipe_tower_data.tool_changes); + m_wipe_tower_data.depth = wipe_tower.get_depth(); + m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + + // Unload the current filament over the purge tower. + coordf_t layer_height = m_objects.front()->config().layer_height.value; + if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) { + // The wipe tower goes up to the last layer of the print. + if (wipe_tower.layer_finished()) { + // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge. + // Lift Z to the next layer. + wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, + true); + } else { + // There is yet enough space at this layer of the wipe tower for the final purge. + } + } else { + // The wipe tower does not reach the last print layer, perform the pruge at the last print layer. + assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0); + wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true); + } + m_wipe_tower_data.final_purge = Slic3r::make_unique(wipe_tower.tool_change((unsigned int) (-1))); + + m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); + const Vec3d origin = this->get_plate_origin(); + m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(), + wipe_tower.get_layer_height(), m_wipe_tower_data.depth, m_wipe_tower_data.brim_width, + {scale_(origin.x()), scale_(origin.y())}); + } + else{ + // Initialize the wipe tower. + WipeTower2 wipe_tower(m_config, m_default_region_config, m_plate_index, m_origin, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++ i) - wipe_tower.set_extruder(i, m_config); + for (size_t i = 0; i < number_of_extruders; ++i) + wipe_tower.set_extruder(i, m_config); - // BBS: remove priming logic - //m_wipe_tower_data.priming = Slic3r::make_unique>( - // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + // m_wipe_tower_data.priming = Slic3r::make_unique>( + // wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); // Lets go through the wipe tower layers and determine pairs of extruder changes for each // to pass to wipe_tower (so that it can use it for planning the layout of the tower) { - // BBS: priming logic is removed, so get the initial extruder by first_extruder() unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder(); for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id); - + wipe_tower.plan_toolchange((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height, current_extruder_id, + current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { - // BBS: priming logic is removed, so no need to do toolchange for first extruder if (/*(first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || */extruder_id != current_extruder_id) { - float volume_to_purge = wipe_volumes[current_extruder_id][extruder_id]; - volume_to_purge *= m_config.flush_multiplier; - + float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange + volume_to_wipe *= m_config.flush_multiplier; // Not all of that can be used for infill purging: - //volume_to_purge -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: - volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_purge); + volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: - //volume_to_purge += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // request a toolchange at the wipe tower with at least volume_to_wipe purging amount wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, - current_extruder_id, extruder_id, m_config.prime_volume, volume_to_purge); + current_extruder_id, extruder_id, volume_to_wipe); current_extruder_id = extruder_id; } } layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); - - // if enable timelapse, slice all layer - if (enable_timelapse_print()) { - if (layer_tools.wipe_tower_partitions == 0) - wipe_tower.set_last_layer_extruder_fill(false); - continue; - } - if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) break; } @@ -2305,7 +2429,9 @@ void Print::_make_wipe_tower() m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); wipe_tower.generate(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); + m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height(); // Unload the current filament over the purge tower. coordf_t layer_height = m_objects.front()->config().layer_height.value; @@ -2326,11 +2452,15 @@ void Print::_make_wipe_tower() m_wipe_tower_data.final_purge = Slic3r::make_unique( wipe_tower.tool_change((unsigned int)(-1))); - m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); - const Vec3d origin = this->get_plate_origin(); - m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), m_wipe_tower_data.depth, - m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())}); + const Vec3d origin = Vec3d::Zero(); + m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(), + config().initial_layer_print_height, m_wipe_tower_data.depth, + m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width, + config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle, + {scale_(origin.x()), scale_(origin.y())}); + } } // Generate a recommended G-code output file name based on the format template, default extension, and template parameters diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 83ccfbaa6d..f688e4ec3b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,6 +11,7 @@ #include "TriangleMeshSlicer.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/WipeTower2.hpp" #include "GCode/ThumbnailData.hpp" #include "GCode/GCodeProcessor.hpp" #include "MultiMaterialSegmentation.hpp" @@ -545,7 +546,10 @@ struct FakeWipeTower float height; float layer_height; float depth; + std::vector> z_and_depth_pairs; float brim_width; + float rotation_angle; + float cone_angle; Vec2d plate_origin; void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o) @@ -558,8 +562,21 @@ struct FakeWipeTower brim_width = bd; plate_origin = o; } - + void set_fake_extrusion_data(const Vec2f& p, float w, float h, float lh, float d, const std::vector>& zad, float bd, float ra, float ca, const Vec2d& o) + { + pos = p; + width = w; + height = h; + layer_height = lh; + depth = d; + z_and_depth_pairs = zad; + brim_width = bd; + rotation_angle = ra; + cone_angle = ca; + plate_origin = o; + } void set_pos(Vec2f p) { pos = p; } + void set_pos_and_rotation(const Vec2f& p, float rotation) { pos = p; rotation_angle = rotation; } std::vector getFakeExtrusionPathsFromWipeTower() const { @@ -587,6 +604,82 @@ struct FakeWipeTower } return paths; } + + std::vector getFakeExtrusionPathsFromWipeTower2() const + { + float h = height; + float lh = layer_height; + int d = scale_(depth); + int w = scale_(width); + int bd = scale_(brim_width); + Point minCorner = { -bd, -bd }; + Point maxCorner = { minCorner.x() + w + bd, minCorner.y() + d + bd }; + + const auto [cone_base_R, cone_scale_x] = WipeTower2::get_wipe_tower_cone_base(width, height, depth, cone_angle); + + std::vector paths; + for (float hh = 0.f; hh < h; hh += lh) { + + if (hh != 0.f) { + // The wipe tower may be getting smaller. Find the depth for this layer. + size_t i = 0; + for (i=0; i= z_and_depth_pairs[i].first && hh < z_and_depth_pairs[i+1].first) + break; + d = scale_(z_and_depth_pairs[i].second); + minCorner = {0.f, -d/2 + scale_(z_and_depth_pairs.front().second/2.f)}; + maxCorner = { minCorner.x() + w, minCorner.y() + d }; + } + + + ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh); + path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; + paths.push_back({ path }); + + // We added the border, now add several parallel lines so we can detect an object that is fully inside the tower. + // For now, simply use fixed spacing of 3mm. + for (coord_t y=minCorner.y()+scale_(3.); y 0.) { + path.polyline.clear(); + double r = cone_base_R * (1 - hh/height); + for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) + path.polyline.points.emplace_back(Point::new_scale(width/2. + r * std::cos(alpha)/cone_scale_x, depth/2. + r * std::sin(alpha))); + paths.back().emplace_back(path); + if (hh == 0.f) { // Cone brim. + for (float bw=brim_width; bw>0.f; bw-=3.f) { + path.polyline.clear(); + for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) // see load_wipe_tower_preview, where the same is a bit clearer + path.polyline.points.emplace_back(Point::new_scale( + width/2. + cone_base_R * std::cos(alpha)/cone_scale_x * (1. + cone_scale_x*bw/cone_base_R), + depth/2. + cone_base_R * std::sin(alpha) * (1. + bw/cone_base_R)) + ); + paths.back().emplace_back(path); + } + } + } + + // Only the first layer has brim. + if (hh == 0.f) { + minCorner = minCorner + Point(bd, bd); + maxCorner = maxCorner - Point(bd, bd); + } + } + + // Rotate and translate the tower into the final position. + for (ExtrusionPaths& ps : paths) { + for (ExtrusionPath& p : ps) { + p.polyline.rotate(Geometry::deg2rad(rotation_angle)); + p.polyline.translate(scale_(pos.x()), scale_(pos.y())); + } + } + + return paths; + } }; struct WipeTowerData @@ -604,7 +697,9 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; + std::vector> z_and_depth_pairs; float brim_width; + float height; void clear() { priming.reset(nullptr); @@ -617,7 +712,7 @@ struct WipeTowerData } private: - // Only allow the WipeTowerData to be instantiated internally by Print, + // Only allow the WipeTowerData to be instantiated internally by Print, // as this WipeTowerData shares reference to Print::m_tool_ordering. friend class Print; WipeTowerData(ToolOrdering &tool_ordering) : tool_ordering(tool_ordering) { clear(); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index aa757fffaf..8d0e942210 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1415,6 +1415,132 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercents{ 100 }); +def = this->add("filament_loading_speed", coFloats); + def->label = L("Loading speed"); + def->tooltip = L("Speed used for loading the filament on the wipe tower."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 28. }); + + def = this->add("filament_loading_speed_start", coFloats); + def->label = L("Loading speed at the start"); + def->tooltip = L("Speed used at the very beginning of loading phase."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 3. }); + + def = this->add("filament_unloading_speed", coFloats); + def->label = L("Unloading speed"); + def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect " + " initial part of unloading just after ramming)."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 90. }); + + def = this->add("filament_unloading_speed_start", coFloats); + def->label = L("Unloading speed at the start"); + def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 100. }); + + def = this->add("filament_toolchange_delay", coFloats); + def->label = L("Delay after unloading"); + def->tooltip = L("Time to wait after the filament is unloaded. " + "May help to get reliable toolchanges with flexible materials " + "that may need more time to shrink to original dimensions."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_cooling_moves", coInts); + def->label = L("Number of cooling moves"); + def->tooltip = L("Filament is cooled by being moved back and forth in the " + "cooling tubes. Specify desired number of these moves."); + def->max = 0; + def->max = 20; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInts { 4 }); + + def = this->add("filament_cooling_initial_speed", coFloats); + def->label = L("Speed of the first cooling move"); + def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 2.2 }); + + def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); + def->label = L("Minimal purge on wipe tower"); + def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside " + "the nozzle may not be known, and the filament pressure is likely not yet stable. " + "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " + "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); + def->sidetext = L("mm³"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 15. }); + + def = this->add("filament_cooling_final_speed", coFloats); + def->label = L("Speed of the last cooling move"); + def->tooltip = L("Cooling moves are gradually accelerating towards this speed."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 3.4 }); + + def = this->add("filament_load_time", coFloats); + def->label = L("Filament load time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_ramming_parameters", coStrings); + def->label = L("Ramming parameters"); + def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" + " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); + + def = this->add("filament_unload_time", coFloats); + def->label = L("Filament unload time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_multitool_ramming", coBools); + def->label = L("Enable ramming for multitool setups"); + def->tooltip = L("Perform ramming when using multitool printer (i.e. when the 'Single Extruder Multimaterial' in Printer Settings is unchecked). " + "When checked, a small amount of filament is rapidly extruded on the wipe tower just before the toolchange. " + "This option is only used when the wipe tower is enabled."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBools { false }); + + def = this->add("filament_multitool_ramming_volume", coFloats); + def->label = L("Multitool ramming volume"); + def->tooltip = L("The volume to be rammed before the toolchange."); + def->sidetext = L("mm³"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 10. }); + + def = this->add("filament_multitool_ramming_flow", coFloats); + def->label = L("Multitool ramming flow"); + def->tooltip = L("Flow used for ramming the filament before the toolchange."); + def->sidetext = L("mm³/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 10. }); + def = this->add("filament_density", coFloats); def->label = L("Density"); def->tooltip = L("Filament density. For statistics only"); @@ -2460,6 +2586,48 @@ void PrintConfigDef::init_fff_params() def->readonly = false; def->set_default_value(new ConfigOptionFloat { 0.0 }); + def = this->add("cooling_tube_retraction", coFloat); + def->label = L("Cooling tube position"); + def->tooltip = L("Distance of the center-point of the cooling tube from the extruder tip."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(91.5)); + + def = this->add("cooling_tube_length", coFloat); + def->label = L("Cooling tube length"); + def->tooltip = L("Length of the cooling tube to limit space for cooling moves inside it."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(5.)); + + def = this->add("high_current_on_filament_swap", coBool); + def->label = L("High extruder current on filament swap"); + def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" + " sequence to allow for rapid ramming feed rates and to overcome resistance when loading" + " a filament with an ugly shaped tip."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(0)); + + def = this->add("parking_pos_retraction", coFloat); + def->label = L("Filament parking position"); + def->tooltip = L("Distance of the extruder tip from the position where the filament is parked " + "when unloaded. This should match the value in printer firmware."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(92.)); + + def = this->add("extra_loading_move", coFloat); + def->label = L("Extra loading distance"); + def->tooltip = L("When set to zero, the distance the filament is moved from parking position during load " + "is exactly the same as it was moved back during unload. When positive, it is loaded further, " + " if negative, the loading move is shorter than unloading."); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(-2.)); + def = this->add("start_end_points", coPoints); def->label = L("Start end points"); def->tooltip = L("The start and end points which is from cutter area to garbage can."); @@ -3026,19 +3194,25 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionStrings { " " }); def = this->add("single_extruder_multi_material", coBool); - //def->label = L("Single Extruder Multi Material"); - //def->tooltip = L("Use single nozzle to print multi filament"); - def->mode = comDevelop; + def->label = L("Single Extruder Multi Material"); + def->tooltip = L("Use single nozzle to print multi filament"); + def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); def = this->add("wipe_tower_no_sparse_layers", coBool); - //def->label = L("No sparse layers (EXPERIMENTAL)"); - //def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no toolchanges. " - // "On layers with a toolchange, extruder will travel downward to print the wipe tower. " - // "User is responsible for ensuring there is no collision with the print."); - def->mode = comDevelop; + def->label = L("No sparse layers (EXPERIMENTAL)"); + def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no toolchanges. " + "On layers with a toolchange, extruder will travel downward to print the wipe tower. " + "User is responsible for ensuring there is no collision with the print."); + def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("single_extruder_multi_material_priming", coBool); + def->label = L("Prime all printing extruders"); + def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("slice_closing_radius", coFloat); def->label = L("Slice gap closing radius"); def->category = L("Quality"); @@ -3715,10 +3889,10 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(35.)); def = this->add("wipe_tower_rotation_angle", coFloat); - //def->label = L("Wipe tower rotation angle"); - //def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); - //def->sidetext = L("°"); - def->mode = comDevelop; + def->label = L("Wipe tower rotation angle"); + def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); + def->sidetext = L("°"); + def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("prime_tower_brim_width", coFloat); @@ -3729,6 +3903,41 @@ void PrintConfigDef::init_fff_params() def->min = 0.; def->set_default_value(new ConfigOptionFloat(3.)); + def = this->add("wipe_tower_cone_angle", coFloat); + def->label = L("Stabilization cone apex angle"); + def->tooltip = L("Angle at the apex of the cone that is used to stabilize the wipe tower. " + "Larger angle means wider base."); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->min = 0.; + def->max = 90.; + def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("wipe_tower_extra_spacing", coPercent); + def->label = L("Wipe tower purge lines spacing"); + def->tooltip = L("Spacing of purge lines on the wipe tower."); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = 100.; + def->max = 300.; + def->set_default_value(new ConfigOptionPercent(100.)); + + def = this->add("wipe_tower_extruder", coInt); + def->label = L("Wipe tower extruder"); + def->category = L("Extruders"); + def->tooltip = L("The extruder to use when printing perimeter of the wipe tower. " + "Set to 0 to use the one that is available (non-soluble would be preferred)."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("wiping_volumes_extruders", coFloats); + def->label = L("Purging volumes - load/unload volumes"); + def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " + "wipe tower. These values are used to simplify creation of the full purging " + "volumes below."); + def->set_default_value(new ConfigOptionFloats { 70., 70., 70., 70., 70., 70., 70., 70., 70., 70. }); + def = this->add("flush_into_infill", coBool); def->category = L("Flush options"); def->label = L("Flush into objects' infill"); @@ -3754,13 +3963,12 @@ void PrintConfigDef::init_fff_params() "It will not take effect, unless the prime tower is enabled."); def->set_default_value(new ConfigOptionBool(false)); - //BBS - //def = this->add("wipe_tower_bridging", coFloat); - //def->label = L("Maximal bridging distance"); - //def->tooltip = L("Maximal distance between supports on sparse infill sections."); - //def->sidetext = L("mm"); - //def->mode = comAdvanced; - //def->set_default_value(new ConfigOptionFloat(10.)); + def = this->add("wipe_tower_bridging", coFloat); + def->label = L("Maximal bridging distance"); + def->tooltip = L("Maximal distance between supports on sparse infill sections."); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.)); def = this->add("xy_hole_compensation", coFloat); def->label = L("X-Y hole compensation"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 3bdfb8844d..c7ea723b8e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -870,9 +870,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInts, temperature_vitrification)) //BBS ((ConfigOptionFloats, filament_max_volumetric_speed)) ((ConfigOptionInts, required_nozzle_HRC)) - ((ConfigOptionFloat, machine_load_filament_time)) - ((ConfigOptionFloat, machine_unload_filament_time)) - ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) // BBS ((ConfigOptionBool, scan_first_layer)) // ((ConfigOptionBool, spaghetti_detector)) @@ -900,6 +897,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionString, machine_start_gcode)) ((ConfigOptionStrings, filament_start_gcode)) ((ConfigOptionBool, single_extruder_multi_material)) + ((ConfigOptionBool, single_extruder_multi_material_priming)) ((ConfigOptionBool, wipe_tower_no_sparse_layers)) ((ConfigOptionString, change_filament_gcode)) ((ConfigOptionFloat, travel_speed)) @@ -919,6 +917,31 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, initial_layer_travel_speed)) ((ConfigOptionBool, bbl_calib_mark_logo)) + // Orca: mmu + ((ConfigOptionFloat, cooling_tube_retraction)) + ((ConfigOptionFloat, cooling_tube_length)) + ((ConfigOptionBool, high_current_on_filament_swap)) + ((ConfigOptionFloat, parking_pos_retraction)) + ((ConfigOptionFloat, extra_loading_move)) + ((ConfigOptionFloat, machine_load_filament_time)) + ((ConfigOptionFloat, machine_unload_filament_time)) + ((ConfigOptionFloats, filament_loading_speed)) + ((ConfigOptionFloats, filament_loading_speed_start)) + ((ConfigOptionFloats, filament_load_time)) + ((ConfigOptionFloats, filament_unloading_speed)) + ((ConfigOptionFloats, filament_unloading_speed_start)) + ((ConfigOptionFloats, filament_toolchange_delay)) + // Orca todo: consolidate with machine_load_filament_time + ((ConfigOptionFloats, filament_unload_time)) + ((ConfigOptionInts, filament_cooling_moves)) + ((ConfigOptionFloats, filament_cooling_initial_speed)) + ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) + ((ConfigOptionFloats, filament_cooling_final_speed)) + ((ConfigOptionStrings, filament_ramming_parameters)) + ((ConfigOptionBools, filament_multitool_ramming)) + ((ConfigOptionFloats, filament_multitool_ramming_volume)) + ((ConfigOptionFloats, filament_multitool_ramming_flow)) + ) // This object is mapped to Perl as Slic3r::Config::Print. @@ -1021,9 +1044,16 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_per_color_wipe)) ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, prime_tower_brim_width)) - //((ConfigOptionFloat, wipe_tower_bridging)) + ((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloats, flush_volumes_matrix)) ((ConfigOptionFloats, flush_volumes_vector)) + + // Orca: mmu support + ((ConfigOptionFloat, wipe_tower_cone_angle)) + ((ConfigOptionPercent, wipe_tower_extra_spacing)) + ((ConfigOptionInt, wipe_tower_extruder)) + ((ConfigOptionFloats, wiping_volumes_extruders)) + // BBS: wipe tower is only used for priming ((ConfigOptionFloat, prime_volume)) ((ConfigOptionFloat, flush_multiplier)) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 82bd5ac3a3..61dc8c9491 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -278,7 +278,8 @@ set(SLIC3R_GUI_SOURCES GUI/wxExtensions.cpp GUI/wxExtensions.hpp GUI/WipeTowerDialog.cpp - GUI/WipeTowerDialog.hpp + GUI/RammingChart.cpp + GUI/RammingChart.hpp GUI/RemovableDriveManager.cpp GUI/RemovableDriveManager.hpp GUI/SendSystemInfoDialog.cpp diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d3e74d7f60..ff1d5c9b97 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -645,6 +645,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_ooze_prevention = config->opt_bool("ooze_prevention"); toggle_field("standby_temperature_delta", have_ooze_prevention); + // Orca todo: enable/disable wipe tower parameters + // for (auto el : + // {"wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", + // "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming"}) + // toggle_field(el, have_wipe_tower); + bool have_prime_tower = config->opt_bool("enable_prime_tower"); for (auto el : { "prime_tower_width", "prime_volume", "prime_tower_brim_width"}) toggle_line(el, have_prime_tower); @@ -692,7 +698,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line("exclude_object", gcflavor == gcfKlipper); toggle_line("min_width_top_surface",config->opt_bool("only_one_wall_top")); - } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 068049769a..fbef103121 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2328,7 +2328,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", - "support_top_z_distance", "support_bottom_z_distance", "raft_layers" + "support_top_z_distance", "support_bottom_z_distance", "raft_layers", + "wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder", })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp new file mode 100644 index 0000000000..244d83a9b8 --- /dev/null +++ b/src/slic3r/GUI/RammingChart.cpp @@ -0,0 +1,292 @@ +#include +#include + +#include "RammingChart.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" + +wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); + + +void Chart::draw() { + wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win + + dc.SetBrush(GetBackgroundColour()); + dc.SetPen(GetBackgroundColour()); + dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows + +#ifdef _WIN32 + dc.SetPen(wxPen(GetForegroundColour())); + dc.SetBrush(wxBrush(Slic3r::GUI::wxGetApp().get_highlight_default_clr())); +#else + dc.SetPen(*wxBLACK_PEN); + dc.SetBrush(*wxWHITE_BRUSH); +#endif + dc.DrawRectangle(m_rect); + + if (visible_area.m_width < 0.499) { + dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-legend_side,m_rect.GetBottom()-m_rect.GetHeight()/2)); + return; + } + + + if (!m_line_to_draw.empty()) { + for (unsigned int i=0;i2) { + m_buttons.erase(m_buttons.begin()+button_index); + recalculate_line(); + } +} + + + +void Chart::mouse_clicked(wxMouseEvent& event) { + wxPoint point = event.GetPosition(); + int button_index = which_button_is_clicked(point); + if ( button_index != -1) { + m_dragged = &m_buttons[button_index]; + m_previous_mouse = point; + } +} + + + +void Chart::mouse_moved(wxMouseEvent& event) { + if (!event.Dragging() || !m_dragged) return; + wxPoint pos = event.GetPosition(); + wxRect rect = m_rect; + rect.Deflate(side/2.); + if (!(rect.Contains(pos))) { // the mouse left chart area + mouse_left_window(event); + return; + } + int delta_x = pos.x - m_previous_mouse.x; + int delta_y = pos.y - m_previous_mouse.y; + m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); + m_previous_mouse = pos; + recalculate_line(); +} + + + +void Chart::mouse_double_clicked(wxMouseEvent& event) { + if (!manual_points_manipulation) + return; + wxPoint point = event.GetPosition(); + if (!m_rect.Contains(point)) // the click is outside the chart + return; + m_buttons.push_back(screen_to_math(point)); + std::sort(m_buttons.begin(),m_buttons.end()); + recalculate_line(); + return; +} + + + + +void Chart::recalculate_line() { + m_line_to_draw.clear(); + m_total_volume = 0.f; + + std::vector points; + for (auto& but : m_buttons) { + points.push_back(wxPoint(math_to_screen(but.get_pos()))); + if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back(); + if (points.size()>1 && points.back().x > m_rect.GetRight()) { + points.pop_back(); + break; + } + } + + // The calculation wouldn't work in case the ramming is to be turned off completely. + if (points.size()>1) { + std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; }); + + // Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods + const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point + // false - second ---- || ------- + const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points + std::vector diag(N+1); + std::vector mu(N+1); + std::vector lambda(N+1); + std::vector h(N+1); + std::vector rhs(N+1); + + // let's fill in inner equations + for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x; + std::fill(diag.begin(),diag.end(),2.f); + for (int i=1;i<=N-1;++i) { + mu[i] = h[i]/(h[i]+h[i+1]); + lambda[i] = 1.f - mu[i]; + rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) - + float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) ); + } + + // now fill in the first and last equations, according to boundary conditions: + if (boundary_first_derivative) { + const float endpoints_derivative = 0; + lambda[0] = 1; + mu[N] = 1; + rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative); + rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x)); + } + else { + lambda[0] = 0; + mu[N] = 0; + rhs[0] = 0; + rhs[N] = 0; + } + + // the trilinear system is ready to be solved: + for (int i=1;i<=N;++i) { + float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation + diag[i]-= multiple * lambda[i-1]; + rhs[i] -= multiple * rhs[i-1]; + } + // now the back substitution (vector mu contains invalid values from now on): + rhs[N] = rhs[N]/diag[N]; + for (int i=N-1;i>=0;--i) + rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i]; + + unsigned int i=1; + float y=0.f; + for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) { + if (splines) { + if (i x) + y = points[0].y; + else + if (points[N].x < x) + y = points[N].y; + else + y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) + + (points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] + + (points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i]; + m_line_to_draw.push_back(y); + } + else { + float x_math = screen_to_math(wxPoint(x,0)).m_x; + if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math) + ++i; + m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y); + } + + m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); + m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); + m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); + } + } + + wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED)); + Refresh(); +} + + + +std::vector Chart::get_ramming_speed(float sampling) const { + std::vector speeds_out; + + const int number_of_samples = std::round( visible_area.m_width / sampling); + if (number_of_samples>0) { + const int dx = (m_line_to_draw.size()-1) / number_of_samples; + for (int j=0;j> Chart::get_buttons() const { + std::vector> buttons_out; + for (const auto& button : m_buttons) + buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y))); + return buttons_out; +} + + + + +BEGIN_EVENT_TABLE(Chart, wxWindow) +EVT_MOTION(Chart::mouse_moved) +EVT_LEFT_DOWN(Chart::mouse_clicked) +EVT_LEFT_UP(Chart::mouse_released) +EVT_LEFT_DCLICK(Chart::mouse_double_clicked) +EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked) +EVT_LEAVE_WINDOW(Chart::mouse_left_window) +EVT_PAINT(Chart::paint_event) +END_EVENT_TABLE() diff --git a/src/slic3r/GUI/RammingChart.hpp b/src/slic3r/GUI/RammingChart.hpp new file mode 100644 index 0000000000..f62546b1d5 --- /dev/null +++ b/src/slic3r/GUI/RammingChart.hpp @@ -0,0 +1,120 @@ +#ifndef RAMMING_CHART_H_ +#define RAMMING_CHART_H_ + +#include +#include +#ifndef WX_PRECOMP + #include +#endif + +wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); + + +class Chart : public wxWindow { + +public: + Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons,int ramming_speed_size, float sampling, int scale_unit=10) : + wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()), + scale_unit(scale_unit), legend_side(5*scale_unit) + { + SetBackgroundStyle(wxBG_STYLE_PAINT); + m_rect = wxRect(wxPoint(legend_side,0),rect.GetSize()-wxSize(legend_side,legend_side)); + visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.); + m_buttons.clear(); + if (initial_buttons.size()>0) + for (const auto& pair : initial_buttons) + m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second)); + recalculate_line(); + } + void set_xy_range(float x,float y) { + x = int(x/0.5) * 0.5; + if (x>=0) visible_area.SetRight(x); + if (y>=0) visible_area.SetBottom(y); + recalculate_line(); + } + float get_volume() const { return m_total_volume; } + float get_time() const { return visible_area.m_width; } + + std::vector get_ramming_speed(float sampling) const; //returns sampled ramming speed + std::vector> get_buttons() const; // returns buttons position + + void draw(); + + void mouse_clicked(wxMouseEvent& event); + void mouse_right_button_clicked(wxMouseEvent& event); + void mouse_moved(wxMouseEvent& event); + void mouse_double_clicked(wxMouseEvent& event); + void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; } + void mouse_released(wxMouseEvent&) { m_dragged = nullptr; } + void paint_event(wxPaintEvent&) { draw(); } + DECLARE_EVENT_TABLE() + + + + +private: + static const bool fixed_x = true; + static const bool splines = true; + static const bool manual_points_manipulation = false; + static const int side = 10; // side of draggable button + + const int scale_unit; + int legend_side; + + class ButtonToDrag { + public: + bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; } + ButtonToDrag(wxPoint2DDouble pos) : m_pos{pos} {}; + wxPoint2DDouble get_pos() const { return m_pos; } + void move(double x,double y) { m_pos.m_x+=x; m_pos.m_y+=y; } + private: + wxPoint2DDouble m_pos; // position in math coordinates + }; + + + + wxPoint math_to_screen(const wxPoint2DDouble& math) const { + wxPoint screen; + screen.x = (math.m_x-visible_area.m_x) * (m_rect.GetWidth() / visible_area.m_width ); + screen.y = (math.m_y-visible_area.m_y) * (m_rect.GetHeight() / visible_area.m_height ); + screen.y *= -1; + screen += m_rect.GetLeftBottom(); + return screen; + } + wxPoint2DDouble screen_to_math(const wxPoint& screen) const { + wxPoint2DDouble math = screen; + math -= m_rect.GetLeftBottom(); + math.m_y *= -1; + math.m_x *= visible_area.m_width / m_rect.GetWidth(); // scales to [0;1]x[0,1] + math.m_y *= visible_area.m_height / m_rect.GetHeight(); + return (math+visible_area.GetLeftTop()); + } + + int which_button_is_clicked(const wxPoint& point) const { + if (!m_rect.Contains(point)) + return -1; + for (unsigned int i=0;i m_buttons; + std::vector m_line_to_draw; + wxRect2DDouble visible_area; + ButtonToDrag* m_dragged = nullptr; + float m_total_volume = 0.f; + +}; + + +#endif // RAMMING_CHART_H_ \ No newline at end of file diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8de6ae14f8..c872d65555 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" +#include "WipeTowerDialog.hpp" #include "Search.hpp" #include "OG_CustomCtrl.hpp" @@ -2072,6 +2073,13 @@ void TabPrint::build() optgroup->append_single_option_line("prime_tower_width"); optgroup->append_single_option_line("prime_volume"); optgroup->append_single_option_line("prime_tower_brim_width"); + optgroup->append_single_option_line("wipe_tower_rotation_angle"); + optgroup->append_single_option_line("wipe_tower_bridging"); + optgroup->append_single_option_line("wipe_tower_cone_angle"); + optgroup->append_single_option_line("wipe_tower_extra_spacing"); + optgroup->append_single_option_line("wipe_tower_no_sparse_layers"); + optgroup->append_single_option_line("single_extruder_multi_material_priming"); + optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill"); @@ -2844,6 +2852,46 @@ void TabFilament::build() option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); + page = add_options_page(L("MMU"), "advanced"); + optgroup = page->new_optgroup(L("Wipe tower parameters")); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); + + optgroup = page->new_optgroup(L("Toolchange parameters with single extruder MM printers")); + optgroup->append_single_option_line("filament_loading_speed_start"); + optgroup->append_single_option_line("filament_loading_speed"); + optgroup->append_single_option_line("filament_unloading_speed_start"); + optgroup->append_single_option_line("filament_unloading_speed"); + optgroup->append_single_option_line("filament_load_time"); + optgroup->append_single_option_line("filament_unload_time"); + optgroup->append_single_option_line("filament_toolchange_delay"); + optgroup->append_single_option_line("filament_cooling_moves"); + optgroup->append_single_option_line("filament_cooling_initial_speed"); + optgroup->append_single_option_line("filament_cooling_final_speed"); + + create_line_with_widget(optgroup.get(), "filament_ramming_parameters", "", [this](wxWindow* parent) { + auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + wxGetApp().UpdateDarkUI(ramming_dialog_btn); + ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + ramming_dialog_btn->SetSize(ramming_dialog_btn->GetBestSize()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(ramming_dialog_btn); + + ramming_dialog_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); + if (dlg.ShowModal() == wxID_OK) { + load_key_value("filament_ramming_parameters", dlg.get_parameters()); + update_changed_ui(); + } + }); + return sizer; + }); + + + optgroup = page->new_optgroup(L("Toolchange parameters with multi extruder MM printers")); + optgroup->append_single_option_line("filament_multitool_ramming"); + optgroup->append_single_option_line("filament_multitool_ramming_volume"); + optgroup->append_single_option_line("filament_multitool_ramming_flow"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"),"note", 0); optgroup->label_width = 0; diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index aa50d06b29..b407c56920 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -35,11 +35,163 @@ static const wxColour g_text_color = wxColour(107, 107, 107, 255); #define MAX_FLUSH_VALUE 999 #define MIN_WIPING_DIALOG_WIDTH FromDIP(400) + static void update_ui(wxWindow* window) { Slic3r::GUI::wxGetApp().UpdateDarkUI(window); } +RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters) +: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + update_ui(this); + m_panel_ramming = new RammingPanel(this,parameters); + + // Not found another way of getting the background colours of RammingDialog, RammingPanel and Chart correct than setting + // them all explicitely. Reading the parent colour yielded colour that didn't really match it, no wxSYS_COLOUR_... matched + // colour used for the dialog. Same issue (and "solution") here : https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608 + // Whoever can fix this, feel free to do so. +#ifndef _WIN32 + this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); +#endif + m_panel_ramming->Show(true); + this->Show(); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_panel_ramming, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + update_ui(static_cast(this->FindWindowById(wxID_OK, this))); + update_ui(static_cast(this->FindWindowById(wxID_CANCEL, this))); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); }); + + this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { + m_output_data = m_panel_ramming->get_parameters(); + EndModal(wxID_OK); + },wxID_OK); + this->Show(); +// wxMessageDialog dlg(this, _(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to " + Slic3r::GUI::MessageDialog dlg(this, _(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to " + "properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself " + "be reinserted later. This phase is important and different materials can require different extrusion speeds to get " + "the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level " + "setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc.")), _(L("Warning")), wxOK | wxICON_EXCLAMATION);// .ShowModal(); + dlg.ShowModal(); +} + + +#ifdef _WIN32 +#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE +#else +#define style wxSP_ARROW_KEYS +#endif + + + +RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) +: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/) +{ + update_ui(this); + auto sizer_chart = new wxBoxSizer(wxVERTICAL); + auto sizer_param = new wxBoxSizer(wxVERTICAL); + + std::stringstream stream{ parameters }; + stream >> m_ramming_line_width_multiplicator >> m_ramming_step_multiplicator; + int ramming_speed_size = 0; + float dummy = 0.f; + while (stream >> dummy) + ++ramming_speed_size; + stream.clear(); + stream.get(); + + std::vector> buttons; + float x = 0.f; + float y = 0.f; + while (stream >> x >> y) + buttons.push_back(std::make_pair(x, y)); + + m_chart = new Chart(this, wxRect(scale(1),scale(1),scale(48),scale(36)), buttons, ramming_speed_size, 0.25f, scale(1)); +#ifdef _WIN32 + update_ui(m_chart); +#else + m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor +#endif + sizer_chart->Add(m_chart, 0, wxALL, 5); + + m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,0.,5.0,3.,0.5); + m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,0,10000,0); + m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100); + m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100); + +#ifdef _WIN32 + update_ui(m_widget_time->GetText()); + update_ui(m_widget_volume); + update_ui(m_widget_ramming_line_width_multiplicator); + update_ui(m_widget_ramming_step_multiplicator); +#endif + + auto gsizer_param = new wxFlexGridSizer(2, 5, 15); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_time); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume")) + " (" + _(L("mm")) + wxString("³):", wxConvUTF8))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_volume); + gsizer_param->AddSpacer(20); + gsizer_param->AddSpacer(20); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_ramming_line_width_multiplicator); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_ramming_step_multiplicator); + + sizer_param->Add(gsizer_param, 0, wxTOP, scale(10)); + + m_widget_time->SetValue(m_chart->get_time()); + m_widget_time->SetDigits(2); + m_widget_volume->SetValue(m_chart->get_volume()); + m_widget_volume->Disable(); + m_widget_ramming_line_width_multiplicator->SetValue(m_ramming_line_width_multiplicator); + m_widget_ramming_step_multiplicator->SetValue(m_ramming_step_multiplicator); + + m_widget_ramming_step_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); }); + m_widget_ramming_line_width_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); }); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(sizer_chart, 0, wxALL, 5); + sizer->Add(sizer_param, 0, wxALL, 10); + + sizer->SetSizeHints(this); + SetSizer(sizer); + + m_widget_time->Bind(wxEVT_TEXT,[this](wxCommandEvent&) {m_chart->set_xy_range(m_widget_time->GetValue(),-1);}); + m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value + m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value + Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} ); + Refresh(true); // erase background +} + +void RammingPanel::line_parameters_changed() { + m_ramming_line_width_multiplicator = m_widget_ramming_line_width_multiplicator->GetValue(); + m_ramming_step_multiplicator = m_widget_ramming_step_multiplicator->GetValue(); +} + +std::string RammingPanel::get_parameters() +{ + std::vector speeds = m_chart->get_ramming_speed(0.25f); + std::vector> buttons = m_chart->get_buttons(); + std::stringstream stream; + stream << m_ramming_line_width_multiplicator << " " << m_ramming_step_multiplicator; + for (const float& speed_value : speeds) + stream << " " << speed_value; + stream << "|"; + for (const auto& button : buttons) + stream << " " << button.first << " " << button.second; + return stream.str(); +} + + #ifdef _WIN32 #define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE #else @@ -233,7 +385,7 @@ void WipingPanel::create_panels(wxWindow* parent, const int num) { sizer->AddSpacer(ROW_BEG_PADDING); sizer->Add(icon, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, ROW_VERT_PADDING); - for (unsigned int j = 0; j < num; ++j) { + for (int j = 0; j < num; ++j) { edit_boxes[j][i]->Reparent(panel); edit_boxes[j][i]->SetBackgroundColour(panel->GetBackgroundColour()); sizer->AddSpacer(EDIT_BOXES_GAP); diff --git a/src/slic3r/GUI/WipeTowerDialog.hpp b/src/slic3r/GUI/WipeTowerDialog.hpp index 91f7612ec0..4d038d378d 100644 --- a/src/slic3r/GUI/WipeTowerDialog.hpp +++ b/src/slic3r/GUI/WipeTowerDialog.hpp @@ -9,8 +9,38 @@ #include #include +#include "RammingChart.hpp" class Button; + +class RammingPanel : public wxPanel { +public: + RammingPanel(wxWindow* parent); + RammingPanel(wxWindow* parent,const std::string& data); + std::string get_parameters(); + +private: + Chart* m_chart = nullptr; + wxSpinCtrl* m_widget_volume = nullptr; + wxSpinCtrl* m_widget_ramming_line_width_multiplicator = nullptr; + wxSpinCtrl* m_widget_ramming_step_multiplicator = nullptr; + wxSpinCtrlDouble* m_widget_time = nullptr; + int m_ramming_step_multiplicator; + int m_ramming_line_width_multiplicator; + + void line_parameters_changed(); +}; + + +class RammingDialog : public wxDialog { +public: + RammingDialog(wxWindow* parent,const std::string& parameters); + std::string get_parameters() { return m_output_data; } +private: + RammingPanel* m_panel_ramming = nullptr; + std::string m_output_data; +}; + class WipingPanel : public wxPanel { public: // BBS