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

This commit is contained in:
bubnikv 2018-04-13 16:43:35 +02:00
commit b79692c35e
32 changed files with 2288 additions and 775 deletions

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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}' .

View File

@ -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);

View File

@ -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);

View File

@ -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]);

View File

@ -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

View File

@ -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<Point>(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<Pointf>& one_period, double width, double height, double offset, double scaleFactor,
double z_cos, double z_sin, bool vertical)
{
std::vector<Pointf> 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>(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<Pointf> 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<Point>(Pointf(x, y) * scaleFactor));
std::vector<Pointf> 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<points.size()-1;++i) {
auto& lp = points[i-1]; // left point
auto& tp = points[i]; // this point
auto& rp = points[i+1]; // right point
// calculate distance of the point to the line:
double dist_mm = unscale(scaleFactor * std::abs( (rp.y - lp.y)*tp.x + (lp.x - rp.x)*tp.y + (rp.x*lp.y - rp.y*lp.x) ) / std::hypot((rp.y - lp.y),(lp.x - rp.x)));
if (dist_mm > 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<Pointf> 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,7 +131,7 @@ 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);

View File

@ -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<unsigned int> &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,

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
#ifndef WipeTowerPrusaMM_hpp_
#define WipeTowerPrusaMM_hpp_
#include <algorithm>
#include <cmath>
#include <string>
#include <sstream>
#include <utility>
#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<float>& 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<number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders,wiping_matrix.begin()+(i+1)*number_of_extruders));
}
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<std::vector<WipeTower::ToolChangeResult>> &result);
// Switch to a next layer.
virtual void set_layer(
// Print height of this layer.
float print_z,
float print_z,
// Layer height, used to calculate extrusion the rate.
float layer_height,
float layer_height,
// Maximum number of tool changes on this layer or the layers below.
size_t max_tool_changes,
// Is this the first layer of the print? In that case print the brim first.
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<unsigned int> &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<float> 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<FilamentParameters> 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<std::vector<float>> wipe_volumes;
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
bool m_left_to_right = true;
float m_extra_spacing = 1.f;
// Calculates extrusion flow needed to produce required line width for given layer height
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
{
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<ToolChange> 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<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::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
// 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_ */

View File

@ -183,6 +183,11 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "filament_type"
|| opt_key == "filament_soluble"
|| opt_key == "first_layer_temperature"
|| opt_key == "filament_loading_speed"
|| opt_key == "filament_unloading_speed"
|| opt_key == "filament_toolchange_delay"
|| opt_key == "filament_cooling_time"
|| opt_key == "filament_ramming_parameters"
|| opt_key == "gcode_flavor"
|| opt_key == "single_extruder_multi_material"
|| opt_key == "spiral_vase"
@ -191,7 +196,12 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_width"
|| opt_key == "wipe_tower_per_color_wipe"
|| opt_key == "wipe_tower_rotation_angle"
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wiping_volumes_matrix"
|| opt_key == "parking_pos_retraction"
|| opt_key == "cooling_tube_retraction"
|| opt_key == "cooling_tube_length"
|| opt_key == "z_offset") {
steps.emplace_back(psWipeTower);
} else if (
@ -571,6 +581,12 @@ std::string Print::validate() const
return "The Spiral Vase option can only be used when printing single material objects.";
}
if (this->config.single_extruder_multi_material) {
for (size_t i=1; i<this->config.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.";*/
}
}
@ -614,6 +641,11 @@ std::string Print::validate() const
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<double> to vector<float>:
std::vector<float> 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<WipeTower::ToolChangeResult>(
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<WipeTower::ToolChangeResult>(
wipe_tower.tool_change((unsigned int)-1, false, WipeTower::PURPOSE_EXTRUDE));
wipe_tower.tool_change((unsigned int)-1, false));
}
std::string Print::output_filename()

View File

@ -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;

View File

@ -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<typename CONFIG>
@ -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<GCodeFlavor> gcode_flavor;
ConfigOptionString layer_gcode;
@ -491,6 +503,10 @@ 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
{
@ -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) {}

View File

@ -420,12 +420,17 @@ std::vector<int> 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);
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

View File

@ -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;

View File

@ -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);

View File

@ -1,4 +1,5 @@
#include "GUI.hpp"
#include "WipeTowerDialog.hpp"
#include <assert.h>
#include <cmath>
@ -185,6 +186,7 @@ wxLocale* g_wxLocale;
std::shared_ptr<ConfigOptionsGroup> 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<double> init_matrix = (config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
std::vector<double> init_extruders = (config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
WipingDialog dlg(parent,std::vector<float>(init_matrix.begin(),init_matrix.end()),std::vector<float>(init_extruders.begin(),init_extruders.end()));
if (dlg.ShowModal() == wxID_OK) {
std::vector<float> matrix = dlg.get_matrix();
std::vector<float> extruders = dlg.get_extruders();
(config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(),matrix.end());
(config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(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

View File

@ -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);

View File

@ -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;
}

View File

@ -199,8 +199,7 @@ const std::vector<std::string>& 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;
}
@ -209,11 +208,11 @@ const std::vector<std::string>& Preset::filament_options()
{
static std::vector<std::string> 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"
"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<std::string>& 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());
}

View File

@ -33,6 +33,11 @@
namespace Slic3r {
static std::vector<std::string> 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<const ConfigOptionFloats*>(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<double> old_matrix = this->project_config.option<ConfigOptionFloats>("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<double>& extruders = this->project_config.option<ConfigOptionFloats>("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<double> new_matrix;
for (unsigned int i=0;i<num_extruders;++i)
for (unsigned int j=0;j<num_extruders;++j) {
// append the value for this pair from the old matrix (if it's there):
if (i<old_number_of_extruders && j<old_number_of_extruders)
new_matrix.push_back(old_matrix[i*old_number_of_extruders + j]);
else
new_matrix.push_back( i==j ? 0. : extruders[2*i]+extruders[2*j+1]); // so it matches new extruder volumes
}
this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values = new_matrix;
}
}
void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)

View File

@ -44,6 +44,11 @@ public:
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> 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<VendorProfile> vendors;

View File

@ -0,0 +1,284 @@
#include <algorithm>
#include <wx/dcbuffer.h>
#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;i<m_line_to_draw.size()-2;++i) {
int color = 510*((m_rect.GetBottom()-(m_line_to_draw)[i])/double(m_rect.GetHeight()));
dc.SetPen( wxPen( wxColor(std::min(255,color),255-std::max(color-255,0),0), 1 ) );
dc.DrawLine(m_rect.GetLeft()+1+i,(m_line_to_draw)[i],m_rect.GetLeft()+1+i,m_rect.GetBottom());
}
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
if (splines)
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
else {
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i]);
dc.DrawLine(m_rect.GetLeft()+i+1,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
}
}
}
// draw draggable buttons
dc.SetBrush(*wxBLUE_BRUSH);
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (auto& button : m_buttons)
//dc.DrawRectangle(math_to_screen(button.get_pos())-wxPoint(side/2.,side/2.), wxSize(side,side));
dc.DrawCircle(math_to_screen(button.get_pos()),side/2.);
//dc.DrawRectangle(math_to_screen(button.get_pos()-wxPoint2DDouble(0.125,0))-wxPoint(0,5),wxSize(50,10));
// draw x-axis:
float last_mark = -10000;
for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1) {
int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x;
int y = m_rect.GetBottom();
if (x-last_mark < 50) continue;
dc.DrawLine(x,y+3,x,y-3);
dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-10,y+7));
last_mark = x;
}
// draw y-axis:
last_mark=10000;
for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) {
int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y;
int x = m_rect.GetLeft();
if (last_mark-y < 50) continue;
dc.DrawLine(x-3,y,x+3,y);
dc.DrawText(wxString()<<math_y,wxPoint(x-25,y-2/*7*/));
last_mark = y;
}
// axis labels:
wxString label = L("Time (s)");
int text_width = 0;
int text_height = 0;
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+25));
label = L("Volumetric speed (mm\u00B3/s)");
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90);
}
void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
if (!manual_points_manipulation)
return;
wxPoint point = event.GetPosition();
int button_index = which_button_is_clicked(point);
if (button_index != -1 && m_buttons.size()>2) {
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<wxPoint> 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<float> diag(N+1);
std::vector<float> mu(N+1);
std::vector<float> lambda(N+1);
std::vector<float> h(N+1);
std::vector<float> 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<points.size()-1 && points[i].x < x ) {
++i;
}
if (points[0].x > 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<float> Chart::get_ramming_speed(float sampling) const {
std::vector<float> 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<number_of_samples;++j) {
float left = screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
float right = screen_to_math(wxPoint(0,m_line_to_draw[(j+1)*dx])).m_y;
speeds_out.push_back((left+right)/2.f);
}
}
return speeds_out;
}
std::vector<std::pair<float,float>> Chart::get_buttons() const {
std::vector<std::pair<float, float>> 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()

View File

@ -0,0 +1,115 @@
#ifndef RAMMING_CHART_H_
#define RAMMING_CHART_H_
#include <vector>
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
class Chart : public wxWindow {
public:
Chart(wxWindow* parent, wxRect rect,const std::vector<std::pair<float,float>>& 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<float> get_ramming_speed(float sampling) const; //returns sampled ramming speed
std::vector<std::pair<float,float>> 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.size();++i) {
wxRect rect(math_to_screen(m_buttons[i].get_pos())-wxPoint(side/2.,side/2.),wxSize(side,side)); // bounding rectangle of this button
if ( rect.Contains(point) )
return i;
}
return (-1);
}
void recalculate_line();
void recalculate_volume();
wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates)
wxPoint m_previous_mouse;
std::vector<ButtonToDrag> m_buttons;
std::vector<int> m_line_to_draw;
wxRect2DDouble visible_area;
ButtonToDrag* m_dragged = nullptr;
float m_total_volume = 0.f;
};
#endif // RAMMING_CHART_H_

View File

@ -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 <wx/app.h>
#include <wx/button.h>
@ -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<ConfigOptionBool*>( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value;
bool multiple_extruders = dynamic_cast<ConfigOptionFloats*>((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1;
bool single_extruder_mm = dynamic_cast<ConfigOptionBool*>( (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<std::string> 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<ConfigOptionPercent>("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<ConfigOptionStrings>("filament_ramming_parameters"))->get_at(0));
if (dlg.ShowModal() == wxID_OK)
(m_config->option<ConfigOptionStrings>("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<std::string> 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<int>(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;i<m_pages.size();++i) // first make sure it's not there already
if (m_pages[i]->title().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();
}

View File

@ -194,6 +194,7 @@ public:
protected:
void on_presets_changed();
void update_frequently_changed_parameters();
void update_wiping_button_visibility();
};
//Slic3r::GUI::Tab::Print;

View File

@ -0,0 +1,343 @@
#include <algorithm>
#include <sstream>
#include "WipeTowerDialog.hpp"
#include <wx/sizer.h>
//! 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<std::pair<float, float>> 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<float> speeds = m_chart->get_ramming_speed(0.25f);
std::vector<std::pair<float,float>> 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<float>& matrix, const std::vector<float>& 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<float>& matrix, const std::vector<float>& 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<wxTextCtrl*>(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;i<m_number_of_extruders;++i) {
m_old.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i]));
m_new.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i+1]));
gridsizer_simple->Add(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<float> WipingPanel::read_matrix_values() {
if (!m_advanced)
fill_in_matrix();
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
for (unsigned int j=0;j<m_number_of_extruders;++j) {
double val = 0.;
edit_boxes[j][i]->GetValue().ToDouble(&val);
output.push_back((float)val);
}
}
return output;
}
// Reads values from simple mode to save them for next time:
std::vector<float> WipingPanel::read_extruders_values() {
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
output.push_back(m_old[i]->GetValue());
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;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
edit_boxes[j][i]->SetValue(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;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
if (edit_boxes[j][i]->GetValue() != (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();
}

View File

@ -0,0 +1,90 @@
#ifndef _WIPE_TOWER_DIALOG_H_
#define _WIPE_TOWER_DIALOG_H_
#include <wx/spinctrl.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/msgdlg.h>
#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<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button);
std::vector<float> read_matrix_values();
std::vector<float> 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<wxSpinCtrl*> m_old;
std::vector<wxSpinCtrl*> m_new;
std::vector<std::vector<wxTextCtrl*>> 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<float>& matrix, const std::vector<float>& extruders);
std::vector<float> get_matrix() const { return m_output_matrix; }
std::vector<float> get_extruders() const { return m_output_extruders; }
private:
WipingPanel* m_panel_wiping = nullptr;
std::vector<float> m_output_matrix;
std::vector<float> m_output_extruders;
};
#endif // _WIPE_TOWER_DIALOG_H_

View File

@ -84,7 +84,7 @@
std::vector<int> load_object(ModelObject *object, int obj_idx, std::vector<int> 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(); %};

View File

@ -169,6 +169,8 @@ PresetCollection::arrayref()
Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %};
Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %};
Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %};
Ref<DynamicPrintConfig> project_config() %code%{ RETVAL = &THIS->project_config; %};
bool has_defauls_only();
std::vector<std::string> filament_presets() %code%{ RETVAL = THIS->filament_presets; %};