diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 14c2d66ae..89bbd4546 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -52,9 +52,8 @@ sub new { $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile - nozzle_diameter single_extruder_multi_material - wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour - max_print_height + nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width + wipe_tower_rotation_angle extruder_colour filament_colour max_print_height )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 09cc02930..cffacfef4 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -210,9 +210,10 @@ sub reload_scene { if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower && ! $self->{config}->complete_objects) { $self->volumes->load_wipe_tower_preview(1000, - $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, - $self->{config}->wipe_tower_width, $self->{config}->wipe_tower_per_color_wipe * ($extruders_count - 1), - $self->{model}->bounding_box->z_max, $self->UseVBOs); + $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width, + #$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete + 15 * ($extruders_count - 1), + $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs); } } diff --git a/t/combineinfill.t b/t/combineinfill.t index e94cf9eb5..5402a84f5 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -57,7 +57,7 @@ plan tests => 8; my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5]); + $config->set('nozzle_diameter', [0.5,0.5,0.5,0.5]); $config->set('infill_every_layers', 2); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); diff --git a/t/custom_gcode.t b/t/custom_gcode.t index bafcd4610..7c2a75f29 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -49,7 +49,7 @@ use Slic3r::Test; my $parser = Slic3r::GCode::PlaceholderParser->new; my $config = Slic3r::Config::new_from_defaults; $config->set('printer_notes', ' PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 '); - $config->set('nozzle_diameter', [0.6, 0.6, 0.6, 0.6]); + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $parser->apply_config($config); $parser->set('foo' => 0); $parser->set('bar' => 2); @@ -123,6 +123,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('extruder', 2); $config->set('first_layer_temperature', [200,205]); @@ -204,6 +205,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]); $config->set('start_gcode', qq! ;substitution:{if infill_extruder==1}if block {elsif infill_extruder==2}elsif block 1 @@ -228,6 +230,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('start_gcode', ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . diff --git a/t/fill.t b/t/fill.t index a6fe8619c..dd9eee487 100644 --- a/t/fill.t +++ b/t/fill.t @@ -164,6 +164,7 @@ SKIP: for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); $config->set('fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); @@ -195,6 +196,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); $config->set('infill_only_where_needed', 1); $config->set('bottom_solid_layers', 0); $config->set('infill_extruder', 2); @@ -276,7 +278,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { $config->set('fill_density', 0); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35]); + $config->set('nozzle_diameter', [0.35,0.35,0.35,0.35]); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 2); $config->set('infill_extrusion_width', 0.52); diff --git a/t/multi.t b/t/multi.t index 49d35d907..75ce0c286 100644 --- a/t/multi.t +++ b/t/multi.t @@ -16,6 +16,7 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('raft_layers', 2); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 3); @@ -89,6 +90,7 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('support_material_extruder', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -97,6 +99,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -105,6 +108,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('perimeter_extruder', 2); $config->set('infill_extruder', 2); $config->set('support_material_extruder', 2); @@ -126,6 +130,7 @@ use Slic3r::Test; $upper_config->set('bottom_solid_layers', 1); $upper_config->set('top_solid_layers', 0); my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('fill_density', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); @@ -172,6 +177,7 @@ use Slic3r::Test; my $object = $model->objects->[0]; my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('skirts', 0); diff --git a/t/retraction.t b/t/retraction.t index d7f1ea145..6e6a130ca 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -95,6 +95,7 @@ use Slic3r::Test qw(_eq); 1; }; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('first_layer_height', $config->layer_height); $config->set('first_layer_speed', '100%'); $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code @@ -207,6 +208,7 @@ use Slic3r::Test qw(_eq); { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('start_gcode', ''); $config->set('retract_lift', [3, 4]); @@ -255,4 +257,4 @@ use Slic3r::Test qw(_eq); 'Z is not lifted above the configured value for 2. extruder'; } -__END__ \ No newline at end of file +__END__ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 84f169e57..abd9c3617 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -202,6 +202,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/2DBed.hpp ${LIBDIR}/slic3r/GUI/wxExtensions.cpp ${LIBDIR}/slic3r/GUI/wxExtensions.hpp + ${LIBDIR}/slic3r/GUI/WipeTowerDialog.cpp + ${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp + ${LIBDIR}/slic3r/GUI/RammingChart.cpp + ${LIBDIR}/slic3r/GUI/RammingChart.hpp ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp ${LIBDIR}/slic3r/Config/Snapshot.cpp diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index dbe6ec896..89d5d231e 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -9,74 +9,115 @@ namespace Slic3r { -static inline Polyline make_wave_vertical( - double width, double height, double x0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip) { - Polyline polyline; - polyline.points.emplace_back(Point(coord_t(clamp(0., width, x0) * scaleFactor), 0)); - double phase_offset_sin = (z_cos < 0 ? M_PI : 0) + M_PI; - double phase_offset_cos = (z_cos < 0 ? M_PI : 0) + M_PI + (flip ? M_PI : 0.); - for (double y = 0.; y < height + segmentSize; y += segmentSize) { - y = std::min(y, height); - double a = sin(y + phase_offset_sin); + if (vertical) { + double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI; + double a = sin(x + phase_offset); double b = - z_cos; - double res = z_sin * cos(y + phase_offset_cos); + double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.)); double r = sqrt(sqr(a) + sqr(b)); - double x = clamp(0., width, asin(a/r) + asin(res/r) + M_PI + x0); - polyline.points.emplace_back(convert_to(Pointf(x, y) * scaleFactor)); + return asin(a/r) + asin(res/r) + M_PI; } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); + else { + double phase_offset = z_sin < 0 ? M_PI : 0.; + double a = cos(x + phase_offset); + double b = - z_sin; + double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI)); + double r = sqrt(sqr(a) + sqr(b)); + return (asin(a/r) + asin(res/r) + 0.5 * M_PI); + } +} + +static inline Polyline make_wave( + const std::vector& one_period, double width, double height, double offset, double scaleFactor, + double z_cos, double z_sin, bool vertical) +{ + std::vector points = one_period; + double period = points.back().x; + points.pop_back(); + int n = points.size(); + do { + points.emplace_back(Pointf(points[points.size()-n].x + period, points[points.size()-n].y)); + } while (points.back().x < width); + points.back().x = width; + + // and construct the final polyline to return: + Polyline polyline; + for (auto& point : points) { + point.y += offset; + point.y = clamp(0., height, double(point.y)); + if (vertical) + std::swap(point.x, point.y); + polyline.points.emplace_back(convert_to(point * scaleFactor)); + } + return polyline; } -static inline Polyline make_wave_horizontal( - double width, double height, double y0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) { - Polyline polyline; - polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor))); - double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI); - double phase_offset_cos = z_sin < 0 ? M_PI : 0.; - for (double x = 0.; x < width + segmentSize; x += segmentSize) { - x = std::min(x, width); - double a = cos(x + phase_offset_cos); - double b = - z_sin; - double res = z_cos * sin(x + phase_offset_sin); - double r = sqrt(sqr(a) + sqr(b)); - double y = clamp(0., height, asin(a/r) + asin(res/r) + 0.5 * M_PI + y0); - polyline.points.emplace_back(convert_to(Pointf(x, y) * scaleFactor)); + std::vector points; + double dx = M_PI_4; // very coarse spacing to begin with + double limit = std::min(2*M_PI, width); + for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too + x = std::min(x, limit); + points.emplace_back(Pointf(x,f(x, z_sin,z_cos, vertical, flip))); } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); - return polyline; + + // now we will check all internal points and in case some are too far from the line connecting its neighbours, + // we will add one more point on each side: + const double tolerance = .1; + for (unsigned int i=1;i tolerance) { // if the difference from straight line is more than this + double x = 0.5f * (points[i-1].x + points[i].x); + points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); + x = 0.5f * (points[i+1].x + points[i].x); + points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); + std::sort(points.begin(), points.end()); // we added the points to the end, but need them all in order + --i; // decrement i so we also check the first newly added point + } + } + return points; } static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { - double scaleFactor = scale_(line_spacing) / density_adjusted; - double segmentSize = 0.5 * density_adjusted; + const double scaleFactor = scale_(line_spacing) / density_adjusted; //scale factor for 5% : 8 712 388 // 1z = 10^-6 mm ? - double z = gridZ / scaleFactor; - double z_sin = sin(z); - double z_cos = cos(z); - Polylines result; - if (std::abs(z_sin) <= std::abs(z_cos)) { - // Vertical wave - double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.); - bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0; - for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip) - result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip)); - } else { - // Horizontal wave - bool flip = true; - for (double y0 = 0.; y0 < height; y0 += M_PI, flip = !flip) - result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip)); + const double z = gridZ / scaleFactor; + const double z_sin = sin(z); + const double z_cos = cos(z); + + bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); + double lower_bound = 0.; + double upper_bound = height; + bool flip = true; + if (vertical) { + flip = false; + lower_bound = -M_PI; + upper_bound = width - M_PI_2; + std::swap(width,height); } + + std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time + Polylines result; + + for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines + result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + + flip = !flip; // even polylines are a bit shifted + one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample + for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines + result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + return result; } @@ -90,13 +131,13 @@ void FillGyroid::_fill_surface_single( // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = params.density * 1.75; + double density_adjusted = std::max(0., params.density * 2.); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance))); - + // generate pattern Polylines polylines = make_gyroid_waves( scale_(this->z), diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 2c92e16e6..36cebeb84 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -17,12 +17,26 @@ public: struct xy { xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} + xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } + + // Rotate the point around given point about given angle (in degrees) + // shifts the result so that point of rotation is in the middle of the tower + xy rotate(const xy& origin, float width, float depth, float angle) const { + xy out(0,0); + float temp_x = x - width / 2.f; + float temp_y = y - depth / 2.f; + angle *= M_PI/180.; + out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle); + out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle); + return out + origin; + } + float x; float y; }; @@ -112,17 +126,15 @@ public: 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, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + bool last_wipe_inside_wipe_tower) = 0; // Returns gcode for toolchange and the end position. // if new_tool == -1, just unload the current filament over the wipe tower. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult finish_layer() = 0; // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 99c6c757f..ad7d91c50 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -1,3 +1,18 @@ +/* + +TODO LIST +--------- + +1. cooling moves - DONE +2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE +3. priming extrusions (last wipe must clear the color) +4. Peter's wipe tower - layer's are not exactly square +5. Peter's wipe tower - variable width for higher levels +6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) +7. Peter's wipe tower - enable enhanced first layer adhesion + +*/ + #include "WipeTowerPrusaMM.hpp" #include @@ -5,6 +20,8 @@ #include #include #include +#include +#include #include "Analyzer.hpp" @@ -16,6 +33,7 @@ #define strcasecmp _stricmp #endif + namespace Slic3r { @@ -28,13 +46,13 @@ public: m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), - m_extrusion_flow(0.f), m_layer_height(0.f), + m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f) {} Writer& set_initial_position(const WipeTower::xy &pos) { - m_start_pos = pos; + m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); m_current_pos = pos; return *this; } @@ -49,6 +67,15 @@ public: Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } + + Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle) + { m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); } + + Writer& set_y_shift(float shift) { + m_current_pos.y -= shift-m_y_shift; + m_y_shift = shift; + 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 @@ -67,8 +94,9 @@ public: const std::vector& extrusions() const { return m_extrusions; } float x() const { return m_current_pos.x; } float y() const { return m_current_pos.y; } - const WipeTower::xy& start_pos() const { return m_start_pos; } const WipeTower::xy& pos() const { return m_current_pos; } + const WipeTower::xy start_pos_rotated() const { return m_start_pos; } + const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); } float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. @@ -81,29 +109,42 @@ public: float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); + + + // For rotated wipe tower, transform position to printer coordinates + WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are + WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go + if (! m_preview_suppressed && e > 0.f && len > 0.) { // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. - float width = float(double(e) * m_filament_area / (len * m_layer_height)); + float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. - width += float(m_layer_height * (1. - M_PI / 4.)); - if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos) - m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool)); + width += m_layer_height * float(1. - M_PI / 4.); + if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) + m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); } m_gcode += "G1"; - if (x != m_current_pos.x) - m_gcode += set_format_X(x); - if (y != m_current_pos.y) - m_gcode += set_format_Y(y); + if (rot.x != rotated_current_pos.x) { + m_gcode += set_format_X(rot.x); // Transform current position back to wipe tower coordinates (was updated by set_format_X) + m_current_pos.x = x; + } + if (rot.y != rotated_current_pos.y) { + m_gcode += set_format_Y(rot.y); + m_current_pos.y = y; + } if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); - + + + + // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; @@ -130,6 +171,31 @@ public: Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) { return extrude(dest.x, dest.y, f); } + + Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f) + { + WipeTower::xy corners[4]; + corners[0] = ld; + corners[1] = WipeTower::xy(ld,width,0.f); + corners[2] = WipeTower::xy(ld,width,height); + corners[3] = WipeTower::xy(ld,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]); + } while (i != index_of_closest); + return (*this); + } Writer& load(float e, float f = 0.f) { @@ -143,7 +209,7 @@ public: m_gcode += "\n"; return *this; } - + // Derectract while moving in the X direction. // If |x| > 0, the feed rate relates to the x distance, // otherwise the feed rate relates to the e distance. @@ -198,8 +264,22 @@ public: // Set extruder temperature, don't wait by default. Writer& set_extruder_temp(int temperature, bool wait = false) { + if (temperature != current_temp) { + char buf[128]; + sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); + m_gcode += buf; + current_temp = temperature; + } + return *this; + }; + + // Wait for a period of time (seconds). + Writer& wait(float time) + { + if (time==0) + return *this; char buf[128]; - sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); + sprintf(buf, "G4 S%.3f\n", time); m_gcode += buf; return *this; }; @@ -243,6 +323,25 @@ public: return *this; }; + + Writer& set_fan(unsigned int speed) + { + if (speed == m_last_fan_speed) + return *this; + + if (speed == 0) + m_gcode += "M107\n"; + else + { + m_gcode += "M106 S"; + char buf[128]; + sprintf(buf,"%u\n",(unsigned int)(255.0 * speed / 100.0)); + m_gcode += buf; + } + m_last_fan_speed = speed; + return *this; + } + Writer& comment_material(WipeTowerPrusaMM::material_type material) { m_gcode += "; material : "; @@ -279,9 +378,17 @@ private: std::string m_gcode; std::vector m_extrusions; float m_elapsed_time; - const double m_filament_area = 0.25*M_PI*1.75*1.75; + float m_angle_deg = 0.f; + float m_y_shift = 0.f; + WipeTower::xy m_wipe_tower_pos; + float m_wipe_tower_width = 0.f; + float m_wipe_tower_depth = 0.f; + float m_last_fan_speed = 0.f; + int current_temp = -1; - std::string set_format_X(float x) { + std::string + set_format_X(float x) + { char buf[64]; sprintf(buf, " X%.3f", x); m_current_pos.x = x; @@ -315,32 +422,11 @@ private: } Writer& operator=(const Writer &rhs); -}; +}; // class Writer -/* -class Material -{ -public: - std::string name; - std::string type; +}; // namespace PrusaMultiMaterial - struct RammingStep { -// float length; - float extrusion_multiplier; // sirka linky - float extrusion; - float speed; - }; - std::vector ramming_sequence; - // Number and speed of the cooling moves. - std::vector cooling_moves; - - // Percentage of the speed overide, in pairs of - std::vector> speed_override; -}; -*/ - -} // namespace PrusaMultiMaterial WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) { @@ -365,6 +451,7 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam return INVALID; } + // Returns gcode to prime the nozzles at the front edge of the print bed. WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. @@ -373,37 +460,19 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( 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, - // May be used by a stand alone post processor. - Purpose purpose) + bool last_wipe_inside_wipe_tower) { - this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - - float wipe_area = m_wipe_area; - // Calculate the amount of wipe over the wipe tower brim following the prime, decrease wipe_area - // with the amount of material extruded over the brim. - { - // Simulate the brim extrusions, summ the length of the extrusion. - float e_length = this->tool_change(0, false, PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - // Minimum wipe area is 5mm wide. - //FIXME calculate the purge_lines_width precisely. - float purge_lines_width = 1.3f; - wipe_area = std::max(5.f, m_wipe_area - float(floor(e_length / m_wipe_tower_width)) * dy - purge_lines_width); - } this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - this->m_num_layer_changes = 0; this->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]. // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); - box_coordinates cleaning_box(xy(5.f, 0.f), m_wipe_tower_width, wipe_area); + + const float prime_section_width = std::min(240.f / tools.size(), 60.f); + box_coordinates cleaning_box(xy(5.f, 0.f), prime_section_width, 100.f); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) @@ -415,48 +484,35 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( .append(";--------------------\n") .speed_override(100); - // Always move to the starting position. - writer.set_initial_position(xy(0.f, 0.f)) - .travel(cleaning_box.ld, 7200) - // Increase the extruder driver current to allow fast ramming. - .set_extruder_trimpot(750); + writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position + .travel(cleaning_box.ld, 7200) + .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); writer.append(buf); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - float y_end = 0.f; - for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { - unsigned int tool = tools[idx_tool]; - // Select the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - // Prime the tool. - toolchange_Load(writer, cleaning_box); - if (idx_tool + 1 == tools.size()) { - // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. - if (last_wipe_inside_wipe_tower) { - // Shrink the last wipe area to the area of the other purge areas, - // remember the last initial wipe width to be purged into the 1st layer of the wipe tower. - this->m_initial_extra_wipe = std::max(0.f, wipe_area - (y_end + 0.5f * 0.85f * m_perimeter_width - cleaning_box.ld.y)); - cleaning_box.lu.y -= this->m_initial_extra_wipe; - cleaning_box.ru.y -= this->m_initial_extra_wipe; - } - toolchange_Wipe(writer, cleaning_box, false); - } 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); - // Change the extruder temperature to the temperature of the next filament before starting the cooling moves. - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_first_layer_temperature[tools[idx_tool+1]]); - // Save the y end of the non-last priming area. - y_end = writer.y(); - cleaning_box.translate(m_wipe_tower_width, 0.f); - writer.travel(cleaning_box.ld, 7200); - } - ++ m_num_tool_changes; - } - } + for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + 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); + 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; + } // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) @@ -467,8 +523,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( ";------------------\n" "\n\n"); - // Force m_idx_tool_change_in_layer to -1, so that tool_change() will know to extrude the wipe tower brim. - m_idx_tool_change_in_layer = (unsigned int)(-1); + // so that tool_change() will know to extrude the wipe tower brim: + m_print_brim = true; ToolChangeResult result; result.print_z = this->m_z_pos; @@ -476,140 +532,101 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer, Purpose purpose) +WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) { - // Either it is the last tool unload, - // or there must be a nonzero wipe tower partitions available. -// assert(tool < 0 || it_layer_tools->wipe_tower_partitions > 0); + if ( m_print_brim ) + return toolchange_Brim(); - if (m_idx_tool_change_in_layer == (unsigned int)(-1)) { - // First layer, prime the extruder. - return toolchange_Brim(purpose); - } - - float wipe_area = m_wipe_area; - if (++ m_idx_tool_change_in_layer < (unsigned int)m_max_color_changes && last_in_layer) { - // This tool_change() call will be followed by a finish_layer() call. - // Try to shrink the wipe_area to save material, as less than usual wipe is required - // if this step is foolowed by finish_layer() extrusions wiping the same extruder. - for (size_t iter = 0; iter < 3; ++ iter) { - // Simulate the finish_layer() extrusions, summ the length of the extrusion. - float e_length = 0.f; - { - unsigned int old_idx_tool_change = m_idx_tool_change_in_layer; - float old_wipe_start_y = m_current_wipe_start_y; - m_current_wipe_start_y += wipe_area; - e_length = this->finish_layer(PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - m_idx_tool_change_in_layer = old_idx_tool_change; - m_current_wipe_start_y = old_wipe_start_y; - } - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - float num_lines_extruded = floor(e_length / m_wipe_tower_width); - // Minimum wipe area is 5mm wide. - wipe_area = m_wipe_area - num_lines_extruded * dy; - if (wipe_area < 5.) { - wipe_area = 5.; + float wipe_area = 0.f; + bool last_change_in_layer = false; + 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 = wipe_volumes[b.old_tool][b.new_tool]; + if (tool == m_layer_info->tool_changes.back().new_tool) + last_change_in_layer = true; + wipe_area = b.required_depth * m_layer_info->extra_spacing; break; } - } + } + else { + // Otherwise we are going to Unload only. And m_layer_info would be invalid. } box_coordinates cleaning_box( - m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y + 0.5f * m_perimeter_width), - m_wipe_tower_width, - wipe_area - m_perimeter_width); + m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + m_wipe_tower_width - m_perimeter_width, + (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width + : m_wipe_tower_depth-m_perimeter_width)); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes) - .comment_material(m_material[m_current_tool]) - .append(";--------------------\n") - .speed_override(100); + .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? 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 + .comment_material(m_filpar[m_current_tool].material) + .append(";--------------------\n") + .speed_override(100); - xy initial_position = ((m_current_shape == SHAPE_NORMAL) ? cleaning_box.ld : cleaning_box.lu) + - xy(m_perimeter_width, ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width); + xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); + writer.set_initial_position(initial_position); - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower. - float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract; - writer // Lift for a Z hop. - .z_hop(m_zhop, 7200) - // Additional retract on move to tower. - .retract(initial_retract, 3600) - // Move to a starting position, one perimeter width inside the cleaning box. - .travel(initial_position, 7200) - // Unlift for a Z hop. - .z_hop_reset(7200) - // Additional retract on move to tower. - .load(initial_retract, 3600) - .load(m_retract, 1500); - } else { - // Already at the initial position. - writer.set_initial_position(initial_position); - } // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); writer.append(buf); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Increase the extruder driver current to allow fast ramming. - writer.set_extruder_trimpot(750); - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + // Increase the extruder driver current to allow fast ramming. + writer.set_extruder_trimpot(750); - if (tool != (unsigned int)-1) { - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], - m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]); - // This is not the last change. - // Change the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - toolchange_Load(writer, cleaning_box); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, false); - // Draw a perimeter around cleaning_box and wipe. - box_coordinates box = cleaning_box; - if (m_current_shape == SHAPE_REVERSED) { - std::swap(box.lu, box.ld); - std::swap(box.ru, box.rd); - } - // Draw a perimeter around cleaning_box. - writer.travel(box.lu, 7000) - .extrude(box.ld, 3200).extrude(box.rd) - .extrude(box.ru).extrude(box.lu); - // Wipe the nozzle. - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(box.ru, 7200) - .travel(box.lu); - } else - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_temperature[m_current_tool]); + // 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, + m_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. + } else + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); - // Reset the extruder current to a normal value. - writer.set_extruder_trimpot(550) - .feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP TOOLCHANGE END\n" - ";------------------\n" - "\n\n"); + ++ m_num_tool_changes; + m_depth_traversed += wipe_area; - ++ m_num_tool_changes; - m_current_wipe_start_y += wipe_area; - } + if (last_change_in_layer) {// draw perimeter line + writer.set_y_shift(m_y_shift); + if (m_peters_wipe_tower) + writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + else { + writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle + writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y()); + } + } + } + + writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value. + .feedrate(6000) + .flush_planner_queue() + .reset_extruder() + .append("; CP TOOLCHANGE END\n" + ";------------------\n" + "\n\n"); ToolChangeResult result; result.print_z = this->m_z_pos; @@ -617,105 +634,55 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( m_wipe_tower_pos, m_wipe_tower_width, - m_wipe_area * float(m_max_color_changes) - m_perimeter_width / 2); + m_wipe_tower_depth); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow * 1.1f) - // Let the writer know the current Z position as a base for Z-hop. - .set_z(m_z_pos) + .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) - .append( - ";-------------------------------------\n" - "; CP WIPE TOWER FIRST LAYER BRIM START\n"); + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .append(";-------------------------------------\n" + "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); - - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Move with Z hop. - writer.z_hop(m_zhop, 7200) - .travel(initial_position, 6000) - .z_hop_reset(7200); - else - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position); // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); + writer.append(buf) + .extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Prime the extruder 10*m_perimeter_width left along the vertical edge of the wipe tower. - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. + // Extrude 4 rounds of a brim around the future wipe tower. + box_coordinates box(wipeTower_box); + box.expand(m_perimeter_width); + for (size_t i = 0; i < 4; ++ i) { + writer.travel (box.ld, 7000) + .extrude(box.lu, 2100).extrude(box.ru) + .extrude(box.rd ).extrude(box.ld); + box.expand(m_perimeter_width); + } - // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. - // toolchange_Change(writer, int(tool), m_material[tool]); + writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. + writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. + .travel(wipeTower_box.ld); + writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" + ";-----------------------------------\n"); - if (sideOnly) { - float x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.ld + xy(- x_offset, y_offset), 7000) - .extrude(wipeTower_box.lu + xy(- x_offset, - y_offset), 2100); - writer.travel(wipeTower_box.rd + xy(x_offset, y_offset), 7000); - x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.rd + xy(x_offset, y_offset), 7000) - .extrude(wipeTower_box.ru + xy(x_offset, - y_offset), 2100); - } else { - // Extrude 4 rounds of a brim around the future wipe tower. - box_coordinates box(wipeTower_box); - //FIXME why is the box shifted in +Y by 0.5f * m_perimeter_width? - box.translate(0.f, 0.5f * m_perimeter_width); - box.expand(0.5f * m_perimeter_width); - for (size_t i = 0; i < 4; ++ i) { - writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); - box.expand(m_perimeter_width); - } - } - - if (m_initial_extra_wipe > m_perimeter_width * 1.9f) { - box_coordinates cleaning_box( - m_wipe_tower_pos + xy(0.f, 0.5f * m_perimeter_width), - m_wipe_tower_width, - m_initial_extra_wipe - m_perimeter_width); - writer.travel(cleaning_box.ld + xy(m_perimeter_width, 0.5f * m_perimeter_width), 6000); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, true); - // Draw a perimeter around cleaning_box. - writer.travel(cleaning_box.lu, 7000) - .extrude(cleaning_box.ld, 3200).extrude(cleaning_box.rd) - .extrude(cleaning_box.ru).extrude(cleaning_box.lu); - m_current_wipe_start_y = m_initial_extra_wipe; - } - - // Move to the front left corner. - writer.travel(wipeTower_box.ld, 7000); - - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Wipe along the front edge. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(wipeTower_box.rd) - .travel(wipeTower_box.ld); - - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" - ";-----------------------------------\n"); - - // Mark the brim as extruded. - m_idx_tool_change_in_layer = 0; - } + m_print_brim = false; // Mark the brim as extruded ToolChangeResult result; result.print_z = this->m_z_pos; @@ -723,11 +690,13 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, b result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } + + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTowerPrusaMM::toolchange_Unload( PrusaMultiMaterial::Writer &writer, @@ -735,87 +704,145 @@ void WipeTowerPrusaMM::toolchange_Unload( const material_type current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 0.5f * m_perimeter_width; - float xr = cleaning_box.rd.x - 0.5f * m_perimeter_width; - float y_step = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width; - + float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x - 1.f * m_perimeter_width; + writer.append("; CP TOOLCHANGE UNLOAD\n"); + + 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 - // Ram the hot material out of the extruder melt zone. - // Current extruder position is on the left, one perimeter inside the cleaning box in both X and Y. - float e0 = m_perimeter_width * m_extrusion_flow; - float e = (xr - xl) * m_extrusion_flow; - switch (current_material) - { - case ABS: - // ramming start end y increment amount feedrate - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.2f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.6f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000) - .ram(xr - m_perimeter_width * 2, xl + m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000); - break; - case PVA: - // Used for the PrimaSelect PVA - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 4500) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.5f, 0, 1.75f * e, 4800) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 5000); - break; - case SCAFF: - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 2.f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 3.f, 0, 2.34f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 3.f, 0, 2.63f * e, 5200); - break; - default: - // PLA, PLA/PHA and others - // Used for the Verbatim BVOH, PET, NGEN, co-polyesters - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.60f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.65f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.74f * e, 5200); + 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 + + writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position + + // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: + if (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 = m_wipe_tower_pos.y; + 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; + + //debugging: + /* float oldx = writer.x(); + float oldy = writer.y(); + writer.travel(xr,sparse_beginning_y); + writer.extrude(xr+5,writer.y()); + writer.travel(oldx,oldy);*/ + + 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 = m_wipe_tower_pos.y + sum_of_depths; + ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line + + // debugging: + /*float oldx = writer.x(); + float oldy = writer.y(); + writer.travel(xl,ramming_end_y); + writer.extrude(xl-15,writer.y()); + writer.travel(oldx,oldy);*/ + + 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 (i < m_filpar[m_current_tool].ramming_speed.size()) + { + const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); + const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / 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 0.25s, or to the next turnaround + const float actual_time = dist/x * 0.25; + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.)); + 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; + } } + WipeTower::xy end_of_ramming(writer.x(),writer.y()); - // Pull the filament end into a cooling tube. - writer.retract(15, 5000).retract(50, 5400).retract(15, 3000).retract(12, 2000); + // Pull the filament end to the BEGINNING of the cooling tube while still moving the print head + float oldx = writer.x(); + float turning_point = (!m_left_to_right ? std::max(xl,oldx-15.f) : std::min(xr,oldx+15.f) ); // so it's not too far + float xdist = std::abs(oldx-turning_point); + float edist = -(m_cooling_tube_retraction+m_cooling_tube_length/2.f-42); + writer.suppress_preview() + .load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * 83 ) // fixed speed after ramming + .load_move_x(oldx ,edist , 60.f * std::hypot(xdist,edist)/std::abs(edist) * m_filpar[m_current_tool].unloading_speed ) + .load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * m_filpar[m_current_tool].unloading_speed*0.55f ) + .load_move_x(oldx ,-12 , 60.f * std::hypot(xdist,12)/12 * m_filpar[m_current_tool].unloading_speed*0.35f ) + .resume_preview(); - if (new_temperature != 0) - // Set the extruder temperature, but don't wait. + if (new_temperature != 0) // Set the extruder temperature, but don't wait. writer.set_extruder_temp(new_temperature, false); - // In case the current print head position is closer to the left edge, reverse the direction. - if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr)) - std::swap(xl, xr); - // Horizontal cooling moves will be performed at the following Y coordinate: - writer.travel(xr, writer.y() + y_step * 0.8f, 7200) - .suppress_preview(); - switch (current_material) - { - case PVA: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); - break; - case SCAFF: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -3, 2400); - break; - default: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); +// cooling: + writer.suppress_preview(); + writer.travel(writer.x(), writer.y() + y_step); + const float start_x = writer.x(); + turning_point = ( xr-start_x > start_x-xl ? xr : xl ); + const float max_x_dist = 2*std::abs(start_x-turning_point); + const unsigned int N = 4 + std::max(0.f, (m_filpar[m_current_tool].cooling_time-14)/3); + float time = m_filpar[m_current_tool].cooling_time / float(N); + + i = 0; + while (i= cleaning_box.ld.y + m_perimeter_width)); - p = ! p) - { - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - if (skip_initial_y_move) - skip_initial_y_move = false; + const float& xl = cleaning_box.ld.x; + const float& xr = cleaning_box.rd.x; + + + // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least + // the ordered volume, even if it means violating the box. This can later be removed and simply + // wipe until the end of the assigned area. + + float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height); + float dy = m_extra_spacing*m_perimeter_width; + float wipe_speed = 1600.f; + + // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway) + if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) { + writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy); + m_left_to_right = !m_left_to_right; + } + + + // now the wiping itself: + for (int i = 0; true; ++i) { + if (i!=0) { + if (wipe_speed < 1610.f) wipe_speed = 1800.f; + else if (wipe_speed < 1810.f) wipe_speed = 2200.f; + else if (wipe_speed < 2210.f) wipe_speed = 4200.f; + else wipe_speed = std::min(4800.f, wipe_speed + 50.f); + } + + float traversed_x = writer.x(); + if (m_left_to_right) + writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); else - writer.extrude(xl - (p ? m_perimeter_width / 2 : m_perimeter_width), writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xr + (p ? m_perimeter_width : m_perimeter_width * 2), writer.y(), wipe_speed * wipe_coeff); - // Next wipe line fits the cleaning box. - if ((m_current_shape == SHAPE_NORMAL) ? - (writer.y() > cleaning_box.lu.y - m_perimeter_width) : - (writer.y() < cleaning_box.ld.y + m_perimeter_width)) + writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); + + if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width) + break; // in case next line would not fit + + traversed_x -= writer.x(); + x_to_wipe -= fabs(traversed_x); + if (x_to_wipe < WT_EPSILON) { + writer.travel(m_left_to_right ? xl + 1.5*m_perimeter_width : xr - 1.5*m_perimeter_width, writer.y(), 7200); break; - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - writer.extrude(xr + m_perimeter_width, writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xl - m_perimeter_width, writer.y()); + } + // stepping to the next line: + writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5*m_perimeter_width, writer.y() + dy); + m_left_to_right = !m_left_to_right; } - // Reset the extrusion flow. - writer.set_extrusion_flow(m_extrusion_flow); + + // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle + 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.travel(writer.x(), writer.y() - dy) + .travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y()); + } + + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. } -WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) + + + +WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. @@ -926,118 +971,82 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - // m_num_layer_changes is incremented by set_z, so it is 1 based. - .comment_with_value(" layer #", m_num_layer_changes - 1); + .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; + float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); + box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); - box_coordinates fill_box(m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y), - m_wipe_tower_width, float(m_max_color_changes) * m_wipe_area - m_current_wipe_start_y); - fill_box.expand(0.f, - 0.5f * m_perimeter_width); - { - float firstLayerOffset = 0.f; - fill_box.ld.y += firstLayerOffset; - fill_box.rd.y += firstLayerOffset; - } + if (m_left_to_right) // so there is never a diagonal travel + writer.set_initial_position(fill_box.ru); + else + writer.set_initial_position(fill_box.lu); - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - if (m_idx_tool_change_in_layer == 0) { - // There were no tool changes at all in this layer. - writer.retract(m_retract * 1.5f, 3600) - // Jump with retract to fill_box.ld + a random shift in +x. - .z_hop(m_zhop, 7200) - .travel(fill_box.ld + xy(5.f + 15.f * float(rand()) / RAND_MAX, 0.f), 7000) - .z_hop_reset(7200) - // Prime the extruder. - .load_move_x(fill_box.ld.x, m_retract * 1.5f, 3600); - } else { - // Otherwise the extruder is already over the wipe tower. - } - } else { - // The print head is inside the wipe tower. Rather move to the start of the following extrusion. - // writer.set_initial_position(fill_box.ld); - writer.set_initial_position(fill_box.ld); - } - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Extrude the first perimeter. - box_coordinates box = fill_box; - writer.extrude(box.lu, 2400 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); + box_coordinates box = fill_box; + for (int i=0;i<2;++i) { + if (m_layer_info->toolchanges_depth() < WT_EPSILON) { // there were no toolchanges on this layer + if (i==0) box.expand(m_perimeter_width); + else box.expand(-m_perimeter_width); + } + else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) + writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor); + } - // Extrude second perimeter. - box.expand(- m_perimeter_width / 2); - writer.extrude(box.lu, 3200 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); + // 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); - if (m_is_first_layer) { - // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. - box.expand(- m_perimeter_width / 2); - box.ld.y -= 0.5f * m_perimeter_width; - box.rd.y = box.ld.y; - int nsteps = int(floor((box.lu.y - box.ld.y) / (2. * (1.0 * m_perimeter_width)))); - float step = (box.lu.y - box.ld.y) / nsteps; - for (size_t i = 0; i < nsteps; ++ i) { - writer.extrude(box.ld.x, writer.y() + 0.5f * step); - writer.extrude(box.rd.x, writer.y()); - writer.extrude(box.rd.x, writer.y() + 0.5f * step); - writer.extrude(box.ld.x, writer.y()); - } - } else { - // Extrude a sparse infill to support the material to be printed above. + if (m_is_first_layer && m_adhesion) { + // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. + box.expand(-m_perimeter_width/2.f); + int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width))); + float step = (box.lu.y - box.ld.y) / nsteps; + writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f)); + if (nsteps >= 0) + for (int i = 0; i < nsteps; ++i) { + writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step); + writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step); + writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y()); + } + writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + } + else { // Extrude a sparse infill to support the material to be printed above. + const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width); + const float left = fill_box.lu.x+2*m_perimeter_width; + const float right = fill_box.ru.x - 2 * m_perimeter_width; + if (dy > m_perimeter_width) + { + // Extrude an inverse U at the left of the region. + writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f)) + .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor); - // Extrude an inverse U at the left of the region. - writer.extrude(box.ld + xy(m_perimeter_width / 2, m_perimeter_width / 2)) - .extrude(fill_box.ld + xy(m_perimeter_width * 3, m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.lu + xy(m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.lu + xy(m_perimeter_width * 6, - m_perimeter_width)) - .extrude(fill_box.ld + xy(m_perimeter_width * 6, m_perimeter_width)); + const int n = 1+(right-left)/(m_bridging); + const float dx = (right-left)/n; + for (int i=1;i<=n;++i) { + float x=left+dx*i; + writer.travel(x,writer.y()); + writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y); + } + writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + } + else + writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + } + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); - if (fill_box.lu.y - fill_box.ld.y > 4.f) { - // Extrude three zig-zags. - float step = (m_wipe_tower_width - m_perimeter_width * 12.f) / 12.f; - for (size_t i = 0; i < 3; ++ i) { - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width * 8, 3200 * speed_factor); - writer.extrude(writer.x() , fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width ); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() , fill_box.ld.y + m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width ); - } - } - - // Extrude an inverse U at the left of the region. - writer.extrude(fill_box.ru + xy(- m_perimeter_width * 6, - m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.ru + xy(- m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width * 3, m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width)); - } - - // if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - if (true) - // Wipe along the front side of the current wiping box. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(fill_box.ld + xy( m_perimeter_width, m_perimeter_width / 2), 7200) - .travel(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width / 2)); - else - writer.feedrate(7200); - - writer.append("; CP EMPTY GRID END\n" - ";------------------\n\n\n\n\n\n\n"); - - // Indicate that this wipe tower layer is fully covered. - m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes; - } + m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; ToolChangeResult result; result.print_z = this->m_z_pos; @@ -1045,9 +1054,182 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } +// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box +void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool,bool brim) +{ + assert(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 (brim) { // this toolchange prints brim - we must add it to m_plan, but not to count its depth + m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool)); + return; + } + + 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_volumes[old_tool][new_tool], 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)); +} + + + +void WipeTowerPrusaMM::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; + + for (int layer_index = 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 WipeTowerPrusaMM::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; + + for (const auto &toolchange : m_layer_info->tool_changes) + tool_change(toolchange.new_tool, false); + + float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); + float length_to_wipe = volume_to_length(wipe_volumes[m_layer_info->tool_changes.back().old_tool][m_layer_info->tool_changes.back().new_tool], + m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save; + + length_to_wipe = std::max(length_to_wipe,0.f); + 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; + + //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; + m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe; + } +} + + +// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower +// Resulting ToolChangeResults are appended into vector "result" +void WipeTowerPrusaMM::generate(std::vector> &result) +{ + if (m_plan.empty()) return; + + m_extra_spacing = 1.f; + + plan_tower(); + for (int i=0;i<5;++i) { + save_on_last_wipe(); + plan_tower(); + } + + if (m_peters_wipe_tower) + make_wipe_tower_square(); + + m_layer_info = m_plan.begin(); + + std::vector layer_result; + for (auto layer : m_plan) + { + set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z); + + + if (m_peters_wipe_tower) + m_wipe_tower_rotation_angle += 90.f; + else + m_wipe_tower_rotation_angle += 180.f; + + if (!m_peters_wipe_tower && 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; + + for (const auto &toolchange : layer.tool_changes) + layer_result.emplace_back(tool_change(toolchange.new_tool, false)); + + if (! layer_finished()) { + auto finish_layer_toolchange = finish_layer(); + if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange + auto& last_toolchange = layer_result.back(); + if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) { + char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. + sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x, finish_layer_toolchange.start_pos.y); + last_toolchange.gcode += buf; + } + last_toolchange.gcode += finish_layer_toolchange.gcode; + last_toolchange.extrusions.insert(last_toolchange.extrusions.end(),finish_layer_toolchange.extrusions.begin(),finish_layer_toolchange.extrusions.end()); + last_toolchange.end_pos = finish_layer_toolchange.end_pos; + } + else + layer_result.emplace_back(std::move(finish_layer_toolchange)); + } + + result.emplace_back(std::move(layer_result)); + m_is_first_layer = false; + } +} + + + + +void WipeTowerPrusaMM::make_wipe_tower_square() +{ + const float width = m_wipe_tower_width - 3 * m_perimeter_width; + const float depth = m_wipe_tower_depth - m_perimeter_width; + // area that we actually print into is width*depth + float side = sqrt(depth * width); + + m_wipe_tower_width = side + 3 * m_perimeter_width; + m_wipe_tower_depth = side + 2 * m_perimeter_width; + // For all layers, find how depth changed and update all toolchange depths + for (auto &lay : m_plan) + { + side = sqrt(lay.depth * width); + float width_ratio = width / side; + + //lay.extra_spacing = width_ratio; + for (auto &tch : lay.tool_changes) + tch.required_depth *= width_ratio; + } + + plan_tower(); // propagates depth downwards again (width has changed) + for (auto& lay : m_plan) // depths set, now the spacing + lay.extra_spacing = lay.depth / lay.toolchanges_depth(); + +} + + + }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index b8c7ab31f..175de0276 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -1,13 +1,14 @@ #ifndef WipeTowerPrusaMM_hpp_ #define WipeTowerPrusaMM_hpp_ -#include #include #include +#include #include #include "WipeTower.hpp" + namespace Slic3r { @@ -15,6 +16,8 @@ namespace PrusaMultiMaterial { class Writer; }; + + class WipeTowerPrusaMM : public WipeTower { public: @@ -39,65 +42,98 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(float x, float y, float width, float wipe_area, unsigned int initial_tool) : + WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, + float cooling_tube_length, float parking_pos_retraction, float bridging, const std::vector& wiping_matrix, + unsigned int initial_tool) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), - m_wipe_area(wipe_area), + m_wipe_tower_rotation_angle(rotation_angle), + m_y_shift(0.f), m_z_pos(0.f), - m_current_tool(initial_tool) + m_is_first_layer(false), + m_cooling_tube_retraction(cooling_tube_retraction), + m_cooling_tube_length(cooling_tube_length), + m_parking_pos_retraction(parking_pos_retraction), + m_bridging(bridging), + m_current_tool(initial_tool) { - for (size_t i = 0; i < 4; ++ i) { - // Extruder specific parameters. - m_material[i] = PLA; - m_temperature[i] = 0; - m_first_layer_temperature[i] = 0; - } + unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+WT_EPSILON); + for (unsigned int i = 0; i(wiping_matrix.begin()+i*number_of_extruders,wiping_matrix.begin()+(i+1)*number_of_extruders)); } virtual ~WipeTowerPrusaMM() {} - // _retract - retract value in mm - void set_retract(float retract) { m_retract = retract; } - - // _zHop - z hop value in mm - void set_zhop(float zhop) { m_zhop = zhop; } // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp) + void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, + float unloading_speed, float delay, float cooling_time, std::string ramming_parameters, float nozzle_diameter) { - m_material[idx] = material; - m_temperature[idx] = temp; - m_first_layer_temperature[idx] = first_layer_temp; + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = material; + m_filpar[idx].temperature = temp; + m_filpar[idx].first_layer_temperature = first_layer_temp; + m_filpar[idx].loading_speed = loading_speed; + m_filpar[idx].unloading_speed = unloading_speed; + m_filpar[idx].delay = delay; + m_filpar[idx].cooling_time = cooling_time; + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + std::stringstream stream{ramming_parameters}; + 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); } + + // Appends into internal structure m_plan containing info about the future wipe tower + // to be used before building begins. The entries must be added ordered in z. + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim); + + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" + void generate(std::vector> &result); + + + // Switch to a next layer. virtual void set_layer( // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, + 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, + size_t max_tool_changes, // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, + bool is_first_layer, // Is this the last layer of the waste tower? - bool is_last_layer) + bool is_last_layer) { m_z_pos = print_z; m_layer_height = layer_height; - m_max_color_changes = max_tool_changes; m_is_first_layer = is_first_layer; - m_is_last_layer = is_last_layer; - // Start counting the color changes from zero. Special case: -1 - extrude a brim first. - m_idx_tool_change_in_layer = is_first_layer ? (unsigned int)(-1) : 0; - m_current_wipe_start_y = 0.f; + m_print_brim = is_first_layer; + m_depth_traversed = 0.f; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; - ++ m_num_layer_changes; - // Extrusion rate for an extrusion aka perimeter width 0.35mm. - // Clamp the extrusion height to a 0.2mm layer height, independent of the nozzle diameter. -// m_extrusion_flow = std::min(0.2f, layer_height) * 0.145f; - // Use a strictly - m_extrusion_flow = layer_height * 0.145f; + if (is_first_layer) { + this->m_num_layer_changes = 0; + this->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); + + // 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; } // Return the wipe tower position. @@ -115,79 +151,114 @@ public: 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, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE); + 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. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose); + virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(Purpose purpose); + virtual ToolChangeResult finish_layer(); + + // Is the current layer finished? + virtual bool layer_finished() const { + return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); + } - // Is the current layer finished? A layer is finished if either the wipe tower is finished, or - // the wipe tower has been completely covered by the tool change extrusions, - // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const - { return m_idx_tool_change_in_layer == m_max_color_changes; } private: WipeTowerPrusaMM(); - // A fill-in direction (positive Y, negative Y) alternates with each layer. - enum wipe_shape + enum wipe_shape // A fill-in direction { - SHAPE_NORMAL = 1, + SHAPE_NORMAL = 1, SHAPE_REVERSED = -1 }; - // Left front corner of the wipe tower in mm. - xy m_wipe_tower_pos; - // Width of the wipe tower. - float m_wipe_tower_width; - // Per color Y span. - float m_wipe_area; - // Current Z position. - float m_z_pos = 0.f; - // Current layer height. - float m_layer_height = 0.f; - // Maximum number of color changes per layer. - size_t m_max_color_changes = 0; - // Is this the 1st layer of the print? If so, print the brim around the waste tower. - bool m_is_first_layer = false; - // Is this the last layer of this waste tower? - bool m_is_last_layer = false; + + const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet + const float Filament_Area = M_PI * 1.75f * 1.75f / 4.f; // filament area in mm^2 + 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; + + + xy 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_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + 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. + bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. // G-code generator parameters. - float m_zhop = 0.5f; - float m_retract = 4.f; - // Width of an extrusion line, also a perimeter spacing for 100% infill. - float m_perimeter_width = 0.5f; - // Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - float m_extrusion_flow = 0.029f; + float m_cooling_tube_retraction = 0.f; + float m_cooling_tube_length = 0.f; + float m_parking_pos_retraction = 0.f; + float m_bridging = 0.f; + bool m_adhesion = true; + + float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_extrusion_flow = 0.038; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. + + + struct FilamentParameters { + material_type material = PLA; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float unloading_speed = 0.f; + float delay = 0.f ; + float cooling_time = 0.f; + float ramming_line_width_multiplicator = 0.f; + float ramming_step_multiplicator = 0.f; + std::vector ramming_speed; + float nozzle_diameter; + }; // Extruder specific parameters. - material_type m_material[4]; - int m_temperature[4]; - int m_first_layer_temperature[4]; + std::vector m_filpar; - // State of the wiper tower generator. - // Layer change counter for the output statistics. - unsigned int m_num_layer_changes = 0; - // Tool change change counter for the output statistics. - unsigned int m_num_tool_changes = 0; - // Layer change counter in this layer. Counting up to m_max_color_changes. - unsigned int m_idx_tool_change_in_layer = 0; + + // 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; unsigned int m_current_tool = 0; - // Current y position at the wipe tower. - float m_current_wipe_start_y = 0.f; - // How much to wipe the 1st extruder over the wipe tower at the 1st layer - // after the wipe tower brim has been extruded? - float m_initial_extra_wipe = 0.f; + std::vector> wipe_volumes; + + float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_left_to_right = true; + float m_extra_spacing = 1.f; + + + // Calculates extrusion flow needed to produce required line width for given layer height + float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow + { + if ( layer_height < 0 ) + return m_extrusion_flow; + return layer_height * ( m_perimeter_width - layer_height * (1-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 volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.))); + } + + // 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(); + struct box_coordinates { @@ -216,14 +287,42 @@ private: } xy ld; // left down xy lu; // left upper - xy ru; // right upper xy rd; // right lower + xy ru; // right upper }; + + // to store information about tool changes for a given layer + struct WipeTowerInfo{ + struct ToolChange { + unsigned int old_tool; + unsigned int new_tool; + float required_depth; + float ramming_depth; + float first_wipe_line; + ToolChange(unsigned int old,unsigned int newtool,float depth=0.f,float ramming_depth=0.f,float fwl=0.f) + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth},first_wipe_line{fwl} {} + }; + float 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(); + + // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower + // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - ToolChangeResult toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); + ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( PrusaMultiMaterial::Writer &writer, @@ -243,11 +342,12 @@ private: void toolchange_Wipe( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, - bool skip_initial_y_move); - - void toolchange_Perimeter(); + float wipe_volume); }; + + + }; // namespace Slic3r #endif /* WipeTowerPrusaMM_hpp_ */ diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index a3f5e231f..dcece7a9b 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -183,6 +183,11 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig.single_extruder_multi_material) { + for (size_t i=1; iconfig.nozzle_diameter.values.size(); ++i) + if (this->config.nozzle_diameter.values[i] != this->config.nozzle_diameter.values[i-1]) + return "All extruders must have the same diameter for single extruder multimaterial printer."; + } + if (this->has_wipe_tower() && ! this->objects.empty()) { #if 0 for (auto dmr : this->config.nozzle_diameter.values) @@ -596,10 +612,21 @@ std::string Print::validate() const bool was_layer_height_profile_valid = object->layer_height_profile_valid; object->update_layer_height_profile(); object->layer_height_profile_valid = was_layer_height_profile_valid; - for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) + + if ( this->config.variable_layer_height ) { + PrintObject* first_object = this->objects.front(); + int i = 0; + while ( i < first_object->layer_height_profile.size() && i < object->layer_height_profile.size() ) { + if (std::abs(first_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON ) + return "The Wipe tower is only supported if all objects have the same layer height profile"; + ++i; + } + } + + /*for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) if (object->layer_height_profile[i-1] > slicing_params.object_print_z_min + EPSILON && std::abs(object->layer_height_profile[i] - object->config.layer_height) > EPSILON) - return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed."; + return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed.";*/ } } @@ -613,7 +640,12 @@ std::string Print::validate() const for (unsigned int extruder_id : extruders) nozzle_diameters.push_back(this->config.nozzle_diameter.get_at(extruder_id)); double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); - + + unsigned int total_extruders_count = this->config.nozzle_diameter.size(); + for (const auto& extruder_idx : extruders) + if ( extruder_idx >= total_extruders_count ) + return "One or more object were assigned an extruder that the printer does not have."; + for (PrintObject *object : this->objects) { if ((object->config.support_material_extruder == -1 || object->config.support_material_interface_extruder == -1) && (object->config.raft_layers > 0 || object->config.support_material.value)) { @@ -1026,22 +1058,33 @@ void Print::_make_wipe_tower() } } + // Get wiping matrix to get number of extruders and convert vector to vector: + std::vector wiping_volumes((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end()); + // Initialize the wipe tower. WipeTowerPrusaMM wipe_tower( float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), - float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), - m_tool_ordering.first_extruder()); - + float(this->config.wipe_tower_width.value), + float(this->config.wipe_tower_rotation_angle.value), float(this->config.cooling_tube_retraction.value), + float(this->config.cooling_tube_length.value), float(this->config.parking_pos_retraction.value), + float(this->config.wipe_tower_bridging), wiping_volumes, m_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 < 4; ++ i) + for (size_t i = 0; i < (int)(sqrt(wiping_volumes.size())+EPSILON); ++ i) wipe_tower.set_extruder( i, WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()), this->config.temperature.get_at(i), - this->config.first_layer_temperature.get_at(i)); + this->config.first_layer_temperature.get_at(i), + this->config.filament_loading_speed.get_at(i), + this->config.filament_unloading_speed.get_at(i), + this->config.filament_toolchange_delay.get_at(i), + this->config.filament_cooling_time.get_at(i), + this->config.filament_ramming_parameters.get_at(i), + this->config.nozzle_diameter.get_at(i)); // When printing the first layer's wipe tower, the first extruder is expected to be active and primed. // Therefore the number of wipe sections at the wipe tower will be (m_tool_ordering.front().extruders-1) at the 1st layer. @@ -1049,12 +1092,37 @@ void Print::_make_wipe_tower() bool last_priming_wipe_full = m_tool_ordering.front().extruders.size() > m_tool_ordering.front().wipe_tower_partitions; m_wipe_tower_priming = Slic3r::make_unique( - wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full)); + + + // Lets go through the wipe tower layers and determine pairs of extruder changes for each + // to pass to wipe_tower (so that it can use it for planning the layout of the tower) + { + unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + for (const auto &layer_tools : m_tool_ordering.layer_tools()) { // for all layers + if (!layer_tools.has_wipe_tower) continue; + bool first_layer = &layer_tools == &m_tool_ordering.front(); + wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); + for (const auto extruder_id : layer_tools.extruders) { + if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { + wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back()); + current_extruder_id = extruder_id; + } + } + if (&layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) + break; + } + } + + // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); + wipe_tower.generate(m_wipe_tower_tool_changes); + // Set current_extruder_id to the last extruder primed. - unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + /*unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { if (! layer_tools.has_wipe_tower) // This is a support only layer, or the wipe tower does not reach to this height. @@ -1098,7 +1166,7 @@ void Print::_make_wipe_tower() m_wipe_tower_tool_changes.emplace_back(std::move(tool_changes)); if (last_layer) break; - } + }*/ // Unload the current filament over the purge tower. coordf_t layer_height = this->objects.front()->config.layer_height.value; @@ -1117,7 +1185,7 @@ void Print::_make_wipe_tower() wipe_tower.set_layer(float(m_tool_ordering.back().print_z), float(layer_height), 0, false, true); } m_wipe_tower_final_purge = Slic3r::make_unique( - wipe_tower.tool_change((unsigned int)-1, false, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.tool_change((unsigned int)-1, false)); } std::string Print::output_filename() diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 657e5a452..1e7e0bacc 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -166,6 +166,22 @@ PrintConfigDef::PrintConfigDef() def->cli = "cooling!"; def->default_value = new ConfigOptionBools { true }; + 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->cli = "cooling_tube_retraction=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(91.5f); + + 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->cli = "cooling_tube_length=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(5.f); + def = this->add("default_acceleration", coFloat); def->label = L("Default"); def->tooltip = L("This is the acceleration your printer will be reset to after " @@ -439,6 +455,49 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; + 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->cli = "filament-loading-speed=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 28. }; + + 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->cli = "filament-unloading-speed=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 90. }; + + 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->cli = "filament-toolchange-delay=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 0. }; + + def = this->add("filament_cooling_time", coFloats); + def->label = L("Cooling time"); + def->tooltip = L("The filament is slowly moved back and forth after retraction into the cooling tube " + "for this amount of time."); + def->cli = "filament_cooling_time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 14.f }; + + 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->cli = "filament-ramming-parameters=s@"; + def->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_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper " @@ -977,6 +1036,15 @@ PrintConfigDef::PrintConfigDef() def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); + 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->cli = "parking_pos_retraction=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(92.f); + def = this->add("perimeter_acceleration", coFloat); def->label = L("Perimeters"); def->tooltip = L("This is the acceleration your printer will use for perimeters. " @@ -1744,6 +1812,25 @@ PrintConfigDef::PrintConfigDef() def->cli = "wipe-tower!"; def->default_value = new ConfigOptionBool(false); + 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->cli = "wiping-volumes-extruders=f@"; + def->default_value = new ConfigOptionFloats { 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f }; + + def = this->add("wiping_volumes_matrix", coFloats); + def->label = L("Purging volumes - matrix"); + def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the" + " new filament on the wipe tower for any given pair of tools. "); + def->cli = "wiping-volumes-matrix=f@"; + def->default_value = new ConfigOptionFloats { 0.f, 140.f, 140.f, 140.f, 140.f, + 140.f, 0.f, 140.f, 140.f, 140.f, + 140.f, 140.f, 0.f, 140.f, 140.f, + 140.f, 140.f, 140.f, 0.f, 140.f, + 140.f, 140.f, 140.f, 140.f, 0.f }; + def = this->add("wipe_tower_x", coFloat); def->label = L("Position X"); def->tooltip = L("X coordinate of the left front corner of a wipe tower"); @@ -1765,14 +1852,19 @@ PrintConfigDef::PrintConfigDef() def->cli = "wipe-tower-width=f"; def->default_value = new ConfigOptionFloat(60.); - def = this->add("wipe_tower_per_color_wipe", coFloat); - def->label = L("Per color change depth"); - def->tooltip = L("Depth of a wipe color per color change. For N colors, there will be " - "maximum (N-1) tool switches performed, therefore the total depth " - "of the wipe tower will be (N-1) times this value."); + 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("degrees"); + def->cli = "wipe-tower-rotation-angle=f"; + def->default_value = new ConfigOptionFloat(0.); + + 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->cli = "wipe-tower-per-color-wipe=f"; - def->default_value = new ConfigOptionFloat(15.); + def->cli = "wipe-tower-bridging=f"; + def->default_value = new ConfigOptionFloat(10.); def = this->add("xy_size_compensation", coFloat); def->label = L("XY Size Compensation"); @@ -1852,8 +1944,9 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", "seal_position", "vibration_limit", "bed_size", "octoprint_host", - "print_center", "g0", "threads", "pressure_advance" + "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" }; + if (ignore.find(opt_key) != ignore.end()) { opt_key = ""; return; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index cb94a7921..967a87310 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -154,6 +154,13 @@ public: // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); + + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override + { PrintConfigDef::handle_legacy(opt_key, value); } }; template @@ -466,6 +473,11 @@ public: ConfigOptionBools filament_soluble; ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; + ConfigOptionFloats filament_loading_speed; + ConfigOptionFloats filament_unloading_speed; + ConfigOptionFloats filament_toolchange_delay; + ConfigOptionFloats filament_cooling_time; + ConfigOptionStrings filament_ramming_parameters; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; @@ -491,7 +503,11 @@ public: ConfigOptionBool use_relative_e_distances; ConfigOptionBool use_volumetric_e; ConfigOptionBool variable_layer_height; - + ConfigOptionFloat cooling_tube_retraction; + ConfigOptionFloat cooling_tube_length; + ConfigOptionFloat parking_pos_retraction; + + std::string get_extrusion_axis() const { return @@ -515,6 +531,11 @@ protected: OPT_PTR(filament_soluble); OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); + OPT_PTR(filament_loading_speed); + OPT_PTR(filament_unloading_speed); + OPT_PTR(filament_toolchange_delay); + OPT_PTR(filament_cooling_time); + OPT_PTR(filament_ramming_parameters); OPT_PTR(gcode_comments); OPT_PTR(gcode_flavor); OPT_PTR(layer_gcode); @@ -540,6 +561,9 @@ protected: OPT_PTR(use_relative_e_distances); OPT_PTR(use_volumetric_e); OPT_PTR(variable_layer_height); + OPT_PTR(cooling_tube_retraction); + OPT_PTR(cooling_tube_length); + OPT_PTR(parking_pos_retraction); } }; @@ -610,6 +634,10 @@ public: ConfigOptionFloat wipe_tower_y; ConfigOptionFloat wipe_tower_width; ConfigOptionFloat wipe_tower_per_color_wipe; + ConfigOptionFloat wipe_tower_rotation_angle; + ConfigOptionFloat wipe_tower_bridging; + ConfigOptionFloats wiping_volumes_matrix; + ConfigOptionFloats wiping_volumes_extruders; ConfigOptionFloat z_offset; protected: @@ -675,6 +703,10 @@ protected: OPT_PTR(wipe_tower_y); OPT_PTR(wipe_tower_width); OPT_PTR(wipe_tower_per_color_wipe); + OPT_PTR(wipe_tower_rotation_angle); + OPT_PTR(wipe_tower_bridging); + OPT_PTR(wiping_volumes_matrix); + OPT_PTR(wiping_volumes_extruders); OPT_PTR(z_offset); } }; @@ -713,6 +745,7 @@ class FullPrintConfig : public: // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); + protected: // Protected constructor to be called to initialize ConfigCache::m_default. FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {} diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index a459b7cbf..7298bc131 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -420,12 +420,17 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs) { float color[4] = { 1.0f, 1.0f, 0.0f, 0.5f }; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); - auto mesh = make_cube(width, depth, height); + + auto mesh = make_cube(width, depth, height); + mesh.translate(-width/2.f,-depth/2.f,0.f); + Point origin_of_rotation(0.f,0.f); + mesh.rotate(rotation_angle,&origin_of_rotation); + if (use_VBOs) v.indexed_vertex_array.load_mesh_full_shading(mesh); else @@ -2594,8 +2599,10 @@ void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, boo coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z; const PrintConfig& config = print.config; unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) - volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, config.wipe_tower_per_color_wipe * (extruders_count - 1), max_z, use_VBOs); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete + volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs); + } } -} +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 0fd31d8d6..f94dda066 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -271,7 +271,7 @@ public: const std::string &drag_by); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box; @@ -392,7 +392,7 @@ public: bool use_VBOs); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); // Render the volumes by OpenGL. void render_VBOs() const; diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 24d84a7df..3dd60ef88 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -34,7 +34,7 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) { -// on_change(nullptr); +// on_change(nullptr); auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 9450f44ae..d350584c1 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1,4 +1,5 @@ #include "GUI.hpp" +#include "WipeTowerDialog.hpp" #include #include @@ -185,6 +186,7 @@ wxLocale* g_wxLocale; std::shared_ptr m_optgroup; double m_brim_width = 0.0; +wxButton* g_wiping_dialog_button = nullptr; static void init_label_colours() { @@ -714,6 +716,33 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl option = Option(def, "brim"); m_optgroup->append_single_option_line(option); + + Line line = { _(L("")), "" }; + line.widget = [config](wxWindow* parent){ + g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + "\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(g_wiping_dialog_button); + g_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) + { + auto &config = g_PresetBundle->project_config; + std::vector init_matrix = (config.option("wiping_volumes_matrix"))->values; + std::vector init_extruders = (config.option("wiping_volumes_extruders"))->values; + + WipingDialog dlg(parent,std::vector(init_matrix.begin(),init_matrix.end()),std::vector(init_extruders.begin(),init_extruders.end())); + + if (dlg.ShowModal() == wxID_OK) { + std::vector matrix = dlg.get_matrix(); + std::vector extruders = dlg.get_extruders(); + (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); + (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); + } + })); + return sizer; + }; + m_optgroup->append_line(line); + + + sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxBottom, 1); } @@ -722,6 +751,12 @@ ConfigOptionsGroup* get_optgroup() return m_optgroup.get(); } + +wxButton* get_wiping_dialog_button() +{ + return g_wiping_dialog_button; +} + wxWindow* export_option_creator(wxWindow* parent) { wxPanel* panel = new wxPanel(parent, -1); @@ -765,6 +800,9 @@ int get_export_option(wxFileDialog* dlg) } return 0; + } -} } + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 24c3ec3f4..28c450eb8 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -18,6 +18,7 @@ class wxArrayLong; class wxColour; class wxBoxSizer; class wxFlexGridSizer; +class wxButton; class wxFileDialog; namespace Slic3r { @@ -133,6 +134,7 @@ wxString from_u8(const std::string &str); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); ConfigOptionsGroup* get_optgroup(); +wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 24e1ddf2e..48d81bebd 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -149,7 +149,7 @@ void OptionsGroup::append_line(const Line& line) { // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { auto wgt = line.widget(parent()); - grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5); + grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 1); return; } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index c437f8b41..5b3363a19 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -199,8 +199,7 @@ const std::vector& Preset::print_options() "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_per_color_wipe", - "compatible_printers", "compatible_printers_condition", "inherits" + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers", "compatible_printers_condition","inherits" }; return s_opts; } @@ -208,12 +207,12 @@ const std::vector& Preset::print_options() const std::vector& Preset::filament_options() { static std::vector s_opts { - "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", - "extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature", - "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", - "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", - "end_filament_gcode", - "compatible_printers", "compatible_printers_condition", "inherits" + "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", + "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay", + "filament_cooling_time", "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature", + "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", + "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", + "compatible_printers_condition", "inherits" }; return s_opts; } @@ -226,8 +225,8 @@ const std::vector& Preset::printer_options() "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", - "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "max_print_height", - "default_print_profile", "inherits", + "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", + "cooling_tube_length", "parking_pos_retraction", "max_print_height", "default_print_profile", "inherits", }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 7131bf771..e80c9ce48 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -33,6 +33,11 @@ namespace Slic3r { +static std::vector s_project_options { + "wiping_volumes_extruders", + "wiping_volumes_matrix" +}; + PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options()), filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), @@ -68,6 +73,8 @@ PresetBundle::PresetBundle() : this->filaments.load_bitmap_default("spool.png"); this->printers .load_bitmap_default("printer_empty.png"); this->load_compatible_bitmaps(); + + this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options); } PresetBundle::~PresetBundle() @@ -293,6 +300,7 @@ DynamicPrintConfig PresetBundle::full_config() const out.apply(FullPrintConfig()); out.apply(this->prints.get_edited_preset().config); out.apply(this->printers.get_edited_preset().config); + out.apply(this->project_config); auto *nozzle_diameter = dynamic_cast(out.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); @@ -482,6 +490,9 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } + // 4) Load the project config values (the per extruder wipe matrix etc). + this->project_config.apply_only(config, s_project_options); + this->update_compatible_with_printer(false); } @@ -873,6 +884,34 @@ void PresetBundle::update_multi_material_filament_presets() // Append the rest of filament presets. // if (this->filament_presets.size() < num_extruders) this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); + + + // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): + std::vector old_matrix = this->project_config.option("wiping_volumes_matrix")->values; + size_t old_number_of_extruders = int(sqrt(old_matrix.size())+EPSILON); + if (num_extruders != old_number_of_extruders) { + // First verify if purging volumes presets for each extruder matches number of extruders + std::vector& extruders = this->project_config.option("wiping_volumes_extruders")->values; + while (extruders.size() < 2*num_extruders) { + extruders.push_back(extruders.size()>1 ? extruders[0] : 50.); // copy the values from the first extruder + extruders.push_back(extruders.size()>1 ? extruders[1] : 50.); + } + while (extruders.size() > 2*num_extruders) { + extruders.pop_back(); + extruders.pop_back(); + } + + std::vector new_matrix; + for (unsigned int i=0;iproject_config.option("wiping_volumes_matrix")->values = new_matrix; + } } void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index f481ba374..8e89590ac 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -44,6 +44,11 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; + // The project configuration values are kept separated from the print/filament/printer preset, + // they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode, + // and they are being used by slicing core. + DynamicPrintConfig project_config; + // There will be an entry for each system profile loaded, // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. std::set vendors; diff --git a/xs/src/slic3r/GUI/RammingChart.cpp b/xs/src/slic3r/GUI/RammingChart.cpp new file mode 100644 index 000000000..1ef43be02 --- /dev/null +++ b/xs/src/slic3r/GUI/RammingChart.cpp @@ -0,0 +1,284 @@ +#include +#include + +#include "RammingChart.hpp" + + +//! macro used to mark string used at localization, +//! return same string +#define L(s) s + + + +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 + + dc.SetPen(*wxBLACK_PEN); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle(m_rect); + + if (visible_area.m_width < 0.499) { + dc.DrawText("NO RAMMING AT ALL",wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,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() { + 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; + } + } + std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; }); + + m_line_to_draw.clear(); + m_total_volume = 0.f; + + + // 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(button.get_pos().m_x,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/xs/src/slic3r/GUI/RammingChart.hpp b/xs/src/slic3r/GUI/RammingChart.hpp new file mode 100644 index 000000000..7d3b9a962 --- /dev/null +++ b/xs/src/slic3r/GUI/RammingChart.hpp @@ -0,0 +1,115 @@ +#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) : + wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()) + { + SetBackgroundStyle(wxBG_STYLE_PAINT); + m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50)); + 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 + + 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/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 7db35f5cf..59994ecc3 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -3,9 +3,11 @@ #include "PresetBundle.hpp" #include "PresetHints.hpp" #include "../../libslic3r/Utils.hpp" + #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/OctoPrint.hpp" #include "BonjourDialog.hpp" +#include "WipeTowerDialog.hpp" #include #include @@ -582,10 +584,27 @@ void Tab::on_value_change(std::string opt_key, boost::any value) get_optgroup()->set_value("brim", val); } - + if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) + update_wiping_button_visibility(); + update(); } + +// Show/hide the 'purging volumes' button +void Tab::update_wiping_button_visibility() { + bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; + bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; + bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; + + if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) + get_wiping_dialog_button()->Show(); + else get_wiping_dialog_button()->Hide(); + + (get_wiping_dialog_button()->GetParent())->Layout(); +} + + // Call a callback to update the selection of presets on the platter: // To update the content of the selection boxes, // to update the filament colors of the selection boxes, @@ -621,6 +640,8 @@ void Tab::update_frequently_changed_parameters() bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; get_optgroup()->set_value("brim", val); + + update_wiping_button_visibility(); } void Tab::reload_compatible_printers_widget() @@ -770,7 +791,8 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_x"); optgroup->append_single_option_line("wipe_tower_y"); optgroup->append_single_option_line("wipe_tower_width"); - optgroup->append_single_option_line("wipe_tower_per_color_wipe"); + optgroup->append_single_option_line("wipe_tower_rotation_angle"); + optgroup->append_single_option_line("wipe_tower_bridging"); optgroup = page->new_optgroup(_(L("Advanced"))); optgroup->append_single_option_line("interface_shells"); @@ -1015,53 +1037,40 @@ void TabPrint::update() } bool have_perimeters = m_config->opt_int("perimeters") > 0; - std::vector vec_enable = { "extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }; - for (auto el : vec_enable) + for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", + "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) get_field(el)->toggle(have_perimeters); bool have_infill = m_config->option("fill_density")->value > 0; - vec_enable.resize(0); - vec_enable = { "fill_pattern", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }; // infill_extruder uses the same logic as in Print::extruders() - for (auto el : vec_enable) + for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", + "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) get_field(el)->toggle(have_infill); bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; - vec_enable.resize(0); - vec_enable = { "external_fill_pattern", "infill_first", "solid_infill_extruder", - "solid_infill_extrusion_width", "solid_infill_speed" }; // solid_infill_extruder uses the same logic as in Print::extruders() - for (auto el : vec_enable) + for (auto el : {"external_fill_pattern", "infill_first", "solid_infill_extruder", + "solid_infill_extrusion_width", "solid_infill_speed" }) get_field(el)->toggle(have_solid_infill); - vec_enable.resize(0); - vec_enable = { "fill_angle", "bridge_angle", "infill_extrusion_width", - "infill_speed", "bridge_speed" }; - for (auto el : vec_enable) + for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", + "infill_speed", "bridge_speed" }) get_field(el)->toggle(have_infill || have_solid_infill); get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; - vec_enable.resize(0); - vec_enable = { "top_infill_extrusion_width", "top_solid_infill_speed" }; - for (auto el : vec_enable) + for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) get_field(el)->toggle(have_top_solid_infill); bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; - vec_enable.resize(0); - vec_enable = { "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration" }; - for (auto el : vec_enable) + for (auto el : {"perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration" }) get_field(el)->toggle(have_default_acceleration); bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; - vec_enable.resize(0); - vec_enable = { "skirt_distance", "skirt_height" }; - for (auto el : vec_enable) + for (auto el : { "skirt_distance", "skirt_height" }) get_field(el)->toggle(have_skirt); bool have_brim = m_config->opt_float("brim_width") > 0; @@ -1072,18 +1081,14 @@ void TabPrint::update() bool have_support_material = m_config->opt_bool("support_material") || have_raft; bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; - vec_enable.resize(0); - vec_enable = { "support_material_threshold", "support_material_pattern", "support_material_with_sheath", + for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_angle", "support_material_interface_layers", "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", - "support_material_xy_spacing" }; - for (auto el : vec_enable) + "support_material_xy_spacing" }) get_field(el)->toggle(have_support_material); - vec_enable.resize(0); - vec_enable = { "support_material_interface_spacing", "support_material_interface_extruder", - "support_material_interface_speed", "support_material_interface_contact_loops" }; - for (auto el : vec_enable) + for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", + "support_material_interface_speed", "support_material_interface_contact_loops" }) get_field(el)->toggle(have_support_material && have_support_interface); get_field("support_material_synchronize_layers")->toggle(have_support_soluble); @@ -1092,18 +1097,14 @@ void TabPrint::update() get_field("support_material_speed")->toggle(have_support_material || have_brim || have_skirt); bool have_sequential_printing = m_config->opt_bool("complete_objects"); - vec_enable.resize(0); - vec_enable = { "extruder_clearance_radius", "extruder_clearance_height" }; - for (auto el : vec_enable) + for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) get_field(el)->toggle(have_sequential_printing); bool have_ooze_prevention = m_config->opt_bool("ooze_prevention"); get_field("standby_temperature_delta")->toggle(have_ooze_prevention); bool have_wipe_tower = m_config->opt_bool("wipe_tower"); - vec_enable.resize(0); - vec_enable = { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_per_color_wipe" }; - for (auto el : vec_enable) + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging"}) get_field(el)->toggle(have_wipe_tower); m_recommended_thin_wall_thickness_description_line->SetText( @@ -1183,7 +1184,29 @@ void TabFilament::build() }; optgroup->append_line(line); - page = add_options_page(_(L("Custom G-code")), "cog.png"); + optgroup = page->new_optgroup(_(L("Toolchange behaviour"))); + optgroup->append_single_option_line("filament_loading_speed"); + optgroup->append_single_option_line("filament_unloading_speed"); + optgroup->append_single_option_line("filament_toolchange_delay"); + optgroup->append_single_option_line("filament_cooling_time"); + line = { _(L("Ramming")), "" }; + line.widget = [this](wxWindow* parent){ + auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + 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) + (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); + })); + return sizer; + }; + optgroup->append_line(line); + + + page = add_options_page(_(L("Custom G-code")), "cog.png"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); Option option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; @@ -1240,13 +1263,10 @@ void TabFilament::update() bool cooling = m_config->opt_bool("cooling", 0); bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); - std::vector vec_enable = { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }; - for (auto el : vec_enable) + for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) get_field(el)->toggle(cooling); - vec_enable.resize(0); - vec_enable = { "min_fan_speed", "disable_fan_first_layers" }; - for (auto el : vec_enable) + for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); } @@ -1325,9 +1345,11 @@ void TabPrinter::build() optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){ size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); wxTheApp->CallAfter([this, opt_key, value, extruders_count](){ - if (opt_key.compare("extruders_count")==0) { + if (opt_key.compare("extruders_count")==0 || opt_key.compare("single_extruder_multi_material")==0) { extruders_count_changed(extruders_count); update_dirty(); + if (opt_key.compare("single_extruder_multi_material")==0) // the single_extruder_multimaterial was added to force pages + on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped } else { update_dirty(); @@ -1336,6 +1358,7 @@ void TabPrinter::build() }); }; + if (!m_no_controller) { optgroup = page->new_optgroup(_(L("USB/Serial connection"))); @@ -1606,6 +1629,25 @@ void TabPrinter::build_extruder_pages(){ for (auto page_extruder : m_extruder_pages) m_pages.push_back(page_extruder); m_pages.push_back(page_note); + + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i=0;ititle().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin()+i); + break; + } + if ( m_extruder_pages.size()>1 && m_config->opt_bool("single_extruder_multi_material")) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png",true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); + m_pages.insert(m_pages.begin()+1,page); + } + } + rebuild_page_tree(); } diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index f39ff728c..c3a6fb573 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -194,6 +194,7 @@ public: protected: void on_presets_changed(); void update_frequently_changed_parameters(); + void update_wiping_button_visibility(); }; //Slic3r::GUI::Tab::Print; diff --git a/xs/src/slic3r/GUI/WipeTowerDialog.cpp b/xs/src/slic3r/GUI/WipeTowerDialog.cpp new file mode 100644 index 000000000..b34de0745 --- /dev/null +++ b/xs/src/slic3r/GUI/WipeTowerDialog.cpp @@ -0,0 +1,343 @@ +#include +#include +#include "WipeTowerDialog.hpp" + +#include + +//! macro used to mark string used at localization, +//! return same string +#define L(s) s + + + +RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters) +: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + 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. + this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + 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); + + 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(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(); +} + + + + + +RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) +: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/) +{ + 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(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f); + m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor + sizer_chart->Add(m_chart, 0, wxALL, 5); + + m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5); + m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0); + m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); + m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); + + auto gsizer_param = new wxFlexGridSizer(2, 5, 15); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time (s):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_time); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume (mm"))+"\u00B3):")), 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, 100); + + 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(this); +} + +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(); +} + + +#define ITEM_WIDTH 60 +// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode: +WipingDialog::WipingDialog(wxWindow* parent,const std::vector& matrix, const std::vector& extruders) +: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize); + m_panel_wiping = new WipingPanel(this,matrix,extruders, widget_button); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + + // set min sizer width according to extruders count + const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH); + main_sizer->SetMinSize(wxSize(sizer_width, -1)); + + main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); }); + + this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked.. + m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values + m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...() + EndModal(wxID_OK); + },wxID_OK); + + this->Show(); +} + +// This function allows to "play" with sizers parameters (like align or border) +void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/) +{ + sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15); + auto table_sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); + table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); + table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10); +} + +// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers) +WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, wxButton* widget_button) +: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/) +{ + m_widget_button = widget_button; // pointer to the button in parent dialog + m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); }); + + m_number_of_extruders = (int)(sqrt(matrix.size())+0.001); + + // Create two switched panels with their own sizers + m_sizer_simple = new wxBoxSizer(wxVERTICAL); + m_sizer_advanced = new wxBoxSizer(wxVERTICAL); + m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_simple->SetSizer(m_sizer_simple); + m_page_advanced->SetSizer(m_sizer_advanced); + + auto gridsizer_simple = new wxGridSizer(3, 5, 10); + m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1); + + // First create controls for advanced mode and assign them to m_page_advanced: + for (unsigned int i = 0; i < m_number_of_extruders; ++i) { + edit_boxes.push_back(std::vector(0)); + + for (unsigned int j = 0; j < m_number_of_extruders; ++j) { + edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1))); + if (i == j) + edit_boxes[i][j]->Disable(); + else + edit_boxes[i][j]->SetValue(wxString("") << int(matrix[m_number_of_extruders*j + i])); + } + } + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString(""))); + for (unsigned int i = 0; i < m_number_of_extruders; ++i) + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + for (unsigned int i = 0; i < m_number_of_extruders; ++i) { + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + for (unsigned int j = 0; j < m_number_of_extruders; ++j) + m_gridsizer_advanced->Add(edit_boxes[j][i], 0); + } + + // collect and format sizer + format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, + _(L("Here you can adjust required purging volume (mm\u00B3) for any given pair of tools.")), + _(L("Extruder changed to"))); + + // Hide preview page before new page creating + // It allows to do that from a beginning of the main panel + m_page_advanced->Hide(); + + // Now the same for simple mode: + gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + + for (unsigned int i=0;iAdd(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(m_old.back(),0); + gridsizer_simple->Add(m_new.back(),0); + } + + // collect and format sizer + format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple, + _(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")), + _(L("Volume to purge (mm\u00B3) when the filament is being")), 50); + + m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25); + m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25); + + m_sizer->SetSizeHints(this); + SetSizer(m_sizer); + + toggle_advanced(); // to show/hide what is appropriate + + m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) { + wxPaintDC dc(m_page_advanced); + int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y); + wxString label = _(L("From")); + int text_width = 0; + int text_height = 0; + dc.GetTextExtent(label,&text_width,&text_height); + int xpos = m_gridsizer_advanced->GetPosition().x; + dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90); + }); +} + + + + +// Reads values from the (advanced) wiping matrix: +std::vector WipingPanel::read_matrix_values() { + if (!m_advanced) + fill_in_matrix(); + std::vector output; + for (unsigned int i=0;iGetValue().ToDouble(&val); + output.push_back((float)val); + } + } + return output; +} + +// Reads values from simple mode to save them for next time: +std::vector WipingPanel::read_extruders_values() { + std::vector output; + for (unsigned int i=0;iGetValue()); + output.push_back(m_new[i]->GetValue()); + } + return output; +} + +// This updates the "advanced" matrix based on values from "simple" mode +void WipingPanel::fill_in_matrix() { + for (unsigned i=0;iSetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())); + } + } +} + + + +// Function to check if simple and advanced settings are matching +bool WipingPanel::advanced_matches_simple() { + for (unsigned i=0;iGetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()))) + return false; + } + } + return true; +} + + +// Switches the dialog from simple to advanced mode and vice versa +void WipingPanel::toggle_advanced(bool user_action) { + if (m_advanced && !advanced_matches_simple() && user_action) { + if (wxMessageDialog(this,wxString(_(L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"))), + wxString(_(L("Warning"))),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES) + return; + } + if (user_action) + m_advanced = !m_advanced; // user demands a change -> toggle + else + m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate + + (m_advanced ? m_page_advanced : m_page_simple)->Show(); + (!m_advanced ? m_page_advanced : m_page_simple)->Hide(); + + m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings"))); + if (m_advanced) + if (user_action) fill_in_matrix(); // otherwise keep values loaded from config + + m_sizer->Layout(); + Refresh(); +} diff --git a/xs/src/slic3r/GUI/WipeTowerDialog.hpp b/xs/src/slic3r/GUI/WipeTowerDialog.hpp new file mode 100644 index 000000000..d858062da --- /dev/null +++ b/xs/src/slic3r/GUI/WipeTowerDialog.hpp @@ -0,0 +1,90 @@ +#ifndef _WIPE_TOWER_DIALOG_H_ +#define _WIPE_TOWER_DIALOG_H_ + +#include +#include +#include +#include +#include + +#include "RammingChart.hpp" + + +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: + WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, wxButton* widget_button); + std::vector read_matrix_values(); + std::vector read_extruders_values(); + void toggle_advanced(bool user_action = false); + void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0); + +private: + void fill_in_matrix(); + bool advanced_matches_simple(); + + std::vector m_old; + std::vector m_new; + std::vector> edit_boxes; + unsigned int m_number_of_extruders = 0; + bool m_advanced = false; + wxPanel* m_page_simple = nullptr; + wxPanel* m_page_advanced = nullptr; + wxBoxSizer* m_sizer = nullptr; + wxBoxSizer* m_sizer_simple = nullptr; + wxBoxSizer* m_sizer_advanced = nullptr; + wxGridSizer* m_gridsizer_advanced = nullptr; + wxButton* m_widget_button = nullptr; +}; + + + + + +class WipingDialog : public wxDialog { +public: + WipingDialog(wxWindow* parent,const std::vector& matrix, const std::vector& extruders); + std::vector get_matrix() const { return m_output_matrix; } + std::vector get_extruders() const { return m_output_extruders; } + + +private: + WipingPanel* m_panel_wiping = nullptr; + std::vector m_output_matrix; + std::vector m_output_extruders; +}; + +#endif // _WIPE_TOWER_DIALOG_H_ \ No newline at end of file diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 25aa6b81a..915d964d1 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -84,7 +84,7 @@ std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); - int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); + int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); void erase() %code{% THIS->clear(); %}; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 84efdde53..00b469309 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -169,6 +169,8 @@ PresetCollection::arrayref() Ref print() %code%{ RETVAL = &THIS->prints; %}; Ref filament() %code%{ RETVAL = &THIS->filaments; %}; Ref printer() %code%{ RETVAL = &THIS->printers; %}; + Ref project_config() %code%{ RETVAL = &THIS->project_config; %}; + bool has_defauls_only(); std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %};