diff --git a/My Settings b/My Settings deleted file mode 100644 index f896db7679..0000000000 --- a/My Settings +++ /dev/null @@ -1,27 +0,0 @@ -# generated by Slic3r Prusa Edition 1.38.4 on 2017-12-20 at 11:02:03 -bed_temperature = 0 -bridge_fan_speed = 100 -compatible_printers = -compatible_printers_condition = -cooling = 1 -disable_fan_first_layers = 3 -end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" -extrusion_multiplier = 1 -fan_always_on = 0 -fan_below_layer_time = 60 -filament_colour = #29b2b2 -filament_cost = 0 -filament_density = 0 -filament_diameter = 3 -filament_max_volumetric_speed = 0 -filament_notes = "" -filament_soluble = 0 -filament_type = PLA -first_layer_bed_temperature = 0 -first_layer_temperature = 205 -max_fan_speed = 100 -min_fan_speed = 35 -min_print_speed = 10 -slowdown_below_layer_time = 5 -start_filament_gcode = "; Filament gcode\n" -temperature = 200 diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index c05de06c67..13460cfed2 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1527,10 +1527,13 @@ sub draw_active_object_annotations { my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport; my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative; + my $print_object = $self->{print}->get_object($object_idx); + my $z_max = $print_object->model_object->bounding_box->z_max; + $self->{layer_height_edit_shader}->enable; $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id); $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height); - $self->{layer_height_edit_shader}->set_uniform('z_cursor', $volume->bounding_box->z_max * $z_cursor_relative); + $self->{layer_height_edit_shader}->set_uniform('z_cursor', $z_max * $z_cursor_relative); $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width}); glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, @@ -1552,8 +1555,8 @@ sub draw_active_object_annotations { glBegin(GL_QUADS); glVertex3f($bar_left, $bar_bottom, 0); glVertex3f($bar_right, $bar_bottom, 0); - glVertex3f($bar_right, $bar_top, $volume->bounding_box->z_max); - glVertex3f($bar_left, $bar_top, $volume->bounding_box->z_max); + glVertex3f($bar_right, $bar_top, $z_max); + glVertex3f($bar_left, $bar_top, $z_max); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); $self->{layer_height_edit_shader}->disable; @@ -1572,7 +1575,6 @@ sub draw_active_object_annotations { # Paint the graph. #FIXME show some kind of legend. - my $print_object = $self->{print}->get_object($object_idx); my $max_z = unscale($print_object->size->z); my $profile = $print_object->model_object->layer_height_profile; my $layer_height = $print_object->config->get('layer_height'); @@ -1677,6 +1679,11 @@ sub draw_warning { } } +sub update_volumes_colors_by_extruder { + my ($self, $config) = @_; + $self->volumes->update_colors_by_extruder($config); +} + sub opengl_info { my ($self, %params) = @_; @@ -1823,7 +1830,6 @@ sub _fragment_shader_Gouraud { return <<'FRAGMENT'; #version 110 -const vec4 OUTSIDE_COLOR = vec4(0.24, 0.42, 0.62, 1.0); const vec3 ZERO = vec3(0.0, 0.0, 0.0); // x = tainted, y = specular; @@ -1836,11 +1842,9 @@ uniform vec4 uniform_color; void main() { - // if the fragment is outside the print volume use predefined color - vec4 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? OUTSIDE_COLOR : uniform_color; - - gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 0.0) + color * intensity.x; - gl_FragColor.a = color.a; + // if the fragment is outside the print volume -> use darker color + vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); } FRAGMENT @@ -1932,8 +1936,6 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 -uniform float z_to_texture_row; - // x = tainted, y = specular; varying vec2 intensity; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 14c2d66ae5..d59865491c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -52,9 +52,8 @@ sub new { $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile - nozzle_diameter single_extruder_multi_material - wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour - max_print_height + nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width + wipe_tower_rotation_angle extruder_colour filament_colour max_print_height )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -105,7 +104,6 @@ sub new { $self->{btn_reslice}->Enable($enable); $self->{btn_print}->Enable($enable); $self->{btn_send_gcode}->Enable($enable); - $self->{btn_export_stl}->Enable($enable); }; # Initialize 3D plater @@ -738,8 +736,14 @@ sub load_model_objects { { # if the object is too large (more than 5 times the bed), scale it down my $size = $o->bounding_box->size; - my $ratio = max(@$size[X,Y]) / unscale(max(@$bed_size[X,Y])); - if ($ratio > 5) { + my $ratio = max($size->x / unscale($bed_size->x), $size->y / unscale($bed_size->y)); + if ($ratio > 10000) { + # the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, + # so scale down the mesh + $o->scale_xyz(Slic3r::Pointf3->new(1/$ratio, 1/$ratio, 1/$ratio)); + $scaled_down = 1; + } + elsif ($ratio > 5) { $_->set_scaling_factor(1/$ratio) for @{$o->instances}; $scaled_down = 1; } @@ -1915,7 +1919,8 @@ sub object_list_changed { } my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; - my $method = ($have_objects && ! $export_in_progress) ? 'Enable' : 'Disable'; + my $model_fits = $self->{model}->fits_print_volume($self->{config}); + my $method = ($have_objects && ! $export_in_progress && $model_fits) ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 09cc029302..dce60e2c44 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -210,12 +210,15 @@ 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); } } + $self->update_volumes_colors_by_extruder($self->{config}); + # checks for geometry outside the print volume to render it accordingly if (scalar @{$self->volumes} > 0) { @@ -229,6 +232,9 @@ sub reload_scene { Slic3r::GUI::_3DScene::reset_warning_texture(); $self->on_enable_action_buttons->(1) if ($self->on_enable_action_buttons); } + } else { + $self->set_warning_enabled(0); + Slic3r::GUI::_3DScene::reset_warning_texture(); } } diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 7712bd01d0..7b5752cd2b 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -60,7 +60,7 @@ sub new { label => 'Z', default => $self->{cut_options}{z}, min => 0, - max => $self->{model_object}->bounding_box->size->z * $self->{model_object}->instances->[0]->scaling_factor, + max => $self->{model_object}->bounding_box->size->z, full_width => 1, )); { @@ -247,6 +247,7 @@ sub _update { $self->{cut_options}{z}, [@expolygons], ); + $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->{config}); $self->{canvas}->Render; } } diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 8a8e6064c8..f7e38ed873 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -9,7 +9,7 @@ use utf8; use File::Basename qw(basename); use Wx qw(:misc :sizer :treectrl :button :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL wxTheApp); -use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN); +use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN EVT_KEY_DOWN); use base 'Wx::Panel'; use constant ICON_OBJECT => 0; @@ -88,7 +88,7 @@ sub new { $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font); # part settings panel - $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; }); + $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; }); my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); @@ -162,6 +162,7 @@ sub new { $canvas->load_object($self->{model_object}, undef, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,700]); + $canvas->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); $canvas->zoom_to_volumes; } @@ -190,6 +191,14 @@ sub new { EVT_BUTTON($self, $self->{btn_split}, \&on_btn_split); EVT_BUTTON($self, $self->{btn_move_up}, \&on_btn_move_up); EVT_BUTTON($self, $self->{btn_move_down}, \&on_btn_move_down); + EVT_KEY_DOWN($canvas, sub { + my ($canvas, $event) = @_; + if ($event->GetKeyCode == WXK_DELETE) { + $canvas->GetParent->on_btn_delete; + } else { + $event->Skip; + } + }); $self->reload_tree; @@ -400,14 +409,17 @@ sub on_tree_key_down { my ($self, $event) = @_; my $keycode = $event->GetKeyCode; # Wx >= 0.9911 - if (defined(&Wx::TreeEvent::GetKeyEvent) && - ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL)) { - if ($keycode == WXK_UP) { - $event->Skip; - $self->on_btn_move_up; - } elsif ($keycode == WXK_DOWN) { - $event->Skip; - $self->on_btn_move_down; + if (defined(&Wx::TreeEvent::GetKeyEvent)) { + if ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL) { + if ($keycode == WXK_UP) { + $event->Skip; + $self->on_btn_move_up; + } elsif ($keycode == WXK_DOWN) { + $event->Skip; + $self->on_btn_move_down; + } + } elsif ($keycode == WXK_DELETE) { + $self->on_btn_delete; } } } @@ -478,6 +490,7 @@ sub _parts_changed { $self->{canvas}->reset_objects; $self->{canvas}->load_object($self->{model_object}); $self->{canvas}->zoom_to_volumes; + $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); $self->{canvas}->Render; } } @@ -508,6 +521,25 @@ sub PartSettingsChanged { return $self->{part_settings_changed}; } +sub _update_canvas { + my ($self) = @_; + + if ($self->{canvas}) { + $self->{canvas}->reset_objects; + $self->{canvas}->load_object($self->{model_object}); + + # restore selection, if any + if (my $itemData = $self->get_selection) { + if ($itemData->{type} eq 'volume') { + $self->{canvas}->volumes->[ $itemData->{volume_id} ]->set_selected(1); + } + } + + $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); + $self->{canvas}->Render; + } +} + sub _update { my ($self) = @_; my ($m_x, $m_y, $m_z) = ($self->{move_options}{x}, $self->{move_options}{y}, $self->{move_options}{z}); @@ -528,6 +560,7 @@ sub _update { push @objects, $self->{model_object}; $self->{canvas}->reset_objects; $self->{canvas}->load_object($_, undef, [0]) for @objects; + $self->{canvas}->update_volumes_colors_by_extruder($self->GetParent->GetParent->GetParent->{config}); $self->{canvas}->Render; } diff --git a/resources/icons/action_undo.png b/resources/icons/action_undo.png index 866ae9773b..877f159868 100644 Binary files a/resources/icons/action_undo.png and b/resources/icons/action_undo.png differ diff --git a/resources/icons/sys_lock.png b/resources/icons/sys_lock.png index 0519c44a17..a33eadbcab 100644 Binary files a/resources/icons/sys_lock.png and b/resources/icons/sys_lock.png differ diff --git a/resources/icons/sys_unlock.png b/resources/icons/sys_unlock.png index 189bc24e78..d53c288a1c 100644 Binary files a/resources/icons/sys_unlock.png and b/resources/icons/sys_unlock.png differ diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0ca094880b..0da7f22d14 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -600,6 +600,7 @@ temperature = 255 [filament:*FLEX*] inherits = *common* +bed_temperature = 50 bridge_fan_speed = 100 compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 cooling = 0 @@ -950,6 +951,7 @@ variable_layer_height = 0 [printer:Original Prusa i3 MK2] inherits = *common* +default_print_profile = 0.15mm OPTIMAL [printer:Original Prusa i3 MK2 0.25 nozzle] inherits = *common* @@ -968,6 +970,7 @@ max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK2 MM Single Mode] inherits = *mm-single* @@ -976,6 +979,7 @@ inherits = *mm-single* inherits = *mm-single* nozzle_diameter = 0.6 printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK2 MultiMaterial] inherits = *mm-multi* diff --git a/t/combineinfill.t b/t/combineinfill.t index e94cf9eb52..5402a84f57 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -57,7 +57,7 @@ plan tests => 8; my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5]); + $config->set('nozzle_diameter', [0.5,0.5,0.5,0.5]); $config->set('infill_every_layers', 2); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); diff --git a/t/custom_gcode.t b/t/custom_gcode.t index bafcd4610d..7c2a75f293 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -49,7 +49,7 @@ use Slic3r::Test; my $parser = Slic3r::GCode::PlaceholderParser->new; my $config = Slic3r::Config::new_from_defaults; $config->set('printer_notes', ' PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 '); - $config->set('nozzle_diameter', [0.6, 0.6, 0.6, 0.6]); + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $parser->apply_config($config); $parser->set('foo' => 0); $parser->set('bar' => 2); @@ -123,6 +123,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('extruder', 2); $config->set('first_layer_temperature', [200,205]); @@ -204,6 +205,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]); $config->set('start_gcode', qq! ;substitution:{if infill_extruder==1}if block {elsif infill_extruder==2}elsif block 1 @@ -228,6 +230,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('start_gcode', ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . diff --git a/t/fill.t b/t/fill.t index a6fe8619cd..dd9eee4873 100644 --- a/t/fill.t +++ b/t/fill.t @@ -164,6 +164,7 @@ SKIP: for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); $config->set('fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); @@ -195,6 +196,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); $config->set('infill_only_where_needed', 1); $config->set('bottom_solid_layers', 0); $config->set('infill_extruder', 2); @@ -276,7 +278,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { $config->set('fill_density', 0); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35]); + $config->set('nozzle_diameter', [0.35,0.35,0.35,0.35]); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 2); $config->set('infill_extrusion_width', 0.52); diff --git a/t/multi.t b/t/multi.t index 49d35d9070..75ce0c286c 100644 --- a/t/multi.t +++ b/t/multi.t @@ -16,6 +16,7 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('raft_layers', 2); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 3); @@ -89,6 +90,7 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('support_material_extruder', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -97,6 +99,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -105,6 +108,7 @@ use Slic3r::Test; { my $config = Slic3r::Config->new; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('perimeter_extruder', 2); $config->set('infill_extruder', 2); $config->set('support_material_extruder', 2); @@ -126,6 +130,7 @@ use Slic3r::Test; $upper_config->set('bottom_solid_layers', 1); $upper_config->set('top_solid_layers', 0); my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('fill_density', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); @@ -172,6 +177,7 @@ use Slic3r::Test; my $object = $model->objects->[0]; my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('skirts', 0); diff --git a/t/retraction.t b/t/retraction.t index d7f1ea1458..6e6a130ca3 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -95,6 +95,7 @@ use Slic3r::Test qw(_eq); 1; }; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('first_layer_height', $config->layer_height); $config->set('first_layer_speed', '100%'); $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code @@ -207,6 +208,7 @@ use Slic3r::Test qw(_eq); { my $config = Slic3r::Config::new_from_defaults; + $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); $config->set('start_gcode', ''); $config->set('retract_lift', [3, 4]); @@ -255,4 +257,4 @@ use Slic3r::Test qw(_eq); 'Z is not lifted above the configured value for 2. extruder'; } -__END__ \ No newline at end of file +__END__ diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 1e777bf673..7798333fb8 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -206,6 +206,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/2DBed.hpp ${LIBDIR}/slic3r/GUI/wxExtensions.cpp ${LIBDIR}/slic3r/GUI/wxExtensions.hpp + ${LIBDIR}/slic3r/GUI/WipeTowerDialog.cpp + ${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp + ${LIBDIR}/slic3r/GUI/RammingChart.cpp + ${LIBDIR}/slic3r/GUI/RammingChart.hpp ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp ${LIBDIR}/slic3r/Config/Snapshot.cpp diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index dbe6ec8961..89d5d231e9 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -9,74 +9,115 @@ namespace Slic3r { -static inline Polyline make_wave_vertical( - double width, double height, double x0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip) { - Polyline polyline; - polyline.points.emplace_back(Point(coord_t(clamp(0., width, x0) * scaleFactor), 0)); - double phase_offset_sin = (z_cos < 0 ? M_PI : 0) + M_PI; - double phase_offset_cos = (z_cos < 0 ? M_PI : 0) + M_PI + (flip ? M_PI : 0.); - for (double y = 0.; y < height + segmentSize; y += segmentSize) { - y = std::min(y, height); - double a = sin(y + phase_offset_sin); + if (vertical) { + double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI; + double a = sin(x + phase_offset); double b = - z_cos; - double res = z_sin * cos(y + phase_offset_cos); + double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.)); double r = sqrt(sqr(a) + sqr(b)); - double x = clamp(0., width, asin(a/r) + asin(res/r) + M_PI + x0); - polyline.points.emplace_back(convert_to(Pointf(x, y) * scaleFactor)); + return asin(a/r) + asin(res/r) + M_PI; } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); + else { + double phase_offset = z_sin < 0 ? M_PI : 0.; + double a = cos(x + phase_offset); + double b = - z_sin; + double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI)); + double r = sqrt(sqr(a) + sqr(b)); + return (asin(a/r) + asin(res/r) + 0.5 * M_PI); + } +} + +static inline Polyline make_wave( + const std::vector& one_period, double width, double height, double offset, double scaleFactor, + double z_cos, double z_sin, bool vertical) +{ + std::vector points = one_period; + double period = points.back().x; + points.pop_back(); + int n = points.size(); + do { + points.emplace_back(Pointf(points[points.size()-n].x + period, points[points.size()-n].y)); + } while (points.back().x < width); + points.back().x = width; + + // and construct the final polyline to return: + Polyline polyline; + for (auto& point : points) { + point.y += offset; + point.y = clamp(0., height, double(point.y)); + if (vertical) + std::swap(point.x, point.y); + polyline.points.emplace_back(convert_to(point * scaleFactor)); + } + return polyline; } -static inline Polyline make_wave_horizontal( - double width, double height, double y0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) { - Polyline polyline; - polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor))); - double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI); - double phase_offset_cos = z_sin < 0 ? M_PI : 0.; - for (double x = 0.; x < width + segmentSize; x += segmentSize) { - x = std::min(x, width); - double a = cos(x + phase_offset_cos); - double b = - z_sin; - double res = z_cos * sin(x + phase_offset_sin); - double r = sqrt(sqr(a) + sqr(b)); - double y = clamp(0., height, asin(a/r) + asin(res/r) + 0.5 * M_PI + y0); - polyline.points.emplace_back(convert_to(Pointf(x, y) * scaleFactor)); + std::vector points; + double dx = M_PI_4; // very coarse spacing to begin with + double limit = std::min(2*M_PI, width); + for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too + x = std::min(x, limit); + points.emplace_back(Pointf(x,f(x, z_sin,z_cos, vertical, flip))); } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); - return polyline; + + // now we will check all internal points and in case some are too far from the line connecting its neighbours, + // we will add one more point on each side: + const double tolerance = .1; + for (unsigned int i=1;i tolerance) { // if the difference from straight line is more than this + double x = 0.5f * (points[i-1].x + points[i].x); + points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); + x = 0.5f * (points[i+1].x + points[i].x); + points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip))); + std::sort(points.begin(), points.end()); // we added the points to the end, but need them all in order + --i; // decrement i so we also check the first newly added point + } + } + return points; } static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { - double scaleFactor = scale_(line_spacing) / density_adjusted; - double segmentSize = 0.5 * density_adjusted; + const double scaleFactor = scale_(line_spacing) / density_adjusted; //scale factor for 5% : 8 712 388 // 1z = 10^-6 mm ? - double z = gridZ / scaleFactor; - double z_sin = sin(z); - double z_cos = cos(z); - Polylines result; - if (std::abs(z_sin) <= std::abs(z_cos)) { - // Vertical wave - double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.); - bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0; - for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip) - result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip)); - } else { - // Horizontal wave - bool flip = true; - for (double y0 = 0.; y0 < height; y0 += M_PI, flip = !flip) - result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip)); + const double z = gridZ / scaleFactor; + const double z_sin = sin(z); + const double z_cos = cos(z); + + bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); + double lower_bound = 0.; + double upper_bound = height; + bool flip = true; + if (vertical) { + flip = false; + lower_bound = -M_PI; + upper_bound = width - M_PI_2; + std::swap(width,height); } + + std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time + Polylines result; + + for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines + result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + + flip = !flip; // even polylines are a bit shifted + one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample + for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines + result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + return result; } @@ -90,13 +131,13 @@ void FillGyroid::_fill_surface_single( // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = params.density * 1.75; + double density_adjusted = std::max(0., params.density * 2.); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance))); - + // generate pattern Polylines polylines = make_gyroid_waves( scale_(this->z), diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 89f9b277fc..7b5bf7e8a6 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -388,7 +388,6 @@ namespace Slic3r { bool _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter); - void _apply_transform(ModelObject& object, const Matrix4x4& matrix); void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix); bool _handle_start_config(const char** attributes, unsigned int num_attributes); @@ -557,19 +556,6 @@ namespace Slic3r { if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr)) return false; - - // apply transformation if the object contains a single instance - if (object.second->instances.size() == 1) - { - for (const Instance& instance : m_instances) - { - if (object.second->instances[0] == instance.instance) - { - _apply_transform(*object.second, instance.matrix); - break; - } - } - } } // fixes the min z of the model if negative @@ -822,10 +808,9 @@ namespace Slic3r { if (instance.instance != nullptr) { ModelObject* object = instance.instance->get_object(); - if ((object != nullptr) && (object->instances.size() > 1)) + if (object != nullptr) { - // multiple instances -> apply the matrix to the instance - // (for single instance the transformation can be applied only after the volumes have been generated) + // apply the matrix to the instance _apply_transform(*instance.instance, instance.matrix); } } @@ -1120,15 +1105,6 @@ namespace Slic3r { return true; } - void _3MF_Importer::_apply_transform(ModelObject& object, const Matrix4x4& matrix) - { - float matrix3x4[12] = { matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(0, 3), - matrix(1, 0), matrix(1, 1), matrix(1, 2), matrix(1, 3), - matrix(2, 0), matrix(2, 1), matrix(2, 2), matrix(2, 3) }; - - object.transform(matrix3x4); - } - void _3MF_Importer::_apply_transform(ModelInstance& instance, const Matrix4x4& matrix) { // slic3r ModelInstance cannot be transformed using a matrix @@ -1645,9 +1621,7 @@ namespace Slic3r { } Eigen::Affine3f transform; - transform = Eigen::Translation3f((float)(instance->offset.x + object.origin_translation.x), (float)(instance->offset.y + object.origin_translation.y), (float)object.origin_translation.z) - * Eigen::AngleAxisf((float)instance->rotation, Eigen::Vector3f::UnitZ()) - * Eigen::Scaling((float)instance->scaling_factor); + transform = Eigen::Translation3f((float)instance->offset.x, (float)instance->offset.y, 0.0f) * Eigen::AngleAxisf((float)instance->rotation, Eigen::Vector3f::UnitZ()) * Eigen::Scaling((float)instance->scaling_factor); build_items.emplace_back(instance_id, transform.matrix()); stream << " \n"; @@ -1690,10 +1664,9 @@ namespace Slic3r { for (int i = 0; i < stl.stats.shared_vertices; ++i) { stream << " <" << VERTEX_TAG << " "; - // Subtract origin_translation in order to restore the original local coordinates - stream << "x=\"" << (stl.v_shared[i].x - object.origin_translation.x) << "\" "; - stream << "y=\"" << (stl.v_shared[i].y - object.origin_translation.y) << "\" "; - stream << "z=\"" << (stl.v_shared[i].z - object.origin_translation.z) << "\" />\n"; + stream << "x=\"" << stl.v_shared[i].x << "\" "; + stream << "y=\"" << stl.v_shared[i].y << "\" "; + stream << "z=\"" << stl.v_shared[i].z << "\" />\n"; } } diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 2c92e16e6d..36cebeb847 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -17,12 +17,26 @@ public: struct xy { xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} + xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } + + // Rotate the point around given point about given angle (in degrees) + // shifts the result so that point of rotation is in the middle of the tower + xy rotate(const xy& origin, float width, float depth, float angle) const { + xy out(0,0); + float temp_x = x - width / 2.f; + float temp_y = y - depth / 2.f; + angle *= M_PI/180.; + out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle); + out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle); + return out + origin; + } + float x; float y; }; @@ -112,17 +126,15 @@ public: const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + bool last_wipe_inside_wipe_tower) = 0; // Returns gcode for toolchange and the end position. // if new_tool == -1, just unload the current filament over the wipe tower. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + virtual ToolChangeResult finish_layer() = 0; // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 99c6c757fa..ad7d91c50d 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -1,3 +1,18 @@ +/* + +TODO LIST +--------- + +1. cooling moves - DONE +2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE +3. priming extrusions (last wipe must clear the color) +4. Peter's wipe tower - layer's are not exactly square +5. Peter's wipe tower - variable width for higher levels +6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) +7. Peter's wipe tower - enable enhanced first layer adhesion + +*/ + #include "WipeTowerPrusaMM.hpp" #include @@ -5,6 +20,8 @@ #include #include #include +#include +#include #include "Analyzer.hpp" @@ -16,6 +33,7 @@ #define strcasecmp _stricmp #endif + namespace Slic3r { @@ -28,13 +46,13 @@ public: m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), - m_extrusion_flow(0.f), m_layer_height(0.f), + m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f) {} Writer& set_initial_position(const WipeTower::xy &pos) { - m_start_pos = pos; + m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); m_current_pos = pos; return *this; } @@ -49,6 +67,15 @@ public: Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } + + Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle) + { m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); } + + Writer& set_y_shift(float shift) { + m_current_pos.y -= shift-m_y_shift; + m_y_shift = shift; + return (*this); + } // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer @@ -67,8 +94,9 @@ public: const std::vector& extrusions() const { return m_extrusions; } float x() const { return m_current_pos.x; } float y() const { return m_current_pos.y; } - const WipeTower::xy& start_pos() const { return m_start_pos; } const WipeTower::xy& pos() const { return m_current_pos; } + const WipeTower::xy start_pos_rotated() const { return m_start_pos; } + const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); } float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. @@ -81,29 +109,42 @@ public: float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); + + + // For rotated wipe tower, transform position to printer coordinates + WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are + WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go + if (! m_preview_suppressed && e > 0.f && len > 0.) { // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. - float width = float(double(e) * m_filament_area / (len * m_layer_height)); + float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. - width += float(m_layer_height * (1. - M_PI / 4.)); - if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos) - m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool)); + width += m_layer_height * float(1. - M_PI / 4.); + if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) + m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); } m_gcode += "G1"; - if (x != m_current_pos.x) - m_gcode += set_format_X(x); - if (y != m_current_pos.y) - m_gcode += set_format_Y(y); + if (rot.x != rotated_current_pos.x) { + m_gcode += set_format_X(rot.x); // Transform current position back to wipe tower coordinates (was updated by set_format_X) + m_current_pos.x = x; + } + if (rot.y != rotated_current_pos.y) { + m_gcode += set_format_Y(rot.y); + m_current_pos.y = y; + } if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); - + + + + // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; @@ -130,6 +171,31 @@ public: Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) { return extrude(dest.x, dest.y, f); } + + Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f) + { + WipeTower::xy corners[4]; + corners[0] = ld; + corners[1] = WipeTower::xy(ld,width,0.f); + corners[2] = WipeTower::xy(ld,width,height); + corners[3] = WipeTower::xy(ld,0.f,height); + int index_of_closest = 0; + if (x()-ld.x > ld.x+width-x()) // closer to the right + index_of_closest = 1; + if (y()-ld.y > ld.y+height-y()) // closer to the top + index_of_closest = (index_of_closest==0 ? 3 : 2); + + travel(corners[index_of_closest].x, y()); // travel to the closest corner + travel(x(),corners[index_of_closest].y); + + int i = index_of_closest; + do { + ++i; + if (i==4) i=0; + extrude(corners[i]); + } while (i != index_of_closest); + return (*this); + } Writer& load(float e, float f = 0.f) { @@ -143,7 +209,7 @@ public: m_gcode += "\n"; return *this; } - + // Derectract while moving in the X direction. // If |x| > 0, the feed rate relates to the x distance, // otherwise the feed rate relates to the e distance. @@ -198,8 +264,22 @@ public: // Set extruder temperature, don't wait by default. Writer& set_extruder_temp(int temperature, bool wait = false) { + if (temperature != current_temp) { + char buf[128]; + sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); + m_gcode += buf; + current_temp = temperature; + } + return *this; + }; + + // Wait for a period of time (seconds). + Writer& wait(float time) + { + if (time==0) + return *this; char buf[128]; - sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); + sprintf(buf, "G4 S%.3f\n", time); m_gcode += buf; return *this; }; @@ -243,6 +323,25 @@ public: return *this; }; + + Writer& set_fan(unsigned int speed) + { + if (speed == m_last_fan_speed) + return *this; + + if (speed == 0) + m_gcode += "M107\n"; + else + { + m_gcode += "M106 S"; + char buf[128]; + sprintf(buf,"%u\n",(unsigned int)(255.0 * speed / 100.0)); + m_gcode += buf; + } + m_last_fan_speed = speed; + return *this; + } + Writer& comment_material(WipeTowerPrusaMM::material_type material) { m_gcode += "; material : "; @@ -279,9 +378,17 @@ private: std::string m_gcode; std::vector m_extrusions; float m_elapsed_time; - const double m_filament_area = 0.25*M_PI*1.75*1.75; + float m_angle_deg = 0.f; + float m_y_shift = 0.f; + WipeTower::xy m_wipe_tower_pos; + float m_wipe_tower_width = 0.f; + float m_wipe_tower_depth = 0.f; + float m_last_fan_speed = 0.f; + int current_temp = -1; - std::string set_format_X(float x) { + std::string + set_format_X(float x) + { char buf[64]; sprintf(buf, " X%.3f", x); m_current_pos.x = x; @@ -315,32 +422,11 @@ private: } Writer& operator=(const Writer &rhs); -}; +}; // class Writer -/* -class Material -{ -public: - std::string name; - std::string type; +}; // namespace PrusaMultiMaterial - struct RammingStep { -// float length; - float extrusion_multiplier; // sirka linky - float extrusion; - float speed; - }; - std::vector ramming_sequence; - // Number and speed of the cooling moves. - std::vector cooling_moves; - - // Percentage of the speed overide, in pairs of - std::vector> speed_override; -}; -*/ - -} // namespace PrusaMultiMaterial WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) { @@ -365,6 +451,7 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam return INVALID; } + // Returns gcode to prime the nozzles at the front edge of the print bed. WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. @@ -373,37 +460,19 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower, - // May be used by a stand alone post processor. - Purpose purpose) + bool last_wipe_inside_wipe_tower) { - this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - - float wipe_area = m_wipe_area; - // Calculate the amount of wipe over the wipe tower brim following the prime, decrease wipe_area - // with the amount of material extruded over the brim. - { - // Simulate the brim extrusions, summ the length of the extrusion. - float e_length = this->tool_change(0, false, PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - // Minimum wipe area is 5mm wide. - //FIXME calculate the purge_lines_width precisely. - float purge_lines_width = 1.3f; - wipe_area = std::max(5.f, m_wipe_area - float(floor(e_length / m_wipe_tower_width)) * dy - purge_lines_width); - } this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - this->m_num_layer_changes = 0; this->m_current_tool = tools.front(); - + // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210]. // Due to the XYZ calibration, this working space may shrink slightly from all directions, // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); - box_coordinates cleaning_box(xy(5.f, 0.f), m_wipe_tower_width, wipe_area); + + const float prime_section_width = std::min(240.f / tools.size(), 60.f); + box_coordinates cleaning_box(xy(5.f, 0.f), prime_section_width, 100.f); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) @@ -415,48 +484,35 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( .append(";--------------------\n") .speed_override(100); - // Always move to the starting position. - writer.set_initial_position(xy(0.f, 0.f)) - .travel(cleaning_box.ld, 7200) - // Increase the extruder driver current to allow fast ramming. - .set_extruder_trimpot(750); + writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position + .travel(cleaning_box.ld, 7200) + .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); writer.append(buf); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - float y_end = 0.f; - for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { - unsigned int tool = tools[idx_tool]; - // Select the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - // Prime the tool. - toolchange_Load(writer, cleaning_box); - if (idx_tool + 1 == tools.size()) { - // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. - if (last_wipe_inside_wipe_tower) { - // Shrink the last wipe area to the area of the other purge areas, - // remember the last initial wipe width to be purged into the 1st layer of the wipe tower. - this->m_initial_extra_wipe = std::max(0.f, wipe_area - (y_end + 0.5f * 0.85f * m_perimeter_width - cleaning_box.ld.y)); - cleaning_box.lu.y -= this->m_initial_extra_wipe; - cleaning_box.ru.y -= this->m_initial_extra_wipe; - } - toolchange_Wipe(writer, cleaning_box, false); - } else { - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. - writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); - // Change the extruder temperature to the temperature of the next filament before starting the cooling moves. - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_first_layer_temperature[tools[idx_tool+1]]); - // Save the y end of the non-last priming area. - y_end = writer.y(); - cleaning_box.translate(m_wipe_tower_width, 0.f); - writer.travel(cleaning_box.ld, 7200); - } - ++ m_num_tool_changes; - } - } + for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + unsigned int tool = tools[idx_tool]; + m_left_to_right = true; + toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. + toolchange_Load(writer, cleaning_box); // Prime the tool. + if (idx_tool + 1 == tools.size()) { + // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. + toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]); + } else { + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); + toolchange_Wipe(writer, cleaning_box , 20.f); + box_coordinates box = cleaning_box; + box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width); + toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); + cleaning_box.translate(prime_section_width, 0.f); + writer.travel(cleaning_box.ld, 7200); + } + ++ m_num_tool_changes; + } // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) @@ -467,8 +523,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( ";------------------\n" "\n\n"); - // Force m_idx_tool_change_in_layer to -1, so that tool_change() will know to extrude the wipe tower brim. - m_idx_tool_change_in_layer = (unsigned int)(-1); + // so that tool_change() will know to extrude the wipe tower brim: + m_print_brim = true; ToolChangeResult result; result.print_z = this->m_z_pos; @@ -476,140 +532,101 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer, Purpose purpose) +WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) { - // Either it is the last tool unload, - // or there must be a nonzero wipe tower partitions available. -// assert(tool < 0 || it_layer_tools->wipe_tower_partitions > 0); + if ( m_print_brim ) + return toolchange_Brim(); - if (m_idx_tool_change_in_layer == (unsigned int)(-1)) { - // First layer, prime the extruder. - return toolchange_Brim(purpose); - } - - float wipe_area = m_wipe_area; - if (++ m_idx_tool_change_in_layer < (unsigned int)m_max_color_changes && last_in_layer) { - // This tool_change() call will be followed by a finish_layer() call. - // Try to shrink the wipe_area to save material, as less than usual wipe is required - // if this step is foolowed by finish_layer() extrusions wiping the same extruder. - for (size_t iter = 0; iter < 3; ++ iter) { - // Simulate the finish_layer() extrusions, summ the length of the extrusion. - float e_length = 0.f; - { - unsigned int old_idx_tool_change = m_idx_tool_change_in_layer; - float old_wipe_start_y = m_current_wipe_start_y; - m_current_wipe_start_y += wipe_area; - e_length = this->finish_layer(PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - m_idx_tool_change_in_layer = old_idx_tool_change; - m_current_wipe_start_y = old_wipe_start_y; - } - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - float num_lines_extruded = floor(e_length / m_wipe_tower_width); - // Minimum wipe area is 5mm wide. - wipe_area = m_wipe_area - num_lines_extruded * dy; - if (wipe_area < 5.) { - wipe_area = 5.; + float wipe_area = 0.f; + bool last_change_in_layer = false; + float wipe_volume = 0.f; + + // Finds this toolchange info + if (tool != (unsigned int)(-1)) + { + for (const auto &b : m_layer_info->tool_changes) + if ( b.new_tool == tool ) { + wipe_volume = wipe_volumes[b.old_tool][b.new_tool]; + if (tool == m_layer_info->tool_changes.back().new_tool) + last_change_in_layer = true; + wipe_area = b.required_depth * m_layer_info->extra_spacing; break; } - } + } + else { + // Otherwise we are going to Unload only. And m_layer_info would be invalid. } box_coordinates cleaning_box( - m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y + 0.5f * m_perimeter_width), - m_wipe_tower_width, - wipe_area - m_perimeter_width); + m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + m_wipe_tower_width - m_perimeter_width, + (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width + : m_wipe_tower_depth-m_perimeter_width)); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes) - .comment_material(m_material[m_current_tool]) - .append(";--------------------\n") - .speed_override(100); + .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) + .append(";--------------------\n" + "; CP TOOLCHANGE START\n") + .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based + .comment_material(m_filpar[m_current_tool].material) + .append(";--------------------\n") + .speed_override(100); - xy initial_position = ((m_current_shape == SHAPE_NORMAL) ? cleaning_box.ld : cleaning_box.lu) + - xy(m_perimeter_width, ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width); + xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); + writer.set_initial_position(initial_position); - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower. - float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract; - writer // Lift for a Z hop. - .z_hop(m_zhop, 7200) - // Additional retract on move to tower. - .retract(initial_retract, 3600) - // Move to a starting position, one perimeter width inside the cleaning box. - .travel(initial_position, 7200) - // Unlift for a Z hop. - .z_hop_reset(7200) - // Additional retract on move to tower. - .load(initial_retract, 3600) - .load(m_retract, 1500); - } else { - // Already at the initial position. - writer.set_initial_position(initial_position); - } // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); writer.append(buf); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Increase the extruder driver current to allow fast ramming. - writer.set_extruder_trimpot(750); - // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + // Increase the extruder driver current to allow fast ramming. + writer.set_extruder_trimpot(750); - if (tool != (unsigned int)-1) { - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], - m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]); - // This is not the last change. - // Change the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - toolchange_Load(writer, cleaning_box); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, false); - // Draw a perimeter around cleaning_box and wipe. - box_coordinates box = cleaning_box; - if (m_current_shape == SHAPE_REVERSED) { - std::swap(box.lu, box.ld); - std::swap(box.ru, box.rd); - } - // Draw a perimeter around cleaning_box. - writer.travel(box.lu, 7000) - .extrude(box.ld, 3200).extrude(box.rd) - .extrude(box.ru).extrude(box.lu); - // Wipe the nozzle. - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(box.ru, 7200) - .travel(box.lu); - } else - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_temperature[m_current_tool]); + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. + if (tool != (unsigned int)-1){ // This is not the last change. + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, + m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); + toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. + toolchange_Load(writer, cleaning_box); + writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road + toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + } else + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); - // Reset the extruder current to a normal value. - writer.set_extruder_trimpot(550) - .feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP TOOLCHANGE END\n" - ";------------------\n" - "\n\n"); + ++ m_num_tool_changes; + m_depth_traversed += wipe_area; - ++ m_num_tool_changes; - m_current_wipe_start_y += wipe_area; - } + if (last_change_in_layer) {// draw perimeter line + writer.set_y_shift(m_y_shift); + if (m_peters_wipe_tower) + writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + else { + writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle + writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y()); + } + } + } + + writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value. + .feedrate(6000) + .flush_planner_queue() + .reset_extruder() + .append("; CP TOOLCHANGE END\n" + ";------------------\n" + "\n\n"); ToolChangeResult result; result.print_z = this->m_z_pos; @@ -617,105 +634,55 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( m_wipe_tower_pos, m_wipe_tower_width, - m_wipe_area * float(m_max_color_changes) - m_perimeter_width / 2); + m_wipe_tower_depth); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow * 1.1f) - // Let the writer know the current Z position as a base for Z-hop. - .set_z(m_z_pos) + .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) - .append( - ";-------------------------------------\n" - "; CP WIPE TOWER FIRST LAYER BRIM START\n"); + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .append(";-------------------------------------\n" + "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); - - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Move with Z hop. - writer.z_hop(m_zhop, 7200) - .travel(initial_position, 6000) - .z_hop_reset(7200); - else - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position); // adds tag for analyzer char buf[32]; sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); + writer.append(buf) + .extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Prime the extruder 10*m_perimeter_width left along the vertical edge of the wipe tower. - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. + // Extrude 4 rounds of a brim around the future wipe tower. + box_coordinates box(wipeTower_box); + box.expand(m_perimeter_width); + for (size_t i = 0; i < 4; ++ i) { + writer.travel (box.ld, 7000) + .extrude(box.lu, 2100).extrude(box.ru) + .extrude(box.rd ).extrude(box.ld); + box.expand(m_perimeter_width); + } - // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. - // toolchange_Change(writer, int(tool), m_material[tool]); + writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. + writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. + .travel(wipeTower_box.ld); + writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" + ";-----------------------------------\n"); - if (sideOnly) { - float x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.ld + xy(- x_offset, y_offset), 7000) - .extrude(wipeTower_box.lu + xy(- x_offset, - y_offset), 2100); - writer.travel(wipeTower_box.rd + xy(x_offset, y_offset), 7000); - x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.rd + xy(x_offset, y_offset), 7000) - .extrude(wipeTower_box.ru + xy(x_offset, - y_offset), 2100); - } else { - // Extrude 4 rounds of a brim around the future wipe tower. - box_coordinates box(wipeTower_box); - //FIXME why is the box shifted in +Y by 0.5f * m_perimeter_width? - box.translate(0.f, 0.5f * m_perimeter_width); - box.expand(0.5f * m_perimeter_width); - for (size_t i = 0; i < 4; ++ i) { - writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); - box.expand(m_perimeter_width); - } - } - - if (m_initial_extra_wipe > m_perimeter_width * 1.9f) { - box_coordinates cleaning_box( - m_wipe_tower_pos + xy(0.f, 0.5f * m_perimeter_width), - m_wipe_tower_width, - m_initial_extra_wipe - m_perimeter_width); - writer.travel(cleaning_box.ld + xy(m_perimeter_width, 0.5f * m_perimeter_width), 6000); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, true); - // Draw a perimeter around cleaning_box. - writer.travel(cleaning_box.lu, 7000) - .extrude(cleaning_box.ld, 3200).extrude(cleaning_box.rd) - .extrude(cleaning_box.ru).extrude(cleaning_box.lu); - m_current_wipe_start_y = m_initial_extra_wipe; - } - - // Move to the front left corner. - writer.travel(wipeTower_box.ld, 7000); - - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Wipe along the front edge. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(wipeTower_box.rd) - .travel(wipeTower_box.ld); - - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" - ";-----------------------------------\n"); - - // Mark the brim as extruded. - m_idx_tool_change_in_layer = 0; - } + m_print_brim = false; // Mark the brim as extruded ToolChangeResult result; result.print_z = this->m_z_pos; @@ -723,11 +690,13 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, b result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } + + // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTowerPrusaMM::toolchange_Unload( PrusaMultiMaterial::Writer &writer, @@ -735,87 +704,145 @@ void WipeTowerPrusaMM::toolchange_Unload( const material_type current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 0.5f * m_perimeter_width; - float xr = cleaning_box.rd.x - 0.5f * m_perimeter_width; - float y_step = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width; - + float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x - 1.f * m_perimeter_width; + writer.append("; CP TOOLCHANGE UNLOAD\n"); + + const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness + const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm - // Ram the hot material out of the extruder melt zone. - // Current extruder position is on the left, one perimeter inside the cleaning box in both X and Y. - float e0 = m_perimeter_width * m_extrusion_flow; - float e = (xr - xl) * m_extrusion_flow; - switch (current_material) - { - case ABS: - // ramming start end y increment amount feedrate - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.2f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.6f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000) - .ram(xr - m_perimeter_width * 2, xl + m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000); - break; - case PVA: - // Used for the PrimaSelect PVA - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 4500) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.5f, 0, 1.75f * e, 4800) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 5000); - break; - case SCAFF: - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 2.f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 3.f, 0, 2.34f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 3.f, 0, 2.63f * e, 5200); - break; - default: - // PLA, PLA/PHA and others - // Used for the Verbatim BVOH, PET, NGEN, co-polyesters - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.60f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.65f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.74f * e, 5200); + unsigned i = 0; // iterates through ramming_speed + m_left_to_right = true; // current direction of ramming + float remaining = xr - xl ; // keeps track of distance to the next turnaround + float e_done = 0; // measures E move done from each segment + + writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position + + // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: + if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { + + // this is y of the center of previous sparse infill border + float sparse_beginning_y = m_wipe_tower_pos.y; + if (m_current_shape == SHAPE_REVERSED) + sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth()) + - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ; + else + sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width; + + //debugging: + /* float oldx = writer.x(); + float oldy = writer.y(); + writer.travel(xr,sparse_beginning_y); + writer.extrude(xr+5,writer.y()); + writer.travel(oldx,oldy);*/ + + float sum_of_depths = 0.f; + for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange + if (tch.old_tool == m_current_tool) { + sum_of_depths += tch.ramming_depth; + float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths; + ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line + + // debugging: + /*float oldx = writer.x(); + float oldy = writer.y(); + writer.travel(xl,ramming_end_y); + writer.extrude(xl-15,writer.y()); + writer.travel(oldx,oldy);*/ + + if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || + (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) + { + writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y()); + remaining -= tch.first_wipe_line-1.f*m_perimeter_width; + } + break; + } + sum_of_depths += tch.required_depth; + } + } + + // now the ramming itself: + while (i < m_filpar[m_current_tool].ramming_speed.size()) + { + const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); + const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move; + const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround + const float actual_time = dist/x * 0.25; + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.)); + remaining -= dist; + + if (remaining < WT_EPSILON) { // we reached a turning point + writer.travel(writer.x(), writer.y() + y_step, 7200); + m_left_to_right = !m_left_to_right; + remaining = xr - xl; + } + e_done += dist; // subtract what was actually done + if (e_done > x - WT_EPSILON) { // current segment finished + ++i; + e_done = 0; + } } + WipeTower::xy end_of_ramming(writer.x(),writer.y()); - // Pull the filament end into a cooling tube. - writer.retract(15, 5000).retract(50, 5400).retract(15, 3000).retract(12, 2000); + // Pull the filament end to the BEGINNING of the cooling tube while still moving the print head + float oldx = writer.x(); + float turning_point = (!m_left_to_right ? std::max(xl,oldx-15.f) : std::min(xr,oldx+15.f) ); // so it's not too far + float xdist = std::abs(oldx-turning_point); + float edist = -(m_cooling_tube_retraction+m_cooling_tube_length/2.f-42); + writer.suppress_preview() + .load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * 83 ) // fixed speed after ramming + .load_move_x(oldx ,edist , 60.f * std::hypot(xdist,edist)/std::abs(edist) * m_filpar[m_current_tool].unloading_speed ) + .load_move_x(turning_point,-15 , 60.f * std::hypot(xdist,15)/15 * m_filpar[m_current_tool].unloading_speed*0.55f ) + .load_move_x(oldx ,-12 , 60.f * std::hypot(xdist,12)/12 * m_filpar[m_current_tool].unloading_speed*0.35f ) + .resume_preview(); - if (new_temperature != 0) - // Set the extruder temperature, but don't wait. + if (new_temperature != 0) // Set the extruder temperature, but don't wait. writer.set_extruder_temp(new_temperature, false); - // In case the current print head position is closer to the left edge, reverse the direction. - if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr)) - std::swap(xl, xr); - // Horizontal cooling moves will be performed at the following Y coordinate: - writer.travel(xr, writer.y() + y_step * 0.8f, 7200) - .suppress_preview(); - switch (current_material) - { - case PVA: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); - break; - case SCAFF: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -3, 2400); - break; - default: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); +// cooling: + writer.suppress_preview(); + writer.travel(writer.x(), writer.y() + y_step); + const float start_x = writer.x(); + turning_point = ( xr-start_x > start_x-xl ? xr : xl ); + const float max_x_dist = 2*std::abs(start_x-turning_point); + const unsigned int N = 4 + std::max(0.f, (m_filpar[m_current_tool].cooling_time-14)/3); + float time = m_filpar[m_current_tool].cooling_time / float(N); + + i = 0; + while (i= cleaning_box.ld.y + m_perimeter_width)); - p = ! p) - { - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - if (skip_initial_y_move) - skip_initial_y_move = false; + const float& xl = cleaning_box.ld.x; + const float& xr = cleaning_box.rd.x; + + + // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least + // the ordered volume, even if it means violating the box. This can later be removed and simply + // wipe until the end of the assigned area. + + float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height); + float dy = m_extra_spacing*m_perimeter_width; + float wipe_speed = 1600.f; + + // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway) + if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) { + writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy); + m_left_to_right = !m_left_to_right; + } + + + // now the wiping itself: + for (int i = 0; true; ++i) { + if (i!=0) { + if (wipe_speed < 1610.f) wipe_speed = 1800.f; + else if (wipe_speed < 1810.f) wipe_speed = 2200.f; + else if (wipe_speed < 2210.f) wipe_speed = 4200.f; + else wipe_speed = std::min(4800.f, wipe_speed + 50.f); + } + + float traversed_x = writer.x(); + if (m_left_to_right) + writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); else - writer.extrude(xl - (p ? m_perimeter_width / 2 : m_perimeter_width), writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xr + (p ? m_perimeter_width : m_perimeter_width * 2), writer.y(), wipe_speed * wipe_coeff); - // Next wipe line fits the cleaning box. - if ((m_current_shape == SHAPE_NORMAL) ? - (writer.y() > cleaning_box.lu.y - m_perimeter_width) : - (writer.y() < cleaning_box.ld.y + m_perimeter_width)) + writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); + + if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width) + break; // in case next line would not fit + + traversed_x -= writer.x(); + x_to_wipe -= fabs(traversed_x); + if (x_to_wipe < WT_EPSILON) { + writer.travel(m_left_to_right ? xl + 1.5*m_perimeter_width : xr - 1.5*m_perimeter_width, writer.y(), 7200); break; - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - writer.extrude(xr + m_perimeter_width, writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xl - m_perimeter_width, writer.y()); + } + // stepping to the next line: + writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5*m_perimeter_width, writer.y() + dy); + m_left_to_right = !m_left_to_right; } - // Reset the extrusion flow. - writer.set_extrusion_flow(m_extrusion_flow); + + // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle + if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { + m_left_to_right = !m_left_to_right; + writer.travel(writer.x(), writer.y() - dy) + .travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y()); + } + + writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. } -WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) + + + +WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. @@ -926,118 +971,82 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - // m_num_layer_changes is incremented by set_z, so it is 1 based. - .comment_with_value(" layer #", m_num_layer_changes - 1); + .set_z(m_z_pos) + .set_layer_height(m_layer_height) + .set_initial_tool(m_current_tool) + .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) + .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) + .append(";--------------------\n" + "; CP EMPTY GRID START\n") + .comment_with_value(" layer #", m_num_layer_changes + 1); // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; + float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); + box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); - box_coordinates fill_box(m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y), - m_wipe_tower_width, float(m_max_color_changes) * m_wipe_area - m_current_wipe_start_y); - fill_box.expand(0.f, - 0.5f * m_perimeter_width); - { - float firstLayerOffset = 0.f; - fill_box.ld.y += firstLayerOffset; - fill_box.rd.y += firstLayerOffset; - } + if (m_left_to_right) // so there is never a diagonal travel + writer.set_initial_position(fill_box.ru); + else + writer.set_initial_position(fill_box.lu); - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - if (m_idx_tool_change_in_layer == 0) { - // There were no tool changes at all in this layer. - writer.retract(m_retract * 1.5f, 3600) - // Jump with retract to fill_box.ld + a random shift in +x. - .z_hop(m_zhop, 7200) - .travel(fill_box.ld + xy(5.f + 15.f * float(rand()) / RAND_MAX, 0.f), 7000) - .z_hop_reset(7200) - // Prime the extruder. - .load_move_x(fill_box.ld.x, m_retract * 1.5f, 3600); - } else { - // Otherwise the extruder is already over the wipe tower. - } - } else { - // The print head is inside the wipe tower. Rather move to the start of the following extrusion. - // writer.set_initial_position(fill_box.ld); - writer.set_initial_position(fill_box.ld); - } - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Extrude the first perimeter. - box_coordinates box = fill_box; - writer.extrude(box.lu, 2400 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); + box_coordinates box = fill_box; + for (int i=0;i<2;++i) { + if (m_layer_info->toolchanges_depth() < WT_EPSILON) { // there were no toolchanges on this layer + if (i==0) box.expand(m_perimeter_width); + else box.expand(-m_perimeter_width); + } + else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) + writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor); + } - // Extrude second perimeter. - box.expand(- m_perimeter_width / 2); - writer.extrude(box.lu, 3200 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); + // we are in one of the corners, travel to ld along the perimeter: + if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y()); + if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y); - if (m_is_first_layer) { - // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. - box.expand(- m_perimeter_width / 2); - box.ld.y -= 0.5f * m_perimeter_width; - box.rd.y = box.ld.y; - int nsteps = int(floor((box.lu.y - box.ld.y) / (2. * (1.0 * m_perimeter_width)))); - float step = (box.lu.y - box.ld.y) / nsteps; - for (size_t i = 0; i < nsteps; ++ i) { - writer.extrude(box.ld.x, writer.y() + 0.5f * step); - writer.extrude(box.rd.x, writer.y()); - writer.extrude(box.rd.x, writer.y() + 0.5f * step); - writer.extrude(box.ld.x, writer.y()); - } - } else { - // Extrude a sparse infill to support the material to be printed above. + if (m_is_first_layer && m_adhesion) { + // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. + box.expand(-m_perimeter_width/2.f); + int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width))); + float step = (box.lu.y - box.ld.y) / nsteps; + writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f)); + if (nsteps >= 0) + for (int i = 0; i < nsteps; ++i) { + writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step); + writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step); + writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y()); + } + writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + } + else { // Extrude a sparse infill to support the material to be printed above. + const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width); + const float left = fill_box.lu.x+2*m_perimeter_width; + const float right = fill_box.ru.x - 2 * m_perimeter_width; + if (dy > m_perimeter_width) + { + // Extrude an inverse U at the left of the region. + writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f)) + .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor); - // Extrude an inverse U at the left of the region. - writer.extrude(box.ld + xy(m_perimeter_width / 2, m_perimeter_width / 2)) - .extrude(fill_box.ld + xy(m_perimeter_width * 3, m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.lu + xy(m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.lu + xy(m_perimeter_width * 6, - m_perimeter_width)) - .extrude(fill_box.ld + xy(m_perimeter_width * 6, m_perimeter_width)); + const int n = 1+(right-left)/(m_bridging); + const float dx = (right-left)/n; + for (int i=1;i<=n;++i) { + float x=left+dx*i; + writer.travel(x,writer.y()); + writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y); + } + writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + } + else + writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + } + writer.append("; CP EMPTY GRID END\n" + ";------------------\n\n\n\n\n\n\n"); - if (fill_box.lu.y - fill_box.ld.y > 4.f) { - // Extrude three zig-zags. - float step = (m_wipe_tower_width - m_perimeter_width * 12.f) / 12.f; - for (size_t i = 0; i < 3; ++ i) { - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width * 8, 3200 * speed_factor); - writer.extrude(writer.x() , fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width ); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() , fill_box.ld.y + m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width ); - } - } - - // Extrude an inverse U at the left of the region. - writer.extrude(fill_box.ru + xy(- m_perimeter_width * 6, - m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.ru + xy(- m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width * 3, m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width)); - } - - // if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - if (true) - // Wipe along the front side of the current wiping box. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(fill_box.ld + xy( m_perimeter_width, m_perimeter_width / 2), 7200) - .travel(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width / 2)); - else - writer.feedrate(7200); - - writer.append("; CP EMPTY GRID END\n" - ";------------------\n\n\n\n\n\n\n"); - - // Indicate that this wipe tower layer is fully covered. - m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes; - } + m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; ToolChangeResult result; result.print_z = this->m_z_pos; @@ -1045,9 +1054,182 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } +// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box +void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool,bool brim) +{ + assert(m_plan.back().z <= z_par + WT_EPSILON ); // refuses to add a layer below the last one + + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first + m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + + if (brim) { // this toolchange prints brim - we must add it to m_plan, but not to count its depth + m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool)); + return; + } + + if (old_tool==new_tool) // new layer without toolchanges - we are done + return; + + // this is an actual toolchange - let's calculate depth to reserve on the wipe tower + float depth = 0.f; + float width = m_wipe_tower_width - 3*m_perimeter_width; + float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f), + m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator, + layer_height_par); + depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator); + float ramming_depth = depth; + length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width; + float first_wipe_line = -length_to_extrude; + length_to_extrude += volume_to_length(wipe_volumes[old_tool][new_tool], m_perimeter_width, layer_height_par); + length_to_extrude = std::max(length_to_extrude,0.f); + + depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; + depth *= m_extra_spacing; + + m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth,first_wipe_line)); +} + + + +void WipeTowerPrusaMM::plan_tower() +{ + // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards + m_wipe_tower_depth = 0.f; + for (auto& layer : m_plan) + layer.depth = 0.f; + + for (int layer_index = m_plan.size() - 1; layer_index >= 0; --layer_index) + { + float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth()); + m_plan[layer_index].depth = this_layer_depth; + + if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width) + m_wipe_tower_depth = this_layer_depth + m_perimeter_width; + + for (int i = layer_index - 1; i >= 0 ; i--) + { + if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width ) + m_plan[i].depth = this_layer_depth; + } + } +} + +void WipeTowerPrusaMM::save_on_last_wipe() +{ + for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); + if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer + continue; + + for (const auto &toolchange : m_layer_info->tool_changes) + tool_change(toolchange.new_tool, false); + + float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into + float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); + float length_to_wipe = volume_to_length(wipe_volumes[m_layer_info->tool_changes.back().old_tool][m_layer_info->tool_changes.back().new_tool], + m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save; + + length_to_wipe = std::max(length_to_wipe,0.f); + float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing; + + //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; + m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe; + } +} + + +// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower +// Resulting ToolChangeResults are appended into vector "result" +void WipeTowerPrusaMM::generate(std::vector> &result) +{ + if (m_plan.empty()) return; + + m_extra_spacing = 1.f; + + plan_tower(); + for (int i=0;i<5;++i) { + save_on_last_wipe(); + plan_tower(); + } + + if (m_peters_wipe_tower) + make_wipe_tower_square(); + + m_layer_info = m_plan.begin(); + + std::vector layer_result; + for (auto layer : m_plan) + { + set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z); + + + if (m_peters_wipe_tower) + m_wipe_tower_rotation_angle += 90.f; + else + m_wipe_tower_rotation_angle += 180.f; + + if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) + m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; + + for (const auto &toolchange : layer.tool_changes) + layer_result.emplace_back(tool_change(toolchange.new_tool, false)); + + if (! layer_finished()) { + auto finish_layer_toolchange = finish_layer(); + if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange + auto& last_toolchange = layer_result.back(); + if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) { + char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos. + sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x, finish_layer_toolchange.start_pos.y); + last_toolchange.gcode += buf; + } + last_toolchange.gcode += finish_layer_toolchange.gcode; + last_toolchange.extrusions.insert(last_toolchange.extrusions.end(),finish_layer_toolchange.extrusions.begin(),finish_layer_toolchange.extrusions.end()); + last_toolchange.end_pos = finish_layer_toolchange.end_pos; + } + else + layer_result.emplace_back(std::move(finish_layer_toolchange)); + } + + result.emplace_back(std::move(layer_result)); + m_is_first_layer = false; + } +} + + + + +void WipeTowerPrusaMM::make_wipe_tower_square() +{ + const float width = m_wipe_tower_width - 3 * m_perimeter_width; + const float depth = m_wipe_tower_depth - m_perimeter_width; + // area that we actually print into is width*depth + float side = sqrt(depth * width); + + m_wipe_tower_width = side + 3 * m_perimeter_width; + m_wipe_tower_depth = side + 2 * m_perimeter_width; + // For all layers, find how depth changed and update all toolchange depths + for (auto &lay : m_plan) + { + side = sqrt(lay.depth * width); + float width_ratio = width / side; + + //lay.extra_spacing = width_ratio; + for (auto &tch : lay.tool_changes) + tch.required_depth *= width_ratio; + } + + plan_tower(); // propagates depth downwards again (width has changed) + for (auto& lay : m_plan) // depths set, now the spacing + lay.extra_spacing = lay.depth / lay.toolchanges_depth(); + +} + + + }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index b8c7ab31f5..175de0276e 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -1,13 +1,14 @@ #ifndef WipeTowerPrusaMM_hpp_ #define WipeTowerPrusaMM_hpp_ -#include #include #include +#include #include #include "WipeTower.hpp" + namespace Slic3r { @@ -15,6 +16,8 @@ namespace PrusaMultiMaterial { class Writer; }; + + class WipeTowerPrusaMM : public WipeTower { public: @@ -39,65 +42,98 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(float x, float y, float width, float wipe_area, unsigned int initial_tool) : + WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, + float cooling_tube_length, float parking_pos_retraction, float bridging, const std::vector& wiping_matrix, + unsigned int initial_tool) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), - m_wipe_area(wipe_area), + m_wipe_tower_rotation_angle(rotation_angle), + m_y_shift(0.f), m_z_pos(0.f), - m_current_tool(initial_tool) + m_is_first_layer(false), + m_cooling_tube_retraction(cooling_tube_retraction), + m_cooling_tube_length(cooling_tube_length), + m_parking_pos_retraction(parking_pos_retraction), + m_bridging(bridging), + m_current_tool(initial_tool) { - for (size_t i = 0; i < 4; ++ i) { - // Extruder specific parameters. - m_material[i] = PLA; - m_temperature[i] = 0; - m_first_layer_temperature[i] = 0; - } + unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+WT_EPSILON); + for (unsigned int i = 0; i(wiping_matrix.begin()+i*number_of_extruders,wiping_matrix.begin()+(i+1)*number_of_extruders)); } virtual ~WipeTowerPrusaMM() {} - // _retract - retract value in mm - void set_retract(float retract) { m_retract = retract; } - - // _zHop - z hop value in mm - void set_zhop(float zhop) { m_zhop = zhop; } // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp) + void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, + float unloading_speed, float delay, float cooling_time, std::string ramming_parameters, float nozzle_diameter) { - m_material[idx] = material; - m_temperature[idx] = temp; - m_first_layer_temperature[idx] = first_layer_temp; + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = material; + m_filpar[idx].temperature = temp; + m_filpar[idx].first_layer_temperature = first_layer_temp; + m_filpar[idx].loading_speed = loading_speed; + m_filpar[idx].unloading_speed = unloading_speed; + m_filpar[idx].delay = delay; + m_filpar[idx].cooling_time = cooling_time; + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + std::stringstream stream{ramming_parameters}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); } + + // Appends into internal structure m_plan containing info about the future wipe tower + // to be used before building begins. The entries must be added ordered in z. + void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim); + + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" + void generate(std::vector> &result); + + + // Switch to a next layer. virtual void set_layer( // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, + float print_z, + // Layer height, used to calculate extrusion the rate. + float layer_height, // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, + size_t max_tool_changes, // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, + bool is_first_layer, // Is this the last layer of the waste tower? - bool is_last_layer) + bool is_last_layer) { m_z_pos = print_z; m_layer_height = layer_height; - m_max_color_changes = max_tool_changes; m_is_first_layer = is_first_layer; - m_is_last_layer = is_last_layer; - // Start counting the color changes from zero. Special case: -1 - extrude a brim first. - m_idx_tool_change_in_layer = is_first_layer ? (unsigned int)(-1) : 0; - m_current_wipe_start_y = 0.f; + m_print_brim = is_first_layer; + m_depth_traversed = 0.f; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; - ++ m_num_layer_changes; - // Extrusion rate for an extrusion aka perimeter width 0.35mm. - // Clamp the extrusion height to a 0.2mm layer height, independent of the nozzle diameter. -// m_extrusion_flow = std::min(0.2f, layer_height) * 0.145f; - // Use a strictly - m_extrusion_flow = layer_height * 0.145f; + if (is_first_layer) { + this->m_num_layer_changes = 0; + this->m_num_tool_changes = 0; + } + else + ++ m_num_layer_changes; + + // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: + m_extrusion_flow = extrusion_flow(layer_height); + + // Advance m_layer_info iterator, making sure we got it right + while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) + ++m_layer_info; } // Return the wipe tower position. @@ -115,79 +151,114 @@ public: const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE); + bool last_wipe_inside_wipe_tower); // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose); + virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(Purpose purpose); + virtual ToolChangeResult finish_layer(); + + // Is the current layer finished? + virtual bool layer_finished() const { + return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); + } - // Is the current layer finished? A layer is finished if either the wipe tower is finished, or - // the wipe tower has been completely covered by the tool change extrusions, - // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const - { return m_idx_tool_change_in_layer == m_max_color_changes; } private: WipeTowerPrusaMM(); - // A fill-in direction (positive Y, negative Y) alternates with each layer. - enum wipe_shape + enum wipe_shape // A fill-in direction { - SHAPE_NORMAL = 1, + SHAPE_NORMAL = 1, SHAPE_REVERSED = -1 }; - // Left front corner of the wipe tower in mm. - xy m_wipe_tower_pos; - // Width of the wipe tower. - float m_wipe_tower_width; - // Per color Y span. - float m_wipe_area; - // Current Z position. - float m_z_pos = 0.f; - // Current layer height. - float m_layer_height = 0.f; - // Maximum number of color changes per layer. - size_t m_max_color_changes = 0; - // Is this the 1st layer of the print? If so, print the brim around the waste tower. - bool m_is_first_layer = false; - // Is this the last layer of this waste tower? - bool m_is_last_layer = false; + + const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet + const float Filament_Area = M_PI * 1.75f * 1.75f / 4.f; // filament area in mm^2 + const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust + const float WT_EPSILON = 1e-3f; + + + xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + float m_wipe_tower_width; // Width of the wipe tower. + float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_y_shift = 0.f; // y shift passed to writer + float m_z_pos = 0.f; // Current Z position. + float m_layer_height = 0.f; // Current layer height. + size_t m_max_color_changes = 0; // Maximum number of color changes per layer. + bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower. // G-code generator parameters. - float m_zhop = 0.5f; - float m_retract = 4.f; - // Width of an extrusion line, also a perimeter spacing for 100% infill. - float m_perimeter_width = 0.5f; - // Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - float m_extrusion_flow = 0.029f; + float m_cooling_tube_retraction = 0.f; + float m_cooling_tube_length = 0.f; + float m_parking_pos_retraction = 0.f; + float m_bridging = 0.f; + bool m_adhesion = true; + + float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. + float m_extrusion_flow = 0.038; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. + + + struct FilamentParameters { + material_type material = PLA; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float unloading_speed = 0.f; + float delay = 0.f ; + float cooling_time = 0.f; + float ramming_line_width_multiplicator = 0.f; + float ramming_step_multiplicator = 0.f; + std::vector ramming_speed; + float nozzle_diameter; + }; // Extruder specific parameters. - material_type m_material[4]; - int m_temperature[4]; - int m_first_layer_temperature[4]; + std::vector m_filpar; - // State of the wiper tower generator. - // Layer change counter for the output statistics. - unsigned int m_num_layer_changes = 0; - // Tool change change counter for the output statistics. - unsigned int m_num_tool_changes = 0; - // Layer change counter in this layer. Counting up to m_max_color_changes. - unsigned int m_idx_tool_change_in_layer = 0; + + // State of the wipe tower generator. + unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. + unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. + ///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. + bool m_print_brim = true; // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; unsigned int m_current_tool = 0; - // Current y position at the wipe tower. - float m_current_wipe_start_y = 0.f; - // How much to wipe the 1st extruder over the wipe tower at the 1st layer - // after the wipe tower brim has been extruded? - float m_initial_extra_wipe = 0.f; + std::vector> wipe_volumes; + + float m_depth_traversed = 0.f; // Current y position at the wipe tower. + bool m_left_to_right = true; + float m_extra_spacing = 1.f; + + + // Calculates extrusion flow needed to produce required line width for given layer height + float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow + { + if ( layer_height < 0 ) + return m_extrusion_flow; + return layer_height * ( m_perimeter_width - layer_height * (1-M_PI/4.f)) / Filament_Area; + } + + // Calculates length of extrusion line to extrude given volume + float volume_to_length(float volume, float line_width, float layer_height) const { + return volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.))); + } + + // Calculates depth for all layers and propagates them downwards + void plan_tower(); + + // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental + void make_wipe_tower_square(); + + // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe + void save_on_last_wipe(); + struct box_coordinates { @@ -216,14 +287,42 @@ private: } xy ld; // left down xy lu; // left upper - xy ru; // right upper xy rd; // right lower + xy ru; // right upper }; + + // to store information about tool changes for a given layer + struct WipeTowerInfo{ + struct ToolChange { + unsigned int old_tool; + unsigned int new_tool; + float required_depth; + float ramming_depth; + float first_wipe_line; + ToolChange(unsigned int old,unsigned int newtool,float depth=0.f,float ramming_depth=0.f,float fwl=0.f) + : old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth},first_wipe_line{fwl} {} + }; + float z; // z position of the layer + float height; // layer height + float depth; // depth of the layer based on all layers above + float extra_spacing; + float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } + + std::vector tool_changes; + + WipeTowerInfo(float z_par, float layer_height_par) + : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} + }; + + std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) + std::vector::iterator m_layer_info = m_plan.end(); + + // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower + // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower - ToolChangeResult toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); + ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( PrusaMultiMaterial::Writer &writer, @@ -243,11 +342,12 @@ private: void toolchange_Wipe( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, - bool skip_initial_y_move); - - void toolchange_Perimeter(); + float wipe_volume); }; + + + }; // namespace Slic3r #endif /* WipeTowerPrusaMM_hpp_ */ diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index ad2ce54cd2..aaea863e83 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -16,6 +16,8 @@ namespace Slic3r { + unsigned int Model::s_auto_extruder_id = 1; + Model::Model(const Model &other) { // copy materials @@ -405,10 +407,19 @@ void Model::convert_multipart_object() ModelObject* object = new ModelObject(this); object->input_file = this->objects.front()->input_file; - + + reset_auto_extruder_id(); + for (const ModelObject* o : this->objects) for (const ModelVolume* v : o->volumes) - object->add_volume(*v)->name = o->name; + { + ModelVolume* new_v = object->add_volume(*v); + if (new_v != nullptr) + { + new_v->name = o->name; + new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string()); + } + } for (const ModelInstance* i : this->objects.front()->instances) object->add_instance(*i); @@ -466,6 +477,28 @@ bool Model::fits_print_volume(const FullPrintConfig &config) const return print_volume.contains(transformed_bounding_box()); } +unsigned int Model::get_auto_extruder_id() +{ + unsigned int id = s_auto_extruder_id; + + if (++s_auto_extruder_id > 4) + reset_auto_extruder_id(); + + return id; +} + +std::string Model::get_auto_extruder_id_as_string() +{ + char str_extruder[64]; + sprintf(str_extruder, "%ud", get_auto_extruder_id()); + return str_extruder; +} + +void Model::reset_auto_extruder_id() +{ + s_auto_extruder_id = 1; +} + ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : name(other.name), input_file(other.input_file), @@ -830,33 +863,8 @@ void ModelObject::cut(coordf_t z, Model* model) const lower->add_volume(*volume); } else { TriangleMesh upper_mesh, lower_mesh; - // TODO: shouldn't we use object bounding box instead of per-volume bb? - coordf_t cut_z = z + volume->mesh.bounding_box().min.z; - if (false) { -// if (volume->mesh.has_multiple_patches()) { - // Cutting algorithm does not work on intersecting meshes. - // As we are not sure whether the meshes don't intersect, - // we rather split the mesh into multiple non-intersecting pieces. - TriangleMeshPtrs meshptrs = volume->mesh.split(); - for (TriangleMeshPtrs::iterator mesh = meshptrs.begin(); mesh != meshptrs.end(); ++mesh) { - printf("Cutting mesh patch %d of %d\n", int(mesh - meshptrs.begin()), int(meshptrs.size())); - (*mesh)->repair(); - TriangleMeshSlicer tms(*mesh); - if (mesh == meshptrs.begin()) { - tms.cut(cut_z, &upper_mesh, &lower_mesh); - } else { - TriangleMesh upper_mesh_this, lower_mesh_this; - tms.cut(cut_z, &upper_mesh_this, &lower_mesh_this); - upper_mesh.merge(upper_mesh_this); - lower_mesh.merge(lower_mesh_this); - } - delete *mesh; - } - } else { - printf("Cutting mesh patch\n"); - TriangleMeshSlicer tms(&volume->mesh); - tms.cut(cut_z, &upper_mesh, &lower_mesh); - } + TriangleMeshSlicer tms(&volume->mesh); + tms.cut(z, &upper_mesh, &lower_mesh); upper_mesh.repair(); lower_mesh.repair(); @@ -995,6 +1003,9 @@ size_t ModelVolume::split() size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); std::string name = this->name; + + Model::reset_auto_extruder_id(); + for (TriangleMesh *mesh : meshptrs) { mesh->repair(); if (idx == 0) @@ -1004,6 +1015,7 @@ size_t ModelVolume::split() char str_idx[64]; sprintf(str_idx, "_%d", idx + 1); this->object->volumes[ivolume]->name = name + str_idx; + this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string()); delete mesh; ++ idx; } diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index bf7f60e36c..0c0ffe7769 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -230,6 +230,8 @@ private: // all objects may share mutliple materials. class Model { + static unsigned int s_auto_extruder_id; + public: // Materials are owned by a model and referenced by objects through t_model_material_id. // Single material may be shared by multiple models. @@ -288,6 +290,10 @@ public: bool fits_print_volume(const FullPrintConfig &config) const; void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } + + static unsigned int get_auto_extruder_id(); + static std::string get_auto_extruder_id_as_string(); + static void reset_auto_extruder_id(); }; } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index a3f5e231f7..c19c97faea 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -183,6 +183,11 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig.single_extruder_multi_material) { + for (size_t i=1; iconfig.nozzle_diameter.values.size(); ++i) + if (this->config.nozzle_diameter.values[i] != this->config.nozzle_diameter.values[i-1]) + return "All extruders must have the same diameter for single extruder multimaterial printer."; + } + if (this->has_wipe_tower() && ! this->objects.empty()) { #if 0 for (auto dmr : this->config.nozzle_diameter.values) @@ -582,6 +598,12 @@ std::string Print::validate() const if (! this->config.use_relative_e_distances) return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."; SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters(); + + const PrintObject* most_layered_object = this->objects.front(); // object with highest layer_height_profile.size() encountered so far + for (const auto* object : objects) + if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size()) + most_layered_object = object; + for (PrintObject *object : this->objects) { SlicingParameters slicing_params = object->slicing_parameters(); if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || @@ -596,10 +618,24 @@ 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 ) { + int i = 0; + while ( i < object->layer_height_profile.size() ) { + if (std::abs(most_layered_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; + if (i == object->layer_height_profile.size()-2) // this element contains the objects max z, if the other object is taller, + // it does not have to match - we will step over it + if (most_layered_object->layer_height_profile[i] > object->layer_height_profile[i]) + ++i; + } + } + + /*for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) if (object->layer_height_profile[i-1] > slicing_params.object_print_z_min + EPSILON && std::abs(object->layer_height_profile[i] - object->config.layer_height) > EPSILON) - return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed."; + return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed.";*/ } } @@ -613,7 +649,12 @@ std::string Print::validate() const for (unsigned int extruder_id : extruders) nozzle_diameters.push_back(this->config.nozzle_diameter.get_at(extruder_id)); double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); - + + unsigned int total_extruders_count = this->config.nozzle_diameter.size(); + for (const auto& extruder_idx : extruders) + if ( extruder_idx >= total_extruders_count ) + return "One or more object were assigned an extruder that the printer does not have."; + for (PrintObject *object : this->objects) { if ((object->config.support_material_extruder == -1 || object->config.support_material_interface_extruder == -1) && (object->config.raft_layers > 0 || object->config.support_material.value)) { @@ -1026,22 +1067,33 @@ void Print::_make_wipe_tower() } } + // Get wiping matrix to get number of extruders and convert vector to vector: + std::vector wiping_volumes((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end()); + // Initialize the wipe tower. WipeTowerPrusaMM wipe_tower( float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), - float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), - m_tool_ordering.first_extruder()); - + float(this->config.wipe_tower_width.value), + float(this->config.wipe_tower_rotation_angle.value), float(this->config.cooling_tube_retraction.value), + float(this->config.cooling_tube_length.value), float(this->config.parking_pos_retraction.value), + float(this->config.wipe_tower_bridging), wiping_volumes, m_tool_ordering.first_extruder()); + //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < 4; ++ i) + for (size_t i = 0; i < (int)(sqrt(wiping_volumes.size())+EPSILON); ++ i) wipe_tower.set_extruder( i, WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()), this->config.temperature.get_at(i), - this->config.first_layer_temperature.get_at(i)); + this->config.first_layer_temperature.get_at(i), + this->config.filament_loading_speed.get_at(i), + this->config.filament_unloading_speed.get_at(i), + this->config.filament_toolchange_delay.get_at(i), + this->config.filament_cooling_time.get_at(i), + this->config.filament_ramming_parameters.get_at(i), + this->config.nozzle_diameter.get_at(i)); // When printing the first layer's wipe tower, the first extruder is expected to be active and primed. // Therefore the number of wipe sections at the wipe tower will be (m_tool_ordering.front().extruders-1) at the 1st layer. @@ -1049,12 +1101,37 @@ void Print::_make_wipe_tower() bool last_priming_wipe_full = m_tool_ordering.front().extruders.size() > m_tool_ordering.front().wipe_tower_partitions; m_wipe_tower_priming = Slic3r::make_unique( - wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full)); + + + // Lets go through the wipe tower layers and determine pairs of extruder changes for each + // to pass to wipe_tower (so that it can use it for planning the layout of the tower) + { + unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + for (const auto &layer_tools : m_tool_ordering.layer_tools()) { // for all layers + if (!layer_tools.has_wipe_tower) continue; + bool first_layer = &layer_tools == &m_tool_ordering.front(); + wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); + for (const auto extruder_id : layer_tools.extruders) { + if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { + wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back()); + current_extruder_id = extruder_id; + } + } + if (&layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) + break; + } + } + + // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); + wipe_tower.generate(m_wipe_tower_tool_changes); + // Set current_extruder_id to the last extruder primed. - unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + /*unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); + for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { if (! layer_tools.has_wipe_tower) // This is a support only layer, or the wipe tower does not reach to this height. @@ -1098,7 +1175,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 +1194,7 @@ void Print::_make_wipe_tower() wipe_tower.set_layer(float(m_tool_ordering.back().print_z), float(layer_height), 0, false, true); } m_wipe_tower_final_purge = Slic3r::make_unique( - wipe_tower.tool_change((unsigned int)-1, false, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.tool_change((unsigned int)-1, false)); } std::string Print::output_filename() diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 5795f044bc..a851a078a5 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -166,6 +166,22 @@ PrintConfigDef::PrintConfigDef() def->cli = "cooling!"; def->default_value = new ConfigOptionBools { true }; + def = this->add("cooling_tube_retraction", coFloat); + def->label = L("Cooling tube position"); + def->tooltip = L("Distance of the center-point of the cooling tube from the extruder tip "); + def->sidetext = L("mm"); + def->cli = "cooling_tube_retraction=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(91.5f); + + def = this->add("cooling_tube_length", coFloat); + def->label = L("Cooling tube length"); + def->tooltip = L("Length of the cooling tube to limit space for cooling moves inside it "); + def->sidetext = L("mm"); + def->cli = "cooling_tube_length=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(5.f); + def = this->add("default_acceleration", coFloat); def->label = L("Default"); def->tooltip = L("This is the acceleration your printer will be reset to after " @@ -439,6 +455,49 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; + def = this->add("filament_loading_speed", coFloats); + def->label = L("Loading speed"); + def->tooltip = L("Speed used for loading the filament on the wipe tower. "); + def->sidetext = L("mm/s"); + def->cli = "filament-loading-speed=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 28. }; + + def = this->add("filament_unloading_speed", coFloats); + def->label = L("Unloading speed"); + def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect " + " initial part of unloading just after ramming). "); + def->sidetext = L("mm/s"); + def->cli = "filament-unloading-speed=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 90. }; + + def = this->add("filament_toolchange_delay", coFloats); + def->label = L("Delay after unloading"); + def->tooltip = L("Time to wait after the filament is unloaded. " + "May help to get reliable toolchanges with flexible materials " + "that may need more time to shrink to original dimensions. "); + def->sidetext = L("s"); + def->cli = "filament-toolchange-delay=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 0. }; + + def = this->add("filament_cooling_time", coFloats); + def->label = L("Cooling time"); + def->tooltip = L("The filament is slowly moved back and forth after retraction into the cooling tube " + "for this amount of time."); + def->cli = "filament_cooling_time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 14.f }; + + def = this->add("filament_ramming_parameters", coStrings); + def->label = L("Ramming parameters"); + def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters "); + def->cli = "filament-ramming-parameters=s@"; + def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" + " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }; + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper " @@ -977,6 +1036,15 @@ PrintConfigDef::PrintConfigDef() def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); + def = this->add("parking_pos_retraction", coFloat); + def->label = L("Filament parking position"); + def->tooltip = L("Distance of the extruder tip from the position where the filament is parked " + "when unloaded. This should match the value in printer firmware. "); + def->sidetext = L("mm"); + def->cli = "parking_pos_retraction=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(92.f); + def = this->add("perimeter_acceleration", coFloat); def->label = L("Perimeters"); def->tooltip = L("This is the acceleration your printer will use for perimeters. " @@ -1744,6 +1812,25 @@ PrintConfigDef::PrintConfigDef() def->cli = "wipe-tower!"; def->default_value = new ConfigOptionBool(false); + def = this->add("wiping_volumes_extruders", coFloats); + def->label = L("Purging volumes - load/unload volumes"); + def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " + "wipe tower. These values are used to simplify creation of the full purging " + "volumes below. "); + def->cli = "wiping-volumes-extruders=f@"; + def->default_value = new ConfigOptionFloats { 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f }; + + def = this->add("wiping_volumes_matrix", coFloats); + def->label = L("Purging volumes - matrix"); + def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the" + " new filament on the wipe tower for any given pair of tools. "); + def->cli = "wiping-volumes-matrix=f@"; + def->default_value = new ConfigOptionFloats { 0.f, 140.f, 140.f, 140.f, 140.f, + 140.f, 0.f, 140.f, 140.f, 140.f, + 140.f, 140.f, 0.f, 140.f, 140.f, + 140.f, 140.f, 140.f, 0.f, 140.f, + 140.f, 140.f, 140.f, 140.f, 0.f }; + def = this->add("wipe_tower_x", coFloat); def->label = L("Position X"); def->tooltip = L("X coordinate of the left front corner of a wipe tower"); @@ -1765,14 +1852,19 @@ PrintConfigDef::PrintConfigDef() def->cli = "wipe-tower-width=f"; def->default_value = new ConfigOptionFloat(60.); - def = this->add("wipe_tower_per_color_wipe", coFloat); - def->label = L("Per color change depth"); - def->tooltip = L("Depth of a wipe color per color change. For N colors, there will be " - "maximum (N-1) tool switches performed, therefore the total depth " - "of the wipe tower will be (N-1) times this value."); + def = this->add("wipe_tower_rotation_angle", coFloat); + def->label = L("Wipe tower rotation angle"); + def->tooltip = L("Wipe tower rotation angle with respect to x-axis "); + def->sidetext = L("degrees"); + def->cli = "wipe-tower-rotation-angle=f"; + def->default_value = new ConfigOptionFloat(0.); + + def = this->add("wipe_tower_bridging", coFloat); + def->label = L("Maximal bridging distance"); + def->tooltip = L("Maximal distance between supports on sparse infill sections. "); def->sidetext = L("mm"); - def->cli = "wipe-tower-per-color-wipe=f"; - def->default_value = new ConfigOptionFloat(15.); + def->cli = "wipe-tower-bridging=f"; + def->default_value = new ConfigOptionFloat(10.); def = this->add("xy_size_compensation", coFloat); def->label = L("XY Size Compensation"); @@ -1852,8 +1944,9 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", "seal_position", "vibration_limit", "bed_size", "octoprint_host", - "print_center", "g0", "threads", "pressure_advance" + "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" }; + if (ignore.find(opt_key) != ignore.end()) { opt_key = ""; return; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index cb94a79211..967a873107 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -154,6 +154,13 @@ public: // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); + + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override + { PrintConfigDef::handle_legacy(opt_key, value); } }; template @@ -466,6 +473,11 @@ public: ConfigOptionBools filament_soluble; ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; + ConfigOptionFloats filament_loading_speed; + ConfigOptionFloats filament_unloading_speed; + ConfigOptionFloats filament_toolchange_delay; + ConfigOptionFloats filament_cooling_time; + ConfigOptionStrings filament_ramming_parameters; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; @@ -491,7 +503,11 @@ public: ConfigOptionBool use_relative_e_distances; ConfigOptionBool use_volumetric_e; ConfigOptionBool variable_layer_height; - + ConfigOptionFloat cooling_tube_retraction; + ConfigOptionFloat cooling_tube_length; + ConfigOptionFloat parking_pos_retraction; + + std::string get_extrusion_axis() const { return @@ -515,6 +531,11 @@ protected: OPT_PTR(filament_soluble); OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); + OPT_PTR(filament_loading_speed); + OPT_PTR(filament_unloading_speed); + OPT_PTR(filament_toolchange_delay); + OPT_PTR(filament_cooling_time); + OPT_PTR(filament_ramming_parameters); OPT_PTR(gcode_comments); OPT_PTR(gcode_flavor); OPT_PTR(layer_gcode); @@ -540,6 +561,9 @@ protected: OPT_PTR(use_relative_e_distances); OPT_PTR(use_volumetric_e); OPT_PTR(variable_layer_height); + OPT_PTR(cooling_tube_retraction); + OPT_PTR(cooling_tube_length); + OPT_PTR(parking_pos_retraction); } }; @@ -610,6 +634,10 @@ public: ConfigOptionFloat wipe_tower_y; ConfigOptionFloat wipe_tower_width; ConfigOptionFloat wipe_tower_per_color_wipe; + ConfigOptionFloat wipe_tower_rotation_angle; + ConfigOptionFloat wipe_tower_bridging; + ConfigOptionFloats wiping_volumes_matrix; + ConfigOptionFloats wiping_volumes_extruders; ConfigOptionFloat z_offset; protected: @@ -675,6 +703,10 @@ protected: OPT_PTR(wipe_tower_y); OPT_PTR(wipe_tower_width); OPT_PTR(wipe_tower_per_color_wipe); + OPT_PTR(wipe_tower_rotation_angle); + OPT_PTR(wipe_tower_bridging); + OPT_PTR(wiping_volumes_matrix); + OPT_PTR(wiping_volumes_extruders); OPT_PTR(z_offset); } }; @@ -713,6 +745,7 @@ class FullPrintConfig : public: // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); + protected: // Protected constructor to be called to initialize ConfigCache::m_default. FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {} diff --git a/xs/src/slic3r/GUI/2DBed.hpp b/xs/src/slic3r/GUI/2DBed.hpp index 859417efb0..a170b708ad 100644 --- a/xs/src/slic3r/GUI/2DBed.hpp +++ b/xs/src/slic3r/GUI/2DBed.hpp @@ -6,7 +6,7 @@ namespace GUI { class Bed_2D : public wxPanel { - bool m_user_drawn_background = false; + bool m_user_drawn_background = true; bool m_painted = false; bool m_interactive = false; @@ -26,7 +26,9 @@ public: { Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL); // m_user_drawn_background = $^O ne 'darwin'; - m_user_drawn_background = true; +#ifdef __APPLE__ + m_user_drawn_background = false; +#endif /*__APPLE__*/ Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); })); // EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background}; // Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event){/*mouse_event()*/; })); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index a459b7cbf5..aa86ae203f 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -9,6 +9,7 @@ #include "../../libslic3r/GCode/PreviewData.hpp" #include "../../libslic3r/Print.hpp" #include "../../libslic3r/Slicing.hpp" +#include "../../slic3r/GUI/PresetBundle.hpp" #include "GCode/Analyzer.hpp" #include @@ -304,7 +305,7 @@ void GLVolume::render_using_layer_height() const glUniform1f(z_texture_row_to_normalized_id, (GLfloat)(1.0f / layer_height_texture_height())); if (z_cursor_id >= 0) - glUniform1f(z_cursor_id, (GLfloat)(bounding_box.max.z * layer_height_texture_data.z_cursor_relative)); + glUniform1f(z_cursor_id, (GLfloat)(layer_height_texture_data.print_object->model_object()->bounding_box().max.z * layer_height_texture_data.z_cursor_relative)); if (z_cursor_band_width_id >= 0) glUniform1f(z_cursor_band_width_id, (GLfloat)layer_height_texture_data.edit_band_width); @@ -326,6 +327,11 @@ void GLVolume::render_using_layer_height() const glUseProgram(current_program_id); } +double GLVolume::layer_height_texture_z_to_row_id() const +{ + return (this->layer_height_texture.get() == nullptr) ? 0.0 : double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * this->layer_height_texture_data.print_object->model_object()->bounding_box().max.z); +} + void GLVolume::generate_layer_height_texture(PrintObject *print_object, bool force) { GLTexture *tex = this->layer_height_texture.get(); @@ -381,6 +387,15 @@ std::vector GLVolumeCollection::load_object( std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; + + int extruder_id = -1; + if (!model_volume->modifier) + { + extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; + if (extruder_id == 0) + extruder_id = model_object->config.has("extruder") ? model_object->config.option("extruder")->getInt() : 0; + } + for (int instance_idx : instance_idxs) { const ModelInstance *instance = model_object->instances[instance_idx]; TriangleMesh mesh = model_volume->mesh; @@ -410,8 +425,14 @@ std::vector GLVolumeCollection::load_object( v.drag_group_id = obj_idx * 1000; else if (drag_by == "instance") v.drag_group_id = obj_idx * 1000 + instance_idx; - if (! model_volume->modifier) + + if (!model_volume->modifier) + { v.layer_height_texture = layer_height_texture; + if (extruder_id != -1) + v.extruder_id = extruder_id; + } + v.is_modifier = model_volume->modifier; } } @@ -420,12 +441,17 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs) { float color[4] = { 1.0f, 1.0f, 0.0f, 0.5f }; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); - auto mesh = make_cube(width, depth, height); + + auto mesh = make_cube(width, depth, height); + mesh.translate(-width/2.f,-depth/2.f,0.f); + Point origin_of_rotation(0.f,0.f); + mesh.rotate(rotation_angle,&origin_of_rotation); + if (use_VBOs) v.indexed_vertex_array.load_mesh_full_shading(mesh); else @@ -438,6 +464,7 @@ int GLVolumeCollection::load_wipe_tower_preview( v.composite_id = obj_idx * 1000000; v.select_group_id = obj_idx * 1000000; v.drag_group_id = obj_idx * 1000; + v.is_wipe_tower = true; return int(this->volumes.size() - 1); } @@ -642,6 +669,83 @@ void GLVolumeCollection::update_outside_state(const DynamicPrintConfig* config, } } +void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* config) +{ + static const float inv_255 = 1.0f / 255.0f; + + struct Color + { + std::string text; + unsigned char rgb[3]; + + Color() + : text("") + { + rgb[0] = 255; + rgb[1] = 255; + rgb[2] = 255; + } + + void set(const std::string& text, unsigned char* rgb) + { + this->text = text; + ::memcpy((void*)this->rgb, (const void*)rgb, 3 * sizeof(unsigned char)); + } + }; + + if (config == nullptr) + return; + + const ConfigOptionStrings* extruders_opt = dynamic_cast(config->option("extruder_colour")); + if (extruders_opt == nullptr) + return; + + const ConfigOptionStrings* filamemts_opt = dynamic_cast(config->option("filament_colour")); + if (filamemts_opt == nullptr) + return; + + unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()); + if (colors_count == 0) + return; + + std::vector colors(colors_count); + + unsigned char rgb[3]; + for (unsigned int i = 0; i < colors_count; ++i) + { + const std::string& txt_color = config->opt_string("extruder_colour", i); + if (PresetBundle::parse_color(txt_color, rgb)) + { + colors[i].set(txt_color, rgb); + } + else + { + const std::string& txt_color = config->opt_string("filament_colour", i); + if (PresetBundle::parse_color(txt_color, rgb)) + colors[i].set(txt_color, rgb); + } + } + + for (GLVolume* volume : volumes) + { + if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower) + continue; + + int extruder_id = volume->extruder_id - 1; + if ((extruder_id < 0) || ((unsigned int)colors.size() <= extruder_id)) + extruder_id = 0; + + const Color& color = colors[extruder_id]; + if (!color.text.empty()) + { + for (int i = 0; i < 3; ++i) + { + volume->color[i] = (float)color.rgb[i] * inv_255; + } + } + } +} + std::vector GLVolumeCollection::get_current_print_zs() const { // Collect layer top positions of all volumes. @@ -2594,8 +2698,10 @@ void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, boo coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z; const PrintConfig& config = print.config; unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) - volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, config.wipe_tower_per_color_wipe * (extruders_count - 1), max_z, use_VBOs); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete + volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs); + } } -} +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 0fd31d8d69..8f03e47746 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -246,12 +246,15 @@ public: composite_id(-1), select_group_id(-1), drag_group_id(-1), + extruder_id(0), selected(false), is_active(true), zoom_to_volumes(true), outside_printer_detection_enabled(true), is_outside(false), hover(false), + is_modifier(false), + is_wipe_tower(false), tverts_range(0, size_t(-1)), qverts_range(0, size_t(-1)) { @@ -271,7 +274,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; @@ -287,6 +290,8 @@ public: int select_group_id; // An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance. int drag_group_id; + // An ID containing the extruder ID (used to select color). + int extruder_id; // Is this object selected? bool selected; // Whether or not this volume is active for rendering @@ -299,6 +304,10 @@ public: bool is_outside; // Boolean: Is mouse over this object? bool hover; + // Wheter or not this volume has been generated from a modifier + bool is_modifier; + // Wheter or not this volume has been generated from the wipe tower + bool is_wipe_tower; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; @@ -352,10 +361,7 @@ public: return (layer_height_texture.get() == nullptr) ? 0 : (void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4); } - double layer_height_texture_z_to_row_id() const { - return (this->layer_height_texture.get() == nullptr) ? 0. : - double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * bounding_box.max.z); - } + double layer_height_texture_z_to_row_id() const; void generate_layer_height_texture(PrintObject *print_object, bool force); void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, PrintObject* print_object, float z_cursor_relative, float edit_band_width) @@ -392,7 +398,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; @@ -417,6 +423,7 @@ public: } void update_outside_state(const DynamicPrintConfig* config, bool all_inside); + void update_colors_by_extruder(const DynamicPrintConfig* config); // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection std::vector get_current_print_zs() const; diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 24d84a7df7..3dd60ef88a 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -34,7 +34,7 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) { -// on_change(nullptr); +// on_change(nullptr); auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); diff --git a/xs/src/slic3r/GUI/BitmapCache.cpp b/xs/src/slic3r/GUI/BitmapCache.cpp index f92369650c..93853458e4 100644 --- a/xs/src/slic3r/GUI/BitmapCache.cpp +++ b/xs/src/slic3r/GUI/BitmapCache.cpp @@ -1,5 +1,14 @@ #include "BitmapCache.hpp" +#if ! defined(WIN32) && ! defined(__APPLE__) +#define BROKEN_ALPHA +#endif + +#ifdef BROKEN_ALPHA + #include + #include +#endif /* BROKEN_ALPHA */ + namespace Slic3r { namespace GUI { void BitmapCache::clear() @@ -8,19 +17,31 @@ void BitmapCache::clear() delete bitmap.second; } +static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image) +{ +#ifdef BROKEN_ALPHA + wxMemoryOutputStream stream; + image.SaveFile(stream, wxBITMAP_TYPE_PNG); + wxStreamBuffer *buf = stream.GetOutputStreamBuffer(); + return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize()); +#else + return wxBitmap(std::move(image)); +#endif +} + wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) { - wxBitmap *bitmap = nullptr; - auto it = m_map.find(bitmap_key); - if (it == m_map.end()) { - bitmap = new wxBitmap(width, height); - m_map[bitmap_key] = bitmap; - } else { - bitmap = it->second; - if (bitmap->GetWidth() != width || bitmap->GetHeight() != height) - bitmap->Create(width, height); - } -#if defined(__APPLE__) || defined(_MSC_VER) + wxBitmap *bitmap = nullptr; + auto it = m_map.find(bitmap_key); + if (it == m_map.end()) { + bitmap = new wxBitmap(width, height); + m_map[bitmap_key] = bitmap; + } else { + bitmap = it->second; + if (bitmap->GetWidth() != width || bitmap->GetHeight() != height) + bitmap->Create(width, height); + } +#ifndef BROKEN_ALPHA bitmap->UseAlpha(); #endif return bitmap; @@ -28,77 +49,108 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp) { - wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth(), bmp.GetHeight()); - - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - memDC.DrawBitmap(bmp, 0, 0, true); - memDC.SelectObject(wxNullBitmap); - - return bitmap; + wxBitmap *bitmap = nullptr; + auto it = m_map.find(bitmap_key); + if (it == m_map.end()) { + bitmap = new wxBitmap(bmp); + m_map[bitmap_key] = bitmap; + } else { + bitmap = it->second; + *bitmap = bmp; + } + return bitmap; } wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2) { - wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth(), std::max(bmp.GetHeight(), bmp2.GetHeight())); - - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - if (bmp.GetWidth() > 0) - memDC.DrawBitmap(bmp, 0, 0, true); - if (bmp2.GetWidth() > 0) - memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); - memDC.SelectObject(wxNullBitmap); - - return bitmap; + // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. + const wxBitmap bmps[2] = { bmp, bmp2 }; + return this->insert(bitmap_key, bmps, bmps + 2); } wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3) { - wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth() + bmp3.GetWidth(), std::max(std::max(bmp.GetHeight(), bmp2.GetHeight()), bmp3.GetHeight())); - - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - if (bmp.GetWidth() > 0) - memDC.DrawBitmap(bmp, 0, 0, true); - if (bmp2.GetWidth() > 0) - memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); - if (bmp3.GetWidth() > 0) - memDC.DrawBitmap(bmp3, bmp.GetWidth() + bmp2.GetWidth(), 0, true); - memDC.SelectObject(wxNullBitmap); - - return bitmap; + // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. + const wxBitmap bmps[3] = { bmp, bmp2, bmp3 }; + return this->insert(bitmap_key, bmps, bmps + 3); } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, std::vector &bmps) +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end) { - size_t width = 0; - size_t height = 0; - for (wxBitmap &bmp : bmps) { - width += bmp.GetWidth(); - height = std::max(height, bmp.GetHeight()); - } - wxBitmap *bitmap = this->insert(bitmap_key, width, height); + size_t width = 0; + size_t height = 0; + for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { + width += bmp->GetWidth(); + height = std::max(height, bmp->GetHeight()); + } +#ifdef BROKEN_ALPHA + + wxImage image(width, height); + image.InitAlpha(); + // Fill in with a white color. + memset(image.GetData(), 0x0ff, width * height * 3); + // Fill in with full transparency. + memset(image.GetAlpha(), 0, width * height); + size_t x = 0; + for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { + if (bmp->GetWidth() > 0) { + if (bmp->GetDepth() == 32) { + wxAlphaPixelData data(*const_cast(bmp)); + data.UseAlpha(); + if (data) { + for (int r = 0; r < bmp->GetHeight(); ++ r) { + wxAlphaPixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char *dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { + *dst_pixels ++ = src.Red(); + *dst_pixels ++ = src.Green(); + *dst_pixels ++ = src.Blue(); + *dst_alpha ++ = src.Alpha(); + } + } + } + } else if (bmp->GetDepth() == 24) { + wxNativePixelData data(*const_cast(bmp)); + if (data) { + for (int r = 0; r < bmp->GetHeight(); ++ r) { + wxNativePixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char *dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { + *dst_pixels ++ = src.Red(); + *dst_pixels ++ = src.Green(); + *dst_pixels ++ = src.Blue(); + *dst_alpha ++ = wxALPHA_OPAQUE; + } + } + } + } + } + x += bmp->GetWidth(); + } + return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); + +#else + + wxBitmap *bitmap = this->insert(bitmap_key, width, height); wxMemoryDC memDC; memDC.SelectObject(*bitmap); memDC.SetBackground(*wxTRANSPARENT_BRUSH); memDC.Clear(); size_t x = 0; - for (wxBitmap &bmp : bmps) { - if (bmp.GetWidth() > 0) - memDC.DrawBitmap(bmp, x, 0, true); - x += bmp.GetWidth(); - } + for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { + if (bmp->GetWidth() > 0) + memDC.DrawBitmap(*bmp, x, 0, true); + x += bmp->GetWidth(); + } memDC.SelectObject(wxNullBitmap); + return bitmap; - return bitmap; +#endif } wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) @@ -113,7 +165,7 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi *imgdata ++ = b; *imgalpha ++ = transparency; } - return wxBitmap(std::move(image)); + return wxImage_to_wxBitmap_with_alpha(std::move(image)); } } // namespace GUI diff --git a/xs/src/slic3r/GUI/BitmapCache.hpp b/xs/src/slic3r/GUI/BitmapCache.hpp index 0cf9d8acf0..bec9a7ad25 100644 --- a/xs/src/slic3r/GUI/BitmapCache.hpp +++ b/xs/src/slic3r/GUI/BitmapCache.hpp @@ -27,7 +27,8 @@ public: wxBitmap* insert(const std::string &name, const wxBitmap &bmp); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); - wxBitmap* insert(const std::string &name, std::vector &bmps); + wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } + wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index fee08e27ed..c68b2304c0 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -75,13 +75,13 @@ namespace Slic3r { namespace GUI { return tooltip_text; } - bool Field::is_matched(std::string string, std::string pattern) + bool Field::is_matched(const std::string& string, const std::string& pattern) { std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl return std::regex_match(string, regex_pattern); } - boost::any Field::get_value_by_opt_type(wxString str) + boost::any Field::get_value_by_opt_type(wxString& str) { boost::any ret_val; switch (m_opt.type){ @@ -173,8 +173,7 @@ namespace Slic3r { namespace GUI { temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) { -// on_kill_focus(e); - e.Skip(); + e.Skip();// on_kill_focus(e); temp->GetToolTip()->Enable(true); }), temp->GetId()); @@ -378,7 +377,7 @@ void Choice::set_selection() } } -void Choice::set_value(const std::string value, bool change_event) //! Redundant? +void Choice::set_value(const std::string& value, bool change_event) //! Redundant? { m_disable_change_event = !change_event; @@ -397,7 +396,7 @@ void Choice::set_value(const std::string value, bool change_event) //! Redundan m_disable_change_event = false; } -void Choice::set_value(boost::any value, bool change_event) +void Choice::set_value(const boost::any& value, bool change_event) { m_disable_change_event = !change_event; @@ -436,7 +435,7 @@ void Choice::set_value(boost::any value, bool change_event) } //! it's needed for _update_serial_ports() -void Choice::set_values(const std::vector values) +void Choice::set_values(const std::vector& values) { if (values.empty()) return; @@ -555,7 +554,7 @@ void PointCtrl::BUILD() y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y)); } -void PointCtrl::set_value(const Pointf value, bool change_event) +void PointCtrl::set_value(const Pointf& value, bool change_event) { m_disable_change_event = !change_event; @@ -567,10 +566,10 @@ void PointCtrl::set_value(const Pointf value, bool change_event) m_disable_change_event = false; } -void PointCtrl::set_value(boost::any value, bool change_event) +void PointCtrl::set_value(const boost::any& value, bool change_event) { Pointf pt; - Pointf *ptf = boost::any_cast(&value); + const Pointf *ptf = boost::any_cast(&value); if (!ptf) { ConfigOptionPoints* pts = boost::any_cast(value); @@ -578,21 +577,6 @@ void PointCtrl::set_value(boost::any value, bool change_event) } else pt = *ptf; -// try -// { -// pt = boost::any_cast(value)->values.at(0); -// } -// catch (const std::exception &e) -// { -// try{ -// pt = boost::any_cast(value); -// } -// catch (const std::exception &e) -// { -// std::cerr << "Error! Can't cast PointCtrl value" << m_opt_id << "\n"; -// return; -// } -// } set_value(pt, change_event); } diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index da3e23ccd8..cdc7c0d812 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -31,8 +31,8 @@ namespace Slic3r { namespace GUI { class Field; using t_field = std::unique_ptr; using t_kill_focus = std::function; -using t_change = std::function; -using t_back_to_init = std::function; +using t_change = std::function; +using t_back_to_init = std::function; wxString double_to_string(double const value); @@ -81,7 +81,7 @@ public: /// Sets a value for this control. /// subclasses should overload with a specific version /// Postcondition: Method does not fire the on_change event. - virtual void set_value(boost::any value, bool change_event) = 0; + virtual void set_value(const boost::any& value, bool change_event) = 0; /// Gets a boost::any representing this control. /// subclasses should overload with a specific version @@ -100,7 +100,7 @@ public: virtual wxString get_tooltip_text(const wxString& default_string); // set icon to "UndoToSystemValue" button according to an inheritance of preset - void set_nonsys_btn_icon(const std::string& icon); + void set_nonsys_btn_icon(const std::string& icon); Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; @@ -109,8 +109,8 @@ public: virtual wxSizer* getSizer() { return nullptr; } virtual wxWindow* getWindow() { return nullptr; } - bool is_matched(std::string string, std::string pattern); - boost::any get_value_by_opt_type(wxString str); + bool is_matched(const std::string& string, const std::string& pattern); + boost::any get_value_by_opt_type(wxString& str); /// Factory method for generating new derived classes. template @@ -137,16 +137,17 @@ class TextCtrl : public Field { public: TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~TextCtrl() {} void BUILD(); wxWindow* window {nullptr}; - virtual void set_value(std::string value, bool change_event = false) { + virtual void set_value(const std::string& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(wxString(value)); m_disable_change_event = false; } - virtual void set_value(boost::any value, bool change_event = false) { + virtual void set_value(const boost::any& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(boost::any_cast(value)); m_disable_change_event = false; @@ -164,6 +165,7 @@ class CheckBox : public Field { public: CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~CheckBox() {} wxWindow* window{ nullptr }; void BUILD() override; @@ -173,7 +175,7 @@ public: dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(boost::any value, bool change_event = false) { + void set_value(const boost::any& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(boost::any_cast(value)); m_disable_change_event = false; @@ -190,21 +192,22 @@ class SpinCtrl : public Field { public: SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(-9999) {} SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(-9999) {} + ~SpinCtrl() {} int tmp_value; wxWindow* window{ nullptr }; void BUILD() override; - void set_value(const std::string value, bool change_event = false) { + void set_value(const std::string& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(boost::any value, bool change_event = false) { + void set_value(const boost::any& value, bool change_event = false) { m_disable_change_event = !change_event; tmp_value = boost::any_cast(value); - dynamic_cast(window)->SetValue(tmp_value/*boost::any_cast(value)*/); + dynamic_cast(window)->SetValue(tmp_value); m_disable_change_event = false; } boost::any get_value() override { @@ -221,14 +224,15 @@ class Choice : public Field { public: Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~Choice() {} wxWindow* window{ nullptr }; void BUILD() override; void set_selection(); - void set_value(const std::string value, bool change_event = false); - void set_value(boost::any value, bool change_event = false); - void set_values(const std::vector values); + void set_value(const std::string& value, bool change_event = false); + void set_value(const boost::any& value, bool change_event = false); + void set_values(const std::vector &values); boost::any get_value() override; void enable() override { dynamic_cast(window)->Enable(); }; @@ -241,16 +245,17 @@ class ColourPicker : public Field { public: ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~ColourPicker() {} wxWindow* window{ nullptr }; void BUILD() override; - void set_value(const std::string value, bool change_event = false) { + void set_value(const std::string& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetColour(value); m_disable_change_event = false; } - void set_value(boost::any value, bool change_event = false) { + void set_value(const boost::any& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetColour(boost::any_cast(value)); m_disable_change_event = false; @@ -268,23 +273,24 @@ class PointCtrl : public Field { public: PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~PointCtrl() {} wxSizer* sizer{ nullptr }; - wxTextCtrl* x_textctrl; - wxTextCtrl* y_textctrl; + wxTextCtrl* x_textctrl{ nullptr }; + wxTextCtrl* y_textctrl{ nullptr }; void BUILD() override; - void set_value(const Pointf value, bool change_event = false); - void set_value(boost::any value, bool change_event = false); + void set_value(const Pointf& value, bool change_event = false); + void set_value(const boost::any& value, bool change_event = false); boost::any get_value() override; void enable() override { x_textctrl->Enable(); - y_textctrl->Enable(); }; + y_textctrl->Enable(); } void disable() override{ x_textctrl->Disable(); - y_textctrl->Disable(); }; + y_textctrl->Disable(); } wxSizer* getSizer() override { return sizer; } }; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index ebc62af1bc..96d1371658 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1,6 +1,8 @@ #include "GUI.hpp" +#include "WipeTowerDialog.hpp" #include +#include #include #include @@ -38,6 +40,7 @@ #include #include #include +#include #include "wxExtensions.hpp" @@ -182,6 +185,8 @@ wxNotebook *g_wxTabPanel = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; PresetUpdater *g_PresetUpdater = nullptr; +wxColour g_color_label_modified; +wxColour g_color_label_sys; std::vector g_tabs_list; @@ -189,10 +194,24 @@ wxLocale* g_wxLocale; std::shared_ptr m_optgroup; double m_brim_width = 0.0; +wxButton* g_wiping_dialog_button = nullptr; + +static void init_label_colours() +{ + auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + if (luma >= 128) { + g_color_label_modified = wxColour(253, 88, 0); + g_color_label_sys = wxColour(26, 132, 57); + } else { + g_color_label_modified = wxColour(253, 111, 40); + g_color_label_sys = wxColour(115, 220, 103); + } +} void set_wxapp(wxApp *app) { g_wxApp = app; + init_label_colours(); } void set_main_frame(wxFrame *main_frame) @@ -531,7 +550,7 @@ TabIface* get_preset_tab_iface(char *name) } // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) -void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index /*= 0*/) +void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ switch (config.def()->get(opt_key)->type){ @@ -567,11 +586,18 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast(value))); break; case coStrings:{ - if (opt_key.compare("compatible_printers") == 0 || - config.def()->get(opt_key)->gui_flags.compare("serialized") == 0){ - config.option(opt_key)->values.resize(0); - std::vector values = boost::any_cast>(value); - if (values.size() == 1 && values[0] == "") + if (opt_key.compare("compatible_printers") == 0) { + config.option(opt_key)->values = + boost::any_cast>(value); + } + else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0){ + std::string str = boost::any_cast(value); + if (str.back() == ';') str.pop_back(); + // Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values. + // Currently used for the post_process config value only. + std::vector values; + boost::split(values, str, boost::is_any_of(";")); + if (values.size() == 1 && values[0] == "") break; config.option(opt_key)->values = values; } @@ -638,17 +664,17 @@ void add_created_tab(Tab* panel) g_wxTabPanel->AddPage(panel, panel->title()); } -void show_error(wxWindow* parent, wxString message){ +void show_error(wxWindow* parent, const wxString& message){ auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR); msg_wingow->ShowModal(); } -void show_info(wxWindow* parent, wxString message, wxString title){ +void show_info(wxWindow* parent, const wxString& message, const wxString& title){ auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); msg_wingow->ShowModal(); } -void warning_catcher(wxWindow* parent, wxString message){ +void warning_catcher(wxWindow* parent, const wxString& message){ if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) ) return; auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); @@ -659,12 +685,25 @@ wxApp* get_app(){ return g_wxApp; } -wxColour* get_modified_label_clr(){ - return new wxColour(253, 88, 0); +const wxColour& get_modified_label_clr() { + return g_color_label_modified; } -wxColour* get_sys_label_clr(){ - return new wxColour(26, 132, 57); +const wxColour& get_sys_label_clr() { + return g_color_label_sys; +} + +unsigned get_colour_approx_luma(const wxColour &colour) +{ + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); } void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) @@ -829,6 +868,33 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl option = Option(def, "brim"); m_optgroup->append_single_option_line(option); + + Line line = { _(L("")), "" }; + line.widget = [config](wxWindow* parent){ + g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + "\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(g_wiping_dialog_button); + g_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) + { + auto &config = g_PresetBundle->project_config; + std::vector init_matrix = (config.option("wiping_volumes_matrix"))->values; + std::vector init_extruders = (config.option("wiping_volumes_extruders"))->values; + + WipingDialog dlg(parent,std::vector(init_matrix.begin(),init_matrix.end()),std::vector(init_extruders.begin(),init_extruders.end())); + + if (dlg.ShowModal() == wxID_OK) { + std::vector matrix = dlg.get_matrix(); + std::vector extruders = dlg.get_extruders(); + (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); + (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); + } + })); + return sizer; + }; + m_optgroup->append_line(line); + + + sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxBottom, 1); } @@ -837,6 +903,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); @@ -880,6 +952,7 @@ int get_export_option(wxFileDialog* dlg) } return 0; + } void about() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 6a23ed4eb8..6ee9d4f54c 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -18,6 +18,7 @@ class wxArrayLong; class wxColour; class wxBoxSizer; class wxFlexGridSizer; +class wxButton; class wxFileDialog; namespace Slic3r { @@ -81,8 +82,10 @@ void set_preset_updater(PresetUpdater *updater); AppConfig* get_app_config(); wxApp* get_app(); -wxColour* get_modified_label_clr(); -wxColour* get_sys_label_clr(); + +const wxColour& get_modified_label_clr(); +const wxColour& get_sys_label_clr(); +unsigned get_colour_approx_luma(const wxColour &colour); extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change); @@ -106,11 +109,11 @@ TabIface* get_preset_tab_iface(char *name); // add it at the end of the tab panel. void add_created_tab(Tab* panel); // Change option value in config -void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, boost::any value, int opt_index = 0); +void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); -void show_error(wxWindow* parent, wxString message); -void show_info(wxWindow* parent, wxString message, wxString title); -void warning_catcher(wxWindow* parent, wxString message); +void show_error(wxWindow* parent, const wxString& message); +void show_info(wxWindow* parent, const wxString& message, const wxString& title); +void warning_catcher(wxWindow* parent, const wxString& message); // load language saved at application config bool load_language(); @@ -143,6 +146,7 @@ wxString from_u8(const std::string &str); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); ConfigOptionsGroup* get_optgroup(); +wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 24e1ddf2eb..168ffcdc93 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -97,7 +97,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co return field; } -void OptionsGroup::append_line(const Line& line) { +void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) { //! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){ if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){ if (line.sizer != nullptr) { @@ -150,6 +150,7 @@ void OptionsGroup::append_line(const Line& line) { if (line.widget != nullptr) { auto wgt = line.widget(parent()); grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, wxOSX ? 0 : 5); + if (colored_Label != nullptr) *colored_Label = label; return; } @@ -227,12 +228,12 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } -void OptionsGroup::on_change_OG(t_config_option_key id, /*config_value*/boost::any value) { +void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) { if (m_on_change != nullptr) - m_on_change(id, value); + m_on_change(opt_id, value); } -Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index /*= -1*/) +Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/) { if (!m_config->has(opt_key)) { std::cerr << "No " << opt_key << " in ConfigOptionsGroup config."; @@ -245,7 +246,7 @@ Option ConfigOptionsGroup::get_option(const std::string opt_key, int opt_index / return Option(*m_config->def()->get(opt_key), opt_id); } -void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any value) +void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) { if (!m_opt_map.empty()) { @@ -268,16 +269,7 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val if (opt_index != -1){ // die "Can't set serialized option indexed value" ; } - // # Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values. - // # Currently used for the post_process config value only. - // my @values = split / ; / , $field_value; - // $self->config->set($opt_key, \@values); - std::string str = boost::any_cast(value); - if (str.back() == ';') - str.pop_back(); - std::vector values; - boost::split(values, str, boost::is_any_of(";")); - change_opt_value(*m_config, opt_key, values); + change_opt_value(*m_config, opt_key, value); } else { if (opt_index == -1) { @@ -297,14 +289,14 @@ void ConfigOptionsGroup::on_change_OG(t_config_option_key opt_id, boost::any val OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this } -void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key) +void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key) { if (m_get_initial_config == nullptr) return; back_to_config_value(m_get_initial_config(), opt_key); } -void ConfigOptionsGroup::back_to_sys_value(const std::string opt_key) +void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key) { if (m_get_sys_config == nullptr) return; @@ -313,7 +305,7 @@ void ConfigOptionsGroup::back_to_sys_value(const std::string opt_key) back_to_config_value(m_get_sys_config(), opt_key); } -void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key) +void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key) { boost::any value; if (opt_key == "extruders_count"){ @@ -348,7 +340,7 @@ void ConfigOptionsGroup::reload_config(){ } -boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index, bool deserialize){ +boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize){ if (deserialize) { // Want to edit a vector value(currently only multi - strings) in a single edit box. @@ -365,7 +357,7 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index, } } -boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index /*= -1*/) +boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/) { size_t idx = opt_index == -1 ? 0 : opt_index; @@ -405,6 +397,10 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = static_cast(config.opt_string(opt_key)); break; case coStrings: + if (opt_key.compare("compatible_printers") == 0){ + ret = config.option(opt_key)->values; + break; + } if (config.option(opt_key)->values.empty()) ret = text_value; else if (opt->gui_flags.compare("serialized") == 0){ @@ -457,7 +453,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config return ret; } -Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index){ +Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index){ Field* field = get_field(opt_key); if (field != nullptr) return field; @@ -471,7 +467,7 @@ Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index return opt_id.empty() ? nullptr : get_field(opt_id); } -void ogStaticText::SetText(wxString value) +void ogStaticText::SetText(const wxString& value) { SetLabel(value); Wrap(400); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index e58d9c9a99..dd6d48f46c 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -93,21 +93,21 @@ public: /// but defining it as const means a lot of const_casts to deal with wx functions. inline wxWindow* parent() const { return m_parent; } - void append_line(const Line& line); + void append_line(const Line& line, wxStaticText** colored_Label = nullptr); Line create_single_option_line(const Option& option) const; void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } // return a non-owning pointer reference - inline Field* get_field(t_config_option_key id) const{ + inline Field* get_field(const t_config_option_key& id) const{ if (m_fields.find(id) == m_fields.end()) return nullptr; return m_fields.at(id).get(); } - bool set_value(t_config_option_key id, boost::any value, bool change_event = false) { + bool set_value(const t_config_option_key& id, const boost::any& value, bool change_event = false) { if (m_fields.find(id) == m_fields.end()) return false; m_fields.at(id)->set_value(value, change_event); return true; } - boost::any get_value(t_config_option_key id) { + boost::any get_value(const t_config_option_key& id) { boost::any out; if (m_fields.find(id) == m_fields.end()) ; else @@ -118,7 +118,7 @@ public: inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } - OptionsGroup(wxWindow* _parent, wxString title, bool is_tab_opt=false) : + OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false) : m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!="") { sizer = (staticbox ? new wxStaticBoxSizer(new wxStaticBox(_parent, wxID_ANY, title), wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; @@ -152,14 +152,14 @@ protected: const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); virtual void on_kill_focus (){}; - virtual void on_change_OG(t_config_option_key opt_id, boost::any value); - virtual void back_to_initial_value(const std::string opt_key){}; - virtual void back_to_sys_value(const std::string opt_key){}; + virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); + virtual void back_to_initial_value(const std::string& opt_key){} + virtual void back_to_sys_value(const std::string& opt_key){} }; class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup(wxWindow* parent, wxString title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) : + ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) : OptionsGroup(parent, title, is_tab_opt), m_config(_config) {} /// reference to libslic3r config, non-owning pointer (?). @@ -167,8 +167,8 @@ public: bool m_full_labels {0}; t_opt_map m_opt_map; - Option get_option(const std::string opt_key, int opt_index = -1); - Line create_single_option_line(const std::string title, int idx = -1) /*const*/{ + Option get_option(const std::string& opt_key, int opt_index = -1); + Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{ Option option = get_option(title, idx); return OptionsGroup::create_single_option_line(option); } @@ -181,16 +181,16 @@ public: append_single_option_line(option); } - void on_change_OG(t_config_option_key opt_id, boost::any value) override; - void back_to_initial_value(const std::string opt_key) override; - void back_to_sys_value(const std::string opt_key) override; - void back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key); + void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override; + void back_to_initial_value(const std::string& opt_key) override; + void back_to_sys_value(const std::string& opt_key) override; + void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key); void on_kill_focus() override{ reload_config();} void reload_config(); - boost::any config_value(std::string opt_key, int opt_index, bool deserialize); + boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize); // return option value from config - boost::any get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index = -1); - Field* get_fieldc(t_config_option_key opt_key, int opt_index); + boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); + Field* get_fieldc(const t_config_option_key& opt_key, int opt_index); }; // Static text shown among the options. @@ -200,7 +200,7 @@ public: ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){} ~ogStaticText(){} - void SetText(wxString value); + void SetText(const wxString& value); }; }} diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 1e74952c49..7fa9c998d7 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -287,8 +287,7 @@ const std::vector& Preset::print_options() "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_per_color_wipe", - "compatible_printers", "compatible_printers_condition", "inherits" + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers", "compatible_printers_condition","inherits" }; return s_opts; } @@ -296,12 +295,12 @@ const std::vector& Preset::print_options() const std::vector& Preset::filament_options() { static std::vector s_opts { - "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", - "extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature", - "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", - "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", - "end_filament_gcode", - "compatible_printers", "compatible_printers_condition", "inherits" + "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", + "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay", + "filament_cooling_time", "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature", + "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", + "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", + "compatible_printers_condition", "inherits" }; return s_opts; } @@ -314,8 +313,8 @@ const std::vector& Preset::printer_options() "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", - "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "max_print_height", - "default_print_profile", "inherits", + "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", + "cooling_tube_length", "parking_pos_retraction", "max_print_height", "default_print_profile", "inherits", }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } @@ -505,6 +504,20 @@ const Preset* PresetCollection::get_selected_preset_parent() const return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; } +const Preset* PresetCollection::get_preset_parent(const Preset& child) const +{ + auto *inherits = dynamic_cast(child.config.option("inherits")); + if (inherits == nullptr || inherits->value.empty()) +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + return nullptr; + const Preset* preset = this->find_preset(inherits->value, false); + return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset; +} + +const std::string& PresetCollection::get_suffix_modified() { + return g_suffix_modified; +} + // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found) @@ -572,16 +585,44 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); + std::map nonsys_presets; + wxString selected = ""; for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible; - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - ui->SetSelection(ui->GetCount() - 1); - } +// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), +// (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); +// if (i == m_idx_selected) +// ui->SetSelection(ui->GetCount() - 1); + + if (preset.is_default || preset.is_system){ + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + ui->SetSelection(ui->GetCount() - 1); + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (preset.is_default) + ui->Append("------------------------------------", wxNullBitmap); + } + if (!nonsys_presets.empty()) + { + ui->Append("------------------------------------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; + ui->Append(it->first, + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (it->first == selected) + ui->SetSelection(ui->GetCount() - 1); + } + } ui->Thaw(); } @@ -591,16 +632,44 @@ void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatibl return; ui->Freeze(); ui->Clear(); + std::map nonsys_presets; + wxString selected = ""; for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - ui->SetSelection(ui->GetCount() - 1); +// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), +// (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); +// if (i == m_idx_selected) +// ui->SetSelection(ui->GetCount() - 1); + + if (preset.is_default || preset.is_system){ + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + ui->SetSelection(ui->GetCount() - 1); + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), preset.is_compatible); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (preset.is_default) + ui->Append("------------------------------------", wxNullBitmap); } + if (!nonsys_presets.empty()) + { + ui->Append("------------------------------------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + const wxBitmap *bmp = it->second ? m_bitmap_compatible : m_bitmap_incompatible; + ui->Append(it->first, + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (it->first == selected) + ui->SetSelection(ui->GetCount() - 1); + } + } ui->Thaw(); } diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 5f9dfa1868..480d6b1787 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -228,9 +228,17 @@ public: // The parent preset may be a system preset or a user preset, which will be // reflected by the UI. const Preset* get_selected_preset_parent() const; - // Return the selected preset including the user modifications. + // get parent preset for some child preset + const Preset* get_preset_parent(const Preset& child) const; + // Return the selected preset including the user modifications. Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } + + // used to update preset_choice from Tab + const std::deque& get_presets() { return m_presets; } + int get_idx_selected() { return m_idx_selected; } + const std::string& get_suffix_modified(); + // Return a preset possibly with modifications. const Preset& default_preset() const { return m_presets.front(); } // Return a preset by an index. If the preset is active, a temporary copy is returned. diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 27dee6383f..beec4fe075 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -34,6 +34,11 @@ namespace Slic3r { +static std::vector s_project_options { + "wiping_volumes_extruders", + "wiping_volumes_matrix" +}; + PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options()), filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), @@ -69,6 +74,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() @@ -274,8 +281,8 @@ bool PresetBundle::load_compatible_bitmaps() { const std::string path_bitmap_compatible = "flag-green-icon.png"; const std::string path_bitmap_incompatible = "flag-red-icon.png"; - const std::string path_bitmap_lock = "lock.png"; - const std::string path_bitmap_lock_open = "lock_open.png"; + const std::string path_bitmap_lock = "sys_lock.png";//"lock.png"; + const std::string path_bitmap_lock_open = "sys_unlock.png";//"lock_open.png"; bool loaded_compatible = m_bitmapCompatible ->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG); bool loaded_incompatible = m_bitmapIncompatible->LoadFile( @@ -313,6 +320,7 @@ DynamicPrintConfig PresetBundle::full_config() const out.apply(FullPrintConfig()); out.apply(this->prints.get_edited_preset().config); out.apply(this->printers.get_edited_preset().config); + out.apply(this->project_config); auto *nozzle_diameter = dynamic_cast(out.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); @@ -502,6 +510,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); } @@ -868,6 +879,34 @@ void PresetBundle::update_multi_material_filament_presets() // Append the rest of filament presets. // if (this->filament_presets.size() < num_extruders) this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); + + + // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): + std::vector old_matrix = this->project_config.option("wiping_volumes_matrix")->values; + size_t old_number_of_extruders = int(sqrt(old_matrix.size())+EPSILON); + if (num_extruders != old_number_of_extruders) { + // First verify if purging volumes presets for each extruder matches number of extruders + std::vector& extruders = this->project_config.option("wiping_volumes_extruders")->values; + while (extruders.size() < 2*num_extruders) { + extruders.push_back(extruders.size()>1 ? extruders[0] : 50.); // copy the values from the first extruder + extruders.push_back(extruders.size()>1 ? extruders[1] : 50.); + } + while (extruders.size() > 2*num_extruders) { + extruders.pop_back(); + extruders.pop_back(); + } + + std::vector new_matrix; + for (unsigned int i=0;iproject_config.option("wiping_volumes_matrix")->values = new_matrix; + } } void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) @@ -967,7 +1006,7 @@ static inline int hex_digit_to_int(const char c) (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } -static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out) +bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out) { rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; if (scolor.size() != 7 || scolor.front() != '#') @@ -1002,6 +1041,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; assert(selected_preset != nullptr); + std::map nonsys_presets; + wxString selected_str = ""; for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) { const Preset &preset = this->filaments.preset(i); bool selected = this->filament_presets[idx_extruder] == preset.name; @@ -1039,10 +1080,36 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); bitmap = m_bitmapCache->insert(bitmap_key, bmps); } - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); - if (selected) - ui->SetSelection(ui->GetCount() - 1); +// ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); +// if (selected) +// ui->SetSelection(ui->GetCount() - 1); + + if (preset.is_default || preset.is_system){ + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + (bitmap == 0) ? wxNullBitmap : *bitmap); + if (selected) + ui->SetSelection(ui->GetCount() - 1); + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + (bitmap == 0) ? wxNullBitmap : *bitmap); + if (selected) + selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + } + if (preset.is_default) + ui->Append("------------------------------------", wxNullBitmap); } + + if (!nonsys_presets.empty()) + { + ui->Append("------------------------------------", wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, it->second); + if (it->first == selected_str) + ui->SetSelection(ui->GetCount() - 1); + } + } ui->Thaw(); } diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index 4189e6c46f..651b957016 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -45,6 +45,11 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; + // The project configuration values are kept separated from the print/filament/printer preset, + // they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode, + // and they are being used by slicing core. + DynamicPrintConfig project_config; + // There will be an entry for each system profile loaded, // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. std::set vendors; @@ -115,6 +120,8 @@ public: // preset if the current print or filament preset is not compatible. void update_compatible_with_printer(bool select_other_if_incompatible); + static bool parse_color(const std::string &scolor, unsigned char *rgb_out); + private: std::string load_system_presets(); diff --git a/xs/src/slic3r/GUI/RammingChart.cpp b/xs/src/slic3r/GUI/RammingChart.cpp new file mode 100644 index 0000000000..1ef43be026 --- /dev/null +++ b/xs/src/slic3r/GUI/RammingChart.cpp @@ -0,0 +1,284 @@ +#include +#include + +#include "RammingChart.hpp" + + +//! macro used to mark string used at localization, +//! return same string +#define L(s) s + + + +wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); + + +void Chart::draw() { + wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win + + dc.SetBrush(GetBackgroundColour()); + dc.SetPen(GetBackgroundColour()); + dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows + + dc.SetPen(*wxBLACK_PEN); + dc.SetBrush(*wxWHITE_BRUSH); + dc.DrawRectangle(m_rect); + + if (visible_area.m_width < 0.499) { + dc.DrawText("NO RAMMING AT ALL",wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2)); + return; + } + + + if (!m_line_to_draw.empty()) { + for (unsigned int i=0;i2) { + m_buttons.erase(m_buttons.begin()+button_index); + recalculate_line(); + } +} + + + +void Chart::mouse_clicked(wxMouseEvent& event) { + wxPoint point = event.GetPosition(); + int button_index = which_button_is_clicked(point); + if ( button_index != -1) { + m_dragged = &m_buttons[button_index]; + m_previous_mouse = point; + } +} + + + +void Chart::mouse_moved(wxMouseEvent& event) { + if (!event.Dragging() || !m_dragged) return; + wxPoint pos = event.GetPosition(); + wxRect rect = m_rect; + rect.Deflate(side/2.); + if (!(rect.Contains(pos))) { // the mouse left chart area + mouse_left_window(event); + return; + } + int delta_x = pos.x - m_previous_mouse.x; + int delta_y = pos.y - m_previous_mouse.y; + m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); + m_previous_mouse = pos; + recalculate_line(); +} + + + +void Chart::mouse_double_clicked(wxMouseEvent& event) { + if (!manual_points_manipulation) + return; + wxPoint point = event.GetPosition(); + if (!m_rect.Contains(point)) // the click is outside the chart + return; + m_buttons.push_back(screen_to_math(point)); + std::sort(m_buttons.begin(),m_buttons.end()); + recalculate_line(); + return; +} + + + + +void Chart::recalculate_line() { + std::vector points; + for (auto& but : m_buttons) { + points.push_back(wxPoint(math_to_screen(but.get_pos()))); + if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back(); + if (points.size()>1 && points.back().x > m_rect.GetRight()) { + points.pop_back(); + break; + } + } + std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; }); + + m_line_to_draw.clear(); + m_total_volume = 0.f; + + + // Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods + const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point + // false - second ---- || ------- + const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points + std::vector diag(N+1); + std::vector mu(N+1); + std::vector lambda(N+1); + std::vector h(N+1); + std::vector rhs(N+1); + + // let's fill in inner equations + for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x; + std::fill(diag.begin(),diag.end(),2.f); + for (int i=1;i<=N-1;++i) { + mu[i] = h[i]/(h[i]+h[i+1]); + lambda[i] = 1.f - mu[i]; + rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) - + float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) ); + } + + // now fill in the first and last equations, according to boundary conditions: + if (boundary_first_derivative) { + const float endpoints_derivative = 0; + lambda[0] = 1; + mu[N] = 1; + rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative); + rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x)); + } + else { + lambda[0] = 0; + mu[N] = 0; + rhs[0] = 0; + rhs[N] = 0; + } + + // the trilinear system is ready to be solved: + for (int i=1;i<=N;++i) { + float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation + diag[i]-= multiple * lambda[i-1]; + rhs[i] -= multiple * rhs[i-1]; + } + // now the back substitution (vector mu contains invalid values from now on): + rhs[N] = rhs[N]/diag[N]; + for (int i=N-1;i>=0;--i) + rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i]; + + + + + unsigned int i=1; + float y=0.f; + for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) { + if (splines) { + if (i x) + y = points[0].y; + else + if (points[N].x < x) + y = points[N].y; + else + y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) + + (points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] + + (points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i]; + m_line_to_draw.push_back(y); + } + else { + float x_math = screen_to_math(wxPoint(x,0)).m_x; + if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math) + ++i; + m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y); + } + + + m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); + m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); + m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); + } + + wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED)); + Refresh(); +} + + + +std::vector Chart::get_ramming_speed(float sampling) const { + std::vector speeds_out; + + const int number_of_samples = std::round( visible_area.m_width / sampling); + if (number_of_samples>0) { + const int dx = (m_line_to_draw.size()-1) / number_of_samples; + for (int j=0;j> Chart::get_buttons() const { + std::vector> buttons_out; + for (const auto& button : m_buttons) + buttons_out.push_back(std::make_pair(button.get_pos().m_x,button.get_pos().m_y)); + return buttons_out; +} + + + + +BEGIN_EVENT_TABLE(Chart, wxWindow) +EVT_MOTION(Chart::mouse_moved) +EVT_LEFT_DOWN(Chart::mouse_clicked) +EVT_LEFT_UP(Chart::mouse_released) +EVT_LEFT_DCLICK(Chart::mouse_double_clicked) +EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked) +EVT_LEAVE_WINDOW(Chart::mouse_left_window) +EVT_PAINT(Chart::paint_event) +END_EVENT_TABLE() diff --git a/xs/src/slic3r/GUI/RammingChart.hpp b/xs/src/slic3r/GUI/RammingChart.hpp new file mode 100644 index 0000000000..7d3b9a9627 --- /dev/null +++ b/xs/src/slic3r/GUI/RammingChart.hpp @@ -0,0 +1,115 @@ +#ifndef RAMMING_CHART_H_ +#define RAMMING_CHART_H_ + +#include +#include +#ifndef WX_PRECOMP + #include +#endif + +wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); + + +class Chart : public wxWindow { + +public: + Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons,int ramming_speed_size, float sampling) : + wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()) + { + SetBackgroundStyle(wxBG_STYLE_PAINT); + m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50)); + visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.); + m_buttons.clear(); + if (initial_buttons.size()>0) + for (const auto& pair : initial_buttons) + m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second)); + recalculate_line(); + } + void set_xy_range(float x,float y) { + x = int(x/0.5) * 0.5; + if (x>=0) visible_area.SetRight(x); + if (y>=0) visible_area.SetBottom(y); + recalculate_line(); + } + float get_volume() const { return m_total_volume; } + float get_time() const { return visible_area.m_width; } + + std::vector get_ramming_speed(float sampling) const; //returns sampled ramming speed + std::vector> get_buttons() const; // returns buttons position + + void draw(); + + void mouse_clicked(wxMouseEvent& event); + void mouse_right_button_clicked(wxMouseEvent& event); + void mouse_moved(wxMouseEvent& event); + void mouse_double_clicked(wxMouseEvent& event); + void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; } + void mouse_released(wxMouseEvent&) { m_dragged = nullptr; } + void paint_event(wxPaintEvent&) { draw(); } + DECLARE_EVENT_TABLE() + + + +private: + static const bool fixed_x = true; + static const bool splines = true; + static const bool manual_points_manipulation = false; + static const int side = 10; // side of draggable button + + class ButtonToDrag { + public: + bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; } + ButtonToDrag(wxPoint2DDouble pos) : m_pos{pos} {}; + wxPoint2DDouble get_pos() const { return m_pos; } + void move(double x,double y) { m_pos.m_x+=x; m_pos.m_y+=y; } + private: + wxPoint2DDouble m_pos; // position in math coordinates + }; + + + + wxPoint math_to_screen(const wxPoint2DDouble& math) const { + wxPoint screen; + screen.x = (math.m_x-visible_area.m_x) * (m_rect.GetWidth() / visible_area.m_width ); + screen.y = (math.m_y-visible_area.m_y) * (m_rect.GetHeight() / visible_area.m_height ); + screen.y *= -1; + screen += m_rect.GetLeftBottom(); + return screen; + } + wxPoint2DDouble screen_to_math(const wxPoint& screen) const { + wxPoint2DDouble math = screen; + math -= m_rect.GetLeftBottom(); + math.m_y *= -1; + math.m_x *= visible_area.m_width / m_rect.GetWidth(); // scales to [0;1]x[0,1] + math.m_y *= visible_area.m_height / m_rect.GetHeight(); + return (math+visible_area.GetLeftTop()); + } + + int which_button_is_clicked(const wxPoint& point) const { + if (!m_rect.Contains(point)) + return -1; + for (unsigned int i=0;i m_buttons; + std::vector m_line_to_draw; + wxRect2DDouble visible_area; + ButtonToDrag* m_dragged = nullptr; + float m_total_volume = 0.f; + +}; + + +#endif // RAMMING_CHART_H_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index ed2b4b9514..38eb5e404d 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -3,9 +3,11 @@ #include "PresetBundle.hpp" #include "PresetHints.hpp" #include "../../libslic3r/Utils.hpp" + #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/OctoPrint.hpp" #include "BonjourDialog.hpp" +#include "WipeTowerDialog.hpp" #include #include @@ -20,6 +22,7 @@ #include #include +#include "wxExtensions.hpp" namespace Slic3r { namespace GUI { @@ -37,7 +40,42 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) // preset chooser m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(270, -1), 0, 0,wxCB_READONLY); + /* + m_cc_presets_choice = new wxComboCtrl(panel, wxID_ANY, L(""), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); + wxDataViewTreeCtrlComboPopup* popup = new wxDataViewTreeCtrlComboPopup; + if (popup != nullptr) + { + // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. + // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. +// m_cc_presets_choice->UseAltPopupWindow(); +// m_cc_presets_choice->EnablePopupAnimation(false); + m_cc_presets_choice->SetPopupControl(popup); + popup->SetStringValue(from_u8("Text1")); + + popup->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this, popup](wxCommandEvent& evt) + { + auto selected = popup->GetItemText(popup->GetSelection()); + if (selected != _(L("System presets")) && selected != _(L("Default presets"))) + { + m_cc_presets_choice->SetText(selected); + std::string selected_string = selected.ToUTF8().data(); +#ifdef __APPLE__ +#else + select_preset(selected_string); +#endif + } + }); + +// popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); +// popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); + + auto icons = new wxImageList(16, 16, true, 1); + popup->SetImageList(icons); + icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); + icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); + } +*/ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); //buttons @@ -82,11 +120,44 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->AddSpacer(64); m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); +// m_hsizer->AddSpacer(64); +// m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 1, wxEXPAND, 0); + +/* + + + //temporary left vertical sizer + m_left_sizer = new wxBoxSizer(wxVERTICAL); + m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); + + // tree + m_presetctrl = new wxDataViewTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(200, -1), wxDV_NO_HEADER); + m_left_sizer->Add(m_presetctrl, 1, wxEXPAND); + m_preset_icons = new wxImageList(16, 16, true, 1); + m_presetctrl->SetImageList(m_preset_icons); + m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); + m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); + + m_presetctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxCommandEvent& evt) + { + auto selected = m_presetctrl->GetItemText(m_presetctrl->GetSelection()); + if (selected != _(L("System presets")) && selected != _(L("Default presets"))) + { + std::string selected_string = selected.ToUTF8().data(); +#ifdef __APPLE__ +#else + select_preset(selected_string); +#endif + } + }); + +*/ + //left vertical sizer m_left_sizer = new wxBoxSizer(wxVERTICAL); m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); @@ -134,11 +205,10 @@ void Tab::load_initial_data() { m_config = &m_presets->get_edited_preset().config; m_nonsys_btn_icon = m_presets->get_selected_preset_parent() == nullptr ? - "bullet_white.png" : - wxMSW ? "sys_unlock.png" : "lock_open.png"; + "bullet_white.png" : "sys_unlock.png"; } -PageShp Tab::add_options_page(wxString title, std::string icon, bool is_extruder_pages/* = false*/) +PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) { // Index of icon in an icon list $self->{icons}. auto icon_idx = 0; @@ -258,27 +328,36 @@ void Tab::update_changed_ui() m_dirty_options = dirty_options; } + Freeze(); //update options "decoration" for (const auto opt_key : m_full_options_list) { bool is_nonsys_value = false; bool is_modified_value = true; - std::string sys_icon = wxMSW ? "sys_lock.png" : "lock.png"; - std::string icon = wxMSW ? "action_undo.png" : "arrow_undo.png"; - wxColour& color = *get_sys_label_clr(); + std::string sys_icon = "sys_lock.png"; + std::string icon = "action_undo.png"; + wxColour color = get_sys_label_clr(); if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { is_nonsys_value = true; sys_icon = m_nonsys_btn_icon; if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) - color = wxSYS_COLOUR_WINDOWTEXT; + color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); else - color = *get_modified_label_clr(); + color = get_modified_label_clr(); } if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { is_modified_value = false; icon = "bullet_white.png"; } + if (opt_key == "bed_shape" || opt_key == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(color); + m_colored_Label->Refresh(true); + } + continue; + } + Field* field = get_field(opt_key); if (field == nullptr) continue; field->m_is_nonsys_value = is_nonsys_value; @@ -290,6 +369,7 @@ void Tab::update_changed_ui() field->m_Label->Refresh(true); } } + Thaw(); wxTheApp->CallAfter([this]() { update_changed_tree_ui(); @@ -343,7 +423,7 @@ void Tab::update_sys_ui_after_sel_preset() field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(m_nonsys_btn_icon)), wxBITMAP_TYPE_PNG)); field->m_is_nonsys_value = true; if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(wxSYS_COLOUR_WINDOWTEXT); + field->m_Label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); field->m_Label->Refresh(true); } } @@ -351,13 +431,20 @@ void Tab::update_sys_ui_after_sel_preset() m_sys_options.resize(0); } +void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) +{ + if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + sys_page = false; + if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + modified_page = true; +} + void Tab::update_changed_tree_ui() { auto cur_item = m_treectrl->GetFirstVisibleItem(); auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); while (cur_item){ auto title = m_treectrl->GetItemText(cur_item); - int i=0; for (auto page : m_pages) { if (page->title() != title) @@ -367,30 +454,27 @@ void Tab::update_changed_tree_ui() if (title == _("General")){ std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; for (auto &opt_key : optional_keys) { - if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - sys_page = false; - if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - modified_page = true; + get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } + if (title == _("Dependencies")){ + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + } for (auto group : page->m_optgroups) { - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; - if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) - sys_page = false; - if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) - modified_page = true; - } if (!sys_page && modified_page) break; + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } } if (sys_page) - m_treectrl->SetItemTextColour(cur_item, *get_sys_label_clr()); + m_treectrl->SetItemTextColour(cur_item, get_sys_label_clr()); else if (modified_page) - m_treectrl->SetItemTextColour(cur_item, *get_modified_label_clr()); + m_treectrl->SetItemTextColour(cur_item, get_modified_label_clr()); else - m_treectrl->SetItemTextColour(cur_item, wxSYS_COLOUR_WINDOWTEXT); + m_treectrl->SetItemTextColour(cur_item, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); page->m_is_nonsys_values = !sys_page; page->m_is_modified_values = modified_page; @@ -409,10 +493,8 @@ void Tab::update_changed_tree_ui() void Tab::update_undo_buttons() { - const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : - wxMSW ? "action_undo.png" : "arrow_undo.png"; - const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : - wxMSW ? "sys_lock.png" : "lock.png"; + const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : "action_undo.png"; + const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : "sys_lock.png"; m_undo_btn->SetBitmap(wxBitmap(from_u8(var(undo_icon)), wxBITMAP_TYPE_PNG)); m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(undo_to_sys_icon)), wxBITMAP_TYPE_PNG)); @@ -434,6 +516,14 @@ void Tab::on_back_to_initial_value() if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()) group->back_to_initial_value("bed_shape"); } + if (group->title == _("Profile dependencies")){ + if (find(m_dirty_options.begin(), m_dirty_options.end(), "compatible_printers") != m_dirty_options.end()) + group->back_to_initial_value("compatible_printers"); + + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers_checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) @@ -461,6 +551,14 @@ void Tab::on_back_to_sys_value() if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()) group->back_to_sys_value("bed_shape"); } + if (group->title == _("Profile dependencies")){ + if (find(m_sys_options.begin(), m_sys_options.end(), "compatible_printers") == m_sys_options.end()) + group->back_to_sys_value("compatible_printers"); + + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers_checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + } for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { const std::string& opt_key = it->first; if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) @@ -478,16 +576,19 @@ void Tab::update_dirty(){ m_presets->update_dirty_ui(m_presets_choice); on_presets_changed(); update_changed_ui(); +// update_dirty_presets(m_cc_presets_choice); } void Tab::update_tab_ui() { m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); +// update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); +// update_presetsctrl(m_presetctrl, m_show_incompatible_presets); } // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. -void Tab::load_config(DynamicPrintConfig config) +void Tab::load_config(const DynamicPrintConfig& config) { bool modified = 0; for(auto opt_key : m_config->diff(config)) { @@ -510,7 +611,7 @@ void Tab::reload_config(){ Thaw(); } -Field* Tab::get_field(t_config_option_key opt_key, int opt_index/* = -1*/) const +Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { Field* field = nullptr; for (auto page : m_pages){ @@ -524,7 +625,7 @@ Field* Tab::get_field(t_config_option_key opt_key, int opt_index/* = -1*/) const // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. -bool Tab::set_value(t_config_option_key opt_key, boost::any value){ +bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value){ bool changed = false; for(auto page: m_pages) { if (page->set_value(opt_key, value)) @@ -535,7 +636,7 @@ bool Tab::set_value(t_config_option_key opt_key, boost::any value){ // To be called by custom widgets, load a value into a config, // update the preset selection boxes (the dirty flags) -void Tab::load_key_value(std::string opt_key, boost::any value) +void Tab::load_key_value(const std::string& opt_key, const boost::any& value) { change_opt_value(*m_config, opt_key, value); // Mark the print & filament enabled if they are compatible with the currently selected preset. @@ -550,7 +651,7 @@ void Tab::load_key_value(std::string opt_key, boost::any value) extern wxFrame *g_wxMainFrame; -void Tab::on_value_change(std::string opt_key, boost::any value) +void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { if (m_event_value_change > 0) { wxCommandEvent event(m_event_value_change); @@ -565,8 +666,8 @@ void Tab::on_value_change(std::string opt_key, boost::any value) } if (opt_key == "fill_density") { - value = get_optgroup()->get_config_value(*m_config, opt_key); - get_optgroup()->set_value(opt_key, value); + boost::any val = get_optgroup()->get_config_value(*m_config, opt_key); + get_optgroup()->set_value(opt_key, val); } if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") { @@ -583,10 +684,27 @@ void Tab::on_value_change(std::string opt_key, boost::any value) get_optgroup()->set_value("brim", val); } - + if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) + update_wiping_button_visibility(); + update(); } + +// Show/hide the 'purging volumes' button +void Tab::update_wiping_button_visibility() { + bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; + bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; + bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; + + if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) + get_wiping_dialog_button()->Show(); + else get_wiping_dialog_button()->Hide(); + + (get_wiping_dialog_button()->GetParent())->Layout(); +} + + // Call a callback to update the selection of presets on the platter: // To update the content of the selection boxes, // to update the filament colors of the selection boxes, @@ -622,6 +740,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() @@ -771,7 +891,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"); @@ -837,7 +958,7 @@ void TabPrint::build() line.widget = [this](wxWindow* parent){ return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); }; - optgroup->append_line(line); + optgroup->append_line(line, &m_colored_Label); option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; @@ -1016,53 +1137,40 @@ void TabPrint::update() } bool have_perimeters = m_config->opt_int("perimeters") > 0; - std::vector vec_enable = { "extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }; - for (auto el : vec_enable) + for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", + "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) get_field(el)->toggle(have_perimeters); bool have_infill = m_config->option("fill_density")->value > 0; - vec_enable.resize(0); - vec_enable = { "fill_pattern", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }; // infill_extruder uses the same logic as in Print::extruders() - for (auto el : vec_enable) + for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", + "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) get_field(el)->toggle(have_infill); bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; - vec_enable.resize(0); - vec_enable = { "external_fill_pattern", "infill_first", "solid_infill_extruder", - "solid_infill_extrusion_width", "solid_infill_speed" }; // solid_infill_extruder uses the same logic as in Print::extruders() - for (auto el : vec_enable) + for (auto el : {"external_fill_pattern", "infill_first", "solid_infill_extruder", + "solid_infill_extrusion_width", "solid_infill_speed" }) get_field(el)->toggle(have_solid_infill); - vec_enable.resize(0); - vec_enable = { "fill_angle", "bridge_angle", "infill_extrusion_width", - "infill_speed", "bridge_speed" }; - for (auto el : vec_enable) + for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", + "infill_speed", "bridge_speed" }) get_field(el)->toggle(have_infill || have_solid_infill); get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; - vec_enable.resize(0); - vec_enable = { "top_infill_extrusion_width", "top_solid_infill_speed" }; - for (auto el : vec_enable) + for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) get_field(el)->toggle(have_top_solid_infill); bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; - vec_enable.resize(0); - vec_enable = { "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration" }; - for (auto el : vec_enable) + for (auto el : {"perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration" }) get_field(el)->toggle(have_default_acceleration); bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; - vec_enable.resize(0); - vec_enable = { "skirt_distance", "skirt_height" }; - for (auto el : vec_enable) + for (auto el : { "skirt_distance", "skirt_height" }) get_field(el)->toggle(have_skirt); bool have_brim = m_config->opt_float("brim_width") > 0; @@ -1073,18 +1181,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); @@ -1093,18 +1197,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( @@ -1184,7 +1284,29 @@ void TabFilament::build() }; optgroup->append_line(line); - page = add_options_page(_(L("Custom G-code")), "cog.png"); + optgroup = page->new_optgroup(_(L("Toolchange behaviour"))); + optgroup->append_single_option_line("filament_loading_speed"); + optgroup->append_single_option_line("filament_unloading_speed"); + optgroup->append_single_option_line("filament_toolchange_delay"); + optgroup->append_single_option_line("filament_cooling_time"); + line = { _(L("Ramming")), "" }; + line.widget = [this](wxWindow* parent){ + auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(ramming_dialog_btn); + + ramming_dialog_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); + if (dlg.ShowModal() == wxID_OK) + (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); + })); + return sizer; + }; + optgroup->append_line(line); + + + page = add_options_page(_(L("Custom G-code")), "cog.png"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); Option option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; @@ -1211,7 +1333,7 @@ void TabFilament::build() line.widget = [this](wxWindow* parent){ return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); }; - optgroup->append_line(line); + optgroup->append_line(line, &m_colored_Label); option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; @@ -1241,13 +1363,10 @@ void TabFilament::update() bool cooling = m_config->opt_bool("cooling", 0); bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); - std::vector vec_enable = { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }; - for (auto el : vec_enable) + for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) get_field(el)->toggle(cooling); - vec_enable.resize(0); - vec_enable = { "min_fan_speed", "disable_fan_first_layers" }; - for (auto el : vec_enable) + for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); } @@ -1308,7 +1427,7 @@ void TabPrinter::build() return sizer; }; - optgroup->append_line(line); + optgroup->append_line(line, &m_colored_Label); optgroup->append_single_option_line("max_print_height"); optgroup->append_single_option_line("z_offset"); @@ -1326,9 +1445,11 @@ void TabPrinter::build() optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){ size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); wxTheApp->CallAfter([this, opt_key, value, extruders_count](){ - if (opt_key.compare("extruders_count")==0) { + if (opt_key.compare("extruders_count")==0 || opt_key.compare("single_extruder_multi_material")==0) { extruders_count_changed(extruders_count); update_dirty(); + if (opt_key.compare("single_extruder_multi_material")==0) // the single_extruder_multimaterial was added to force pages + on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped } else { update_dirty(); @@ -1337,6 +1458,7 @@ void TabPrinter::build() }); }; + if (!m_no_controller) { optgroup = page->new_optgroup(_(L("USB/Serial connection"))); @@ -1607,6 +1729,25 @@ void TabPrinter::build_extruder_pages(){ for (auto page_extruder : m_extruder_pages) m_pages.push_back(page_extruder); m_pages.push_back(page_note); + + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i=0;ititle().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin()+i); + break; + } + if ( m_extruder_pages.size()>1 && m_config->opt_bool("single_extruder_multi_material")) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer_empty.png",true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); + m_pages.insert(m_pages.begin()+1,page); + } + } + rebuild_page_tree(); } @@ -1714,9 +1855,7 @@ void Tab::load_current_preset() // Reload preset pages with the new configuration values. reload_config(); const Preset* parent = m_presets->get_selected_preset_parent(); - m_nonsys_btn_icon = parent == nullptr ? - "bullet_white.png" : - wxMSW ? "sys_unlock.png" : "lock_open.png"; + m_nonsys_btn_icon = parent == nullptr ? "bullet_white.png" : "sys_unlock.png"; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the @@ -1773,7 +1912,7 @@ void Tab::rebuild_page_tree() // Called by the UI combo box when the user switches profiles. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(const std::string &preset_name /*= ""*/) +void Tab::select_preset(const std::string& preset_name /*= ""*/) { std::string name = preset_name; auto force = false; @@ -1840,7 +1979,7 @@ void Tab::select_preset(const std::string &preset_name /*= ""*/) // If the current preset is dirty, the user is asked whether the changes may be discarded. // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. -bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, std::string new_printer_name /*= ""*/) +bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { if (presets == nullptr) presets = m_presets; // Display a dialog showing the dirty options in a human readable form. @@ -2048,6 +2187,7 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox if ((*checkbox)->GetValue()) load_key_value("compatible_printers", std::vector {}); get_field("compatible_printers_condition")->toggle((*checkbox)->GetValue()); + update_changed_ui(); }) ); (*btn)->Bind(wxEVT_BUTTON, ([this, parent, checkbox, btn](wxCommandEvent e) @@ -2090,18 +2230,186 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox } // All printers have been made compatible with this preset. load_key_value("compatible_printers", value); + update_changed_ui(); } })); return sizer; } +void Tab::update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible) +{ + if (ui == nullptr) + return; + ui->Freeze(); + ui->DeleteAllItems(); + auto presets = m_presets->get_presets(); + auto idx_selected = m_presets->get_idx_selected(); + auto suffix_modified = m_presets->get_suffix_modified(); + int icon_compatible = 0; + int icon_incompatible = 1; + int cnt_items = 0; + + auto root_sys = ui->AppendContainer(wxDataViewItem(0), _(L("System presets"))); + auto root_def = ui->AppendContainer(wxDataViewItem(0), _(L("Default presets"))); + + auto show_def = get_app_config()->get("no_defaults")[0] != '1'; + + for (size_t i = presets.front().is_visible ? 0 : 1; i < presets.size(); ++i) { + const Preset &preset = presets[i]; + if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) + continue; + + auto preset_name = wxString::FromUTF8((preset.name + (preset.is_dirty ? suffix_modified : "")).c_str()); + + wxDataViewItem item; + if (preset.is_system) + item = ui->AppendItem(root_sys, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else if (show_def && preset.is_default) + item = ui->AppendItem(root_def, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else + { + auto parent = m_presets->get_preset_parent(preset); + if (parent == nullptr) + item = ui->AppendItem(root_def, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else + { + auto parent_name = parent->name; + + wxDataViewTreeStoreContainerNode *node = ui->GetStore()->FindContainerNode(root_sys); + if (node) + { + wxDataViewTreeStoreNodeList::iterator iter; + for (iter = node->GetChildren().begin(); iter != node->GetChildren().end(); iter++) + { + wxDataViewTreeStoreNode* child = *iter; + auto child_item = child->GetItem(); + auto item_text = ui->GetItemText(child_item); + if (item_text == parent_name) + { + auto added_child = ui->AppendItem(child->GetItem(), preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + if (!added_child){ + ui->DeleteItem(child->GetItem()); + auto new_parent = ui->AppendContainer(root_sys, parent_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + ui->AppendItem(new_parent, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + } + break; + } + } + } + } + } + + cnt_items++; + if (i == idx_selected){ + ui->Select(item); + m_cc_presets_choice->SetText(preset_name); + } + } + if (ui->GetStore()->GetChildCount(root_def) == 0) + ui->DeleteItem(root_def); + + ui->Thaw(); +} + +void Tab::update_tab_presets(wxComboCtrl* ui, bool show_incompatible) +{ + if (ui == nullptr) + return; + ui->Freeze(); + ui->Clear(); + auto presets = m_presets->get_presets(); + auto idx_selected = m_presets->get_idx_selected(); + auto suffix_modified = m_presets->get_suffix_modified(); + int icon_compatible = 0; + int icon_incompatible = 1; + int cnt_items = 0; + + wxDataViewTreeCtrlComboPopup* popup = wxDynamicCast(m_cc_presets_choice->GetPopupControl(), wxDataViewTreeCtrlComboPopup); + if (popup != nullptr) + { + popup->DeleteAllItems(); + + auto root_sys = popup->AppendContainer(wxDataViewItem(0), _(L("System presets"))); + auto root_def = popup->AppendContainer(wxDataViewItem(0), _(L("Default presets"))); + + auto show_def = get_app_config()->get("no_defaults")[0] != '1'; + + for (size_t i = presets.front().is_visible ? 0 : 1; i < presets.size(); ++i) { + const Preset &preset = presets[i]; + if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) + continue; + + auto preset_name = wxString::FromUTF8((preset.name + (preset.is_dirty ? suffix_modified : "")).c_str()); + + wxDataViewItem item; + if (preset.is_system) + item = popup->AppendItem(root_sys, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else if (show_def && preset.is_default) + item = popup->AppendItem(root_def, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else + { + auto parent = m_presets->get_preset_parent(preset); + if (parent == nullptr) + item = popup->AppendItem(root_def, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + else + { + auto parent_name = parent->name; + + wxDataViewTreeStoreContainerNode *node = popup->GetStore()->FindContainerNode(root_sys); + if (node) + { + wxDataViewTreeStoreNodeList::iterator iter; + for (iter = node->GetChildren().begin(); iter != node->GetChildren().end(); iter++) + { + wxDataViewTreeStoreNode* child = *iter; + auto child_item = child->GetItem(); + auto item_text = popup->GetItemText(child_item); + if (item_text == parent_name) + { + auto added_child = popup->AppendItem(child->GetItem(), preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + if (!added_child){ + popup->DeleteItem(child->GetItem()); + auto new_parent = popup->AppendContainer(root_sys, parent_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + popup->AppendItem(new_parent, preset_name, + preset.is_compatible ? icon_compatible : icon_incompatible); + } + break; + } + } + } + } + } + + cnt_items++; + if (i == idx_selected){ + popup->Select(item); + m_cc_presets_choice->SetText(preset_name); + } + } + if (popup->GetStore()->GetChildCount(root_def) == 0) + popup->DeleteItem(root_def); + } + ui->Thaw(); +} + void Page::reload_config() { for (auto group : m_optgroups) group->reload_config(); } -Field* Page::get_field(t_config_option_key opt_key, int opt_index/* = -1*/) const +Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1*/) const { Field* field = nullptr; for (auto opt : m_optgroups){ @@ -2112,7 +2420,7 @@ Field* Page::get_field(t_config_option_key opt_key, int opt_index/* = -1*/) cons return field; } -bool Page::set_value(t_config_option_key opt_key, boost::any value){ +bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value){ bool changed = false; for(auto optgroup: m_optgroups) { if (optgroup->set_value(opt_key, value)) @@ -2122,7 +2430,7 @@ bool Page::set_value(t_config_option_key opt_key, boost::any value){ } // package Slic3r::GUI::Tab::Page; -ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_width /*= -1*/) +ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_label_width /*= -1*/) { //! config_ have to be "right" ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true); @@ -2163,7 +2471,7 @@ ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_wid return optgroup; } -void SavePresetWindow::build(wxString title, std::string default_name, std::vector &values) +void SavePresetWindow::build(const wxString& title, const std::string& default_name, std::vector &values) { auto text = new wxStaticText(this, wxID_ANY, _(L("Save ")) + title + _(L(" as:")), wxDefaultPosition, wxDefaultSize); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index bd9672bb2d..f9ee4f3639 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -67,9 +68,9 @@ public: size_t iconID() const { return m_iconID; } void set_config(DynamicPrintConfig* config_in) { m_config = config_in; } void reload_config(); - Field* get_field(t_config_option_key opt_key, int opt_index = -1) const; - bool set_value(t_config_option_key opt_key, boost::any value); - ConfigOptionsGroupShp new_optgroup(wxString title, int noncommon_label_width = -1); + Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; + bool set_value(const t_config_option_key& opt_key, const boost::any& value); + ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1); }; // Slic3r::GUI::Tab; @@ -95,6 +96,9 @@ protected: wxButton* m_compatible_printers_btn; wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; + wxComboCtrl* m_cc_presets_choice; + wxDataViewTreeCtrl* m_presetctrl; + wxImageList* m_preset_icons; int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index @@ -122,10 +126,11 @@ public: DynamicPrintConfig* m_config; std::string m_nonsys_btn_icon; ogStaticText* m_parent_preset_description_line; + wxStaticText* m_colored_Label = nullptr; public: Tab() {} - Tab(wxNotebook* parent, wxString title, const char* name, bool no_controller) : + Tab(wxNotebook* parent, const wxString& title, const char* name, bool no_controller) : m_parent(parent), m_title(title), m_name(name), m_no_controller(no_controller) { Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); get_tabs_list().push_back(this); @@ -143,11 +148,12 @@ public: void create_preset_tab(PresetBundle *preset_bundle); void load_current_preset(); void rebuild_page_tree(); - void select_preset(const std::string &preset_name = ""); - bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, std::string new_printer_name = ""); + void select_preset(const std::string& preset_name = ""); + bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); - void load_key_value(std::string opt_key, boost::any value); + void update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible); + void load_key_value(const std::string& opt_key, const boost::any& value); void reload_compatible_printers_widget(); void OnTreeSelChange(wxTreeEvent& event); @@ -161,13 +167,14 @@ public: void update_changed_ui(); void update_full_options_list(); void update_sys_ui_after_sel_preset(); + void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); void update_undo_buttons(); void on_back_to_initial_value(); void on_back_to_sys_value(); - PageShp add_options_page(wxString title, std::string icon, bool is_extruder_pages = false); + PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); virtual void OnActivate(){} virtual void on_preset_loaded(){} @@ -176,10 +183,10 @@ public: void load_initial_data(); void update_dirty(); void update_tab_ui(); - void load_config(DynamicPrintConfig config); + void load_config(const DynamicPrintConfig& config); virtual void reload_config(); - Field* get_field(t_config_option_key opt_key, int opt_index = -1) const; - bool set_value(t_config_option_key opt_key, boost::any value); + Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; + bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); bool current_preset_is_dirty(); DynamicPrintConfig* get_config() { return m_config; } @@ -189,11 +196,13 @@ public: } std::vector get_dependent_tabs() { return m_reload_dependent_tabs; } - void on_value_change(std::string opt_key, boost::any value); + void on_value_change(const std::string& opt_key, const boost::any& value); protected: void on_presets_changed(); void update_frequently_changed_parameters(); + void update_wiping_button_visibility(); + void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); }; //Slic3r::GUI::Tab::Print; @@ -264,7 +273,7 @@ public: std::string m_chosen_name; wxComboBox* m_combo; - void build(wxString title, std::string default_name, std::vector &values); + void build(const wxString& title, const std::string& default_name, std::vector &values); void accept(); std::string get_name() { return m_chosen_name; } }; diff --git a/xs/src/slic3r/GUI/WipeTowerDialog.cpp b/xs/src/slic3r/GUI/WipeTowerDialog.cpp new file mode 100644 index 0000000000..b34de07458 --- /dev/null +++ b/xs/src/slic3r/GUI/WipeTowerDialog.cpp @@ -0,0 +1,343 @@ +#include +#include +#include "WipeTowerDialog.hpp" + +#include + +//! macro used to mark string used at localization, +//! return same string +#define L(s) s + + + +RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters) +: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + m_panel_ramming = new RammingPanel(this,parameters); + + // Not found another way of getting the background colours of RammingDialog, RammingPanel and Chart correct than setting + // them all explicitely. Reading the parent colour yielded colour that didn't really match it, no wxSYS_COLOUR_... matched + // colour used for the dialog. Same issue (and "solution") here : https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608 + // Whoever can fix this, feel free to do so. + this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_ramming->Show(true); + this->Show(); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_panel_ramming, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); }); + + this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { + m_output_data = m_panel_ramming->get_parameters(); + EndModal(wxID_OK); + },wxID_OK); + this->Show(); + wxMessageDialog(this,_(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to " + "properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself " + "be reinserted later. This phase is important and different materials can require different extrusion speeds to get " + "the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level " + "setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc.")),_(L("Warning")),wxOK|wxICON_EXCLAMATION).ShowModal(); +} + + + + + +RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) +: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/) +{ + auto sizer_chart = new wxBoxSizer(wxVERTICAL); + auto sizer_param = new wxBoxSizer(wxVERTICAL); + + std::stringstream stream{ parameters }; + stream >> m_ramming_line_width_multiplicator >> m_ramming_step_multiplicator; + int ramming_speed_size = 0; + float dummy = 0.f; + while (stream >> dummy) + ++ramming_speed_size; + stream.clear(); + stream.get(); + + std::vector> buttons; + float x = 0.f; + float y = 0.f; + while (stream >> x >> y) + buttons.push_back(std::make_pair(x, y)); + + m_chart = new Chart(this, wxRect(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f); + m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor + sizer_chart->Add(m_chart, 0, wxALL, 5); + + m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5); + m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0); + m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); + m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100); + + auto gsizer_param = new wxFlexGridSizer(2, 5, 15); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time (s):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_time); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume (mm"))+"\u00B3):")), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_volume); + gsizer_param->AddSpacer(20); + gsizer_param->AddSpacer(20); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width (%):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_ramming_line_width_multiplicator); + gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing (%):")))), 0, wxALIGN_CENTER_VERTICAL); + gsizer_param->Add(m_widget_ramming_step_multiplicator); + + sizer_param->Add(gsizer_param, 0, wxTOP, 100); + + m_widget_time->SetValue(m_chart->get_time()); + m_widget_time->SetDigits(2); + m_widget_volume->SetValue(m_chart->get_volume()); + m_widget_volume->Disable(); + m_widget_ramming_line_width_multiplicator->SetValue(m_ramming_line_width_multiplicator); + m_widget_ramming_step_multiplicator->SetValue(m_ramming_step_multiplicator); + + m_widget_ramming_step_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); }); + m_widget_ramming_line_width_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); }); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(sizer_chart, 0, wxALL, 5); + sizer->Add(sizer_param, 0, wxALL, 10); + + sizer->SetSizeHints(this); + SetSizer(sizer); + + m_widget_time->Bind(wxEVT_TEXT,[this](wxCommandEvent&) {m_chart->set_xy_range(m_widget_time->GetValue(),-1);}); + m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value + m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value + Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} ); + Refresh(this); +} + +void RammingPanel::line_parameters_changed() { + m_ramming_line_width_multiplicator = m_widget_ramming_line_width_multiplicator->GetValue(); + m_ramming_step_multiplicator = m_widget_ramming_step_multiplicator->GetValue(); +} + +std::string RammingPanel::get_parameters() +{ + std::vector speeds = m_chart->get_ramming_speed(0.25f); + std::vector> buttons = m_chart->get_buttons(); + std::stringstream stream; + stream << m_ramming_line_width_multiplicator << " " << m_ramming_step_multiplicator; + for (const float& speed_value : speeds) + stream << " " << speed_value; + stream << "|"; + for (const auto& button : buttons) + stream << " " << button.first << " " << button.second; + return stream.str(); +} + + +#define ITEM_WIDTH 60 +// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode: +WipingDialog::WipingDialog(wxWindow* parent,const std::vector& matrix, const std::vector& extruders) +: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize); + m_panel_wiping = new WipingPanel(this,matrix,extruders, widget_button); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + + // set min sizer width according to extruders count + const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH); + main_sizer->SetMinSize(wxSize(sizer_width, -1)); + + main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); }); + + this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked.. + m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values + m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...() + EndModal(wxID_OK); + },wxID_OK); + + this->Show(); +} + +// This function allows to "play" with sizers parameters (like align or border) +void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/) +{ + sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15); + auto table_sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); + table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); + table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10); +} + +// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers) +WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, wxButton* widget_button) +: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/) +{ + m_widget_button = widget_button; // pointer to the button in parent dialog + m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); }); + + m_number_of_extruders = (int)(sqrt(matrix.size())+0.001); + + // Create two switched panels with their own sizers + m_sizer_simple = new wxBoxSizer(wxVERTICAL); + m_sizer_advanced = new wxBoxSizer(wxVERTICAL); + m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_simple->SetSizer(m_sizer_simple); + m_page_advanced->SetSizer(m_sizer_advanced); + + auto gridsizer_simple = new wxGridSizer(3, 5, 10); + m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1); + + // First create controls for advanced mode and assign them to m_page_advanced: + for (unsigned int i = 0; i < m_number_of_extruders; ++i) { + edit_boxes.push_back(std::vector(0)); + + for (unsigned int j = 0; j < m_number_of_extruders; ++j) { + edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1))); + if (i == j) + edit_boxes[i][j]->Disable(); + else + edit_boxes[i][j]->SetValue(wxString("") << int(matrix[m_number_of_extruders*j + i])); + } + } + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString(""))); + for (unsigned int i = 0; i < m_number_of_extruders; ++i) + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + for (unsigned int i = 0; i < m_number_of_extruders; ++i) { + m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + for (unsigned int j = 0; j < m_number_of_extruders; ++j) + m_gridsizer_advanced->Add(edit_boxes[j][i], 0); + } + + // collect and format sizer + format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, + _(L("Here you can adjust required purging volume (mm\u00B3) for any given pair of tools.")), + _(L("Extruder changed to"))); + + // Hide preview page before new page creating + // It allows to do that from a beginning of the main panel + m_page_advanced->Hide(); + + // Now the same for simple mode: + gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); + + for (unsigned int i=0;iAdd(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + gridsizer_simple->Add(m_old.back(),0); + gridsizer_simple->Add(m_new.back(),0); + } + + // collect and format sizer + format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple, + _(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")), + _(L("Volume to purge (mm\u00B3) when the filament is being")), 50); + + m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25); + m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25); + + m_sizer->SetSizeHints(this); + SetSizer(m_sizer); + + toggle_advanced(); // to show/hide what is appropriate + + m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) { + wxPaintDC dc(m_page_advanced); + int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y); + wxString label = _(L("From")); + int text_width = 0; + int text_height = 0; + dc.GetTextExtent(label,&text_width,&text_height); + int xpos = m_gridsizer_advanced->GetPosition().x; + dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90); + }); +} + + + + +// Reads values from the (advanced) wiping matrix: +std::vector WipingPanel::read_matrix_values() { + if (!m_advanced) + fill_in_matrix(); + std::vector output; + for (unsigned int i=0;iGetValue().ToDouble(&val); + output.push_back((float)val); + } + } + return output; +} + +// Reads values from simple mode to save them for next time: +std::vector WipingPanel::read_extruders_values() { + std::vector output; + for (unsigned int i=0;iGetValue()); + output.push_back(m_new[i]->GetValue()); + } + return output; +} + +// This updates the "advanced" matrix based on values from "simple" mode +void WipingPanel::fill_in_matrix() { + for (unsigned i=0;iSetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())); + } + } +} + + + +// Function to check if simple and advanced settings are matching +bool WipingPanel::advanced_matches_simple() { + for (unsigned i=0;iGetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()))) + return false; + } + } + return true; +} + + +// Switches the dialog from simple to advanced mode and vice versa +void WipingPanel::toggle_advanced(bool user_action) { + if (m_advanced && !advanced_matches_simple() && user_action) { + if (wxMessageDialog(this,wxString(_(L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"))), + wxString(_(L("Warning"))),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES) + return; + } + if (user_action) + m_advanced = !m_advanced; // user demands a change -> toggle + else + m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate + + (m_advanced ? m_page_advanced : m_page_simple)->Show(); + (!m_advanced ? m_page_advanced : m_page_simple)->Hide(); + + m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings"))); + if (m_advanced) + if (user_action) fill_in_matrix(); // otherwise keep values loaded from config + + m_sizer->Layout(); + Refresh(); +} diff --git a/xs/src/slic3r/GUI/WipeTowerDialog.hpp b/xs/src/slic3r/GUI/WipeTowerDialog.hpp new file mode 100644 index 0000000000..d858062da8 --- /dev/null +++ b/xs/src/slic3r/GUI/WipeTowerDialog.hpp @@ -0,0 +1,90 @@ +#ifndef _WIPE_TOWER_DIALOG_H_ +#define _WIPE_TOWER_DIALOG_H_ + +#include +#include +#include +#include +#include + +#include "RammingChart.hpp" + + +class RammingPanel : public wxPanel { +public: + RammingPanel(wxWindow* parent); + RammingPanel(wxWindow* parent,const std::string& data); + std::string get_parameters(); + +private: + Chart* m_chart = nullptr; + wxSpinCtrl* m_widget_volume = nullptr; + wxSpinCtrl* m_widget_ramming_line_width_multiplicator = nullptr; + wxSpinCtrl* m_widget_ramming_step_multiplicator = nullptr; + wxSpinCtrlDouble* m_widget_time = nullptr; + int m_ramming_step_multiplicator; + int m_ramming_line_width_multiplicator; + + void line_parameters_changed(); +}; + + +class RammingDialog : public wxDialog { +public: + RammingDialog(wxWindow* parent,const std::string& parameters); + std::string get_parameters() { return m_output_data; } +private: + RammingPanel* m_panel_ramming = nullptr; + std::string m_output_data; +}; + + + + + + + +class WipingPanel : public wxPanel { +public: + WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, wxButton* widget_button); + std::vector read_matrix_values(); + std::vector read_extruders_values(); + void toggle_advanced(bool user_action = false); + void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0); + +private: + void fill_in_matrix(); + bool advanced_matches_simple(); + + std::vector m_old; + std::vector m_new; + std::vector> edit_boxes; + unsigned int m_number_of_extruders = 0; + bool m_advanced = false; + wxPanel* m_page_simple = nullptr; + wxPanel* m_page_advanced = nullptr; + wxBoxSizer* m_sizer = nullptr; + wxBoxSizer* m_sizer_simple = nullptr; + wxBoxSizer* m_sizer_advanced = nullptr; + wxGridSizer* m_gridsizer_advanced = nullptr; + wxButton* m_widget_button = nullptr; +}; + + + + + +class WipingDialog : public wxDialog { +public: + WipingDialog(wxWindow* parent,const std::vector& matrix, const std::vector& extruders); + std::vector get_matrix() const { return m_output_matrix; } + std::vector get_extruders() const { return m_output_extruders; } + + +private: + WipingPanel* m_panel_wiping = nullptr; + std::vector m_output_matrix; + std::vector m_output_extruders; +}; + +#endif // _WIPE_TOWER_DIALOG_H_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 1ebd7979e9..8bc282474e 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -109,3 +109,59 @@ void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) ProcessEvent(event); } } + + +// *** wxDataViewTreeCtrlComboPopup *** + +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultWidth = 270; +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultHeight = 200; +const unsigned int wxDataViewTreeCtrlComboPopup::DefaultItemHeight = 22; + +bool wxDataViewTreeCtrlComboPopup::Create(wxWindow* parent) +{ + return wxDataViewTreeCtrl::Create(parent, wxID_ANY/*HIGHEST + 1*/, wxPoint(0, 0), wxDefaultSize/*wxSize(270, -1)*/, wxDV_NO_HEADER); +} +/* +wxSize wxDataViewTreeCtrlComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) +{ + // matches owner wxComboCtrl's width + // and sets height dinamically in dependence of contained items count + wxComboCtrl* cmb = GetComboCtrl(); + if (cmb != nullptr) + { + wxSize size = GetComboCtrl()->GetSize(); + if (m_cnt_open_items > 0) + size.SetHeight(m_cnt_open_items * DefaultItemHeight); + else + size.SetHeight(DefaultHeight); + + return size; + } + else + return wxSize(DefaultWidth, DefaultHeight); +} +*/ +void wxDataViewTreeCtrlComboPopup::OnKeyEvent(wxKeyEvent& evt) +{ + // filters out all the keys which are not working properly + if (evt.GetKeyCode() == WXK_UP) + { + return; + } + else if (evt.GetKeyCode() == WXK_DOWN) + { + return; + } + else + { + evt.Skip(); + return; + } +} + +void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& evt) +{ + wxComboCtrl* cmb = GetComboCtrl(); + auto selected = GetItemText(GetSelection()); + cmb->SetText(selected); +} diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index e61c17bbc3..ed8bb9276a 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -3,6 +3,7 @@ #include #include +#include class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { @@ -25,4 +26,28 @@ public: void OnListBoxSelection(wxCommandEvent& evt); }; + +// *** wxDataViewTreeCtrlComboBox *** + +class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup +{ + static const unsigned int DefaultWidth; + static const unsigned int DefaultHeight; + static const unsigned int DefaultItemHeight; + + wxString m_text; + int m_cnt_open_items{0}; + +public: + virtual bool Create(wxWindow* parent); + virtual wxWindow* GetControl() { return this; } + virtual void SetStringValue(const wxString& value) { m_text = value; } + virtual wxString GetStringValue() const { return m_text; } +// virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight); + + virtual void OnKeyEvent(wxKeyEvent& evt); + void OnDataViewTreeCtrlSelection(wxCommandEvent& evt); + void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } +}; + #endif // slic3r_GUI_wxExtensions_hpp_ diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 25aa6b81a7..86d0aeba29 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -84,7 +84,7 @@ std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); - int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); + int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); void erase() %code{% THIS->clear(); %}; @@ -105,6 +105,7 @@ void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z); void update_outside_state(DynamicPrintConfig* config, bool all_inside); + void update_colors_by_extruder(DynamicPrintConfig* config); bool move_volume_up(int idx) %code%{ diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index d0d4057b22..ecf6f5a22e 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -142,6 +142,8 @@ PresetCollection::arrayref() Ref print() %code%{ RETVAL = &THIS->prints; %}; Ref filament() %code%{ RETVAL = &THIS->filaments; %}; Ref printer() %code%{ RETVAL = &THIS->printers; %}; + Ref project_config() %code%{ RETVAL = &THIS->project_config; %}; + bool has_defauls_only(); std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %};