From 5d77c160c66008b5b64c6ee42d8e9189ac4b5df5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 Mar 2017 01:22:54 +0100 Subject: [PATCH] Changes to the GUI. New preset editor dialog. Option overrides in plater and between configs. More. --- README.md | 1 - lib/Slic3r/Config.pm | 7 +- lib/Slic3r/GUI.pm | 58 +- lib/Slic3r/GUI/Controller.pm | 15 +- lib/Slic3r/GUI/MainFrame.pm | 324 ++-------- lib/Slic3r/GUI/OptionsGroup.pm | 2 +- lib/Slic3r/GUI/Plater.pm | 596 +++++++++++------- .../GUI/Plater/OverrideSettingsPanel.pm | 31 +- lib/Slic3r/GUI/Preferences.pm | 10 - lib/Slic3r/GUI/Preset.pm | 52 ++ lib/Slic3r/GUI/{Tab.pm => PresetEditor.pm} | 470 +++++++++----- lib/Slic3r/GUI/PresetEditorDialog.pm | 65 ++ lib/Slic3r/GUI/Projector.pm | 7 +- lib/Slic3r/GUI/SLAPrintOptions.pm | 7 +- lib/Slic3r/GUI/SimpleTab.pm | 351 ----------- slic3r.pl | 32 +- xs/src/libslic3r/Config.cpp | 22 +- xs/src/libslic3r/Config.hpp | 2 + xs/src/libslic3r/PrintConfig.cpp | 26 +- xs/xsp/Config.xsp | 5 + xs/xsp/Print.xsp | 6 + 21 files changed, 996 insertions(+), 1093 deletions(-) create mode 100644 lib/Slic3r/GUI/Preset.pm rename lib/Slic3r/GUI/{Tab.pm => PresetEditor.pm} (85%) create mode 100644 lib/Slic3r/GUI/PresetEditorDialog.pm delete mode 100644 lib/Slic3r/GUI/SimpleTab.pm diff --git a/README.md b/README.md index dd3c2e29a..5d7d70eb0 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,6 @@ The author of the Silk icon set is Mark James. --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab - --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 5168ddf94..72f28c8bb 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -36,12 +36,11 @@ sub new_from_defaults { my (@opt_keys) = @_; my $self = $class->new; - my $defaults = Slic3r::Config::Full->new; if (@opt_keys) { - $self->set($_, $defaults->get($_)) - for grep $defaults->has($_), @opt_keys; + $self->set($_, $Options->{$_}{default}) + for grep exists $Options->{$_}{default}, @opt_keys; } else { - $self->apply_static($defaults); + $self->apply_static(Slic3r::Config::Full->new); } return $self; } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index c4fe92a04..0624e8883 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -31,9 +31,10 @@ use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::Projector; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup::Field; -use Slic3r::GUI::SimpleTab; +use Slic3r::GUI::Preset; +use Slic3r::GUI::PresetEditor; +use Slic3r::GUI::PresetEditorDialog; use Slic3r::GUI::SLAPrintOptions; -use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_LWP = eval "use LWP::UserAgent; 1"; @@ -57,21 +58,17 @@ use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf our $datadir; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. our $no_controller; -our $no_plater; -our $mode; our $autosave; our @cb; our $Settings = { _ => { - mode => 'simple', version_check => 1, autocenter => 1, invert_zoom => 0, background_processing => 0, # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - # By default, Prusa has the controller hidden. - no_controller => 1, + no_controller => 0, }, }; @@ -96,6 +93,7 @@ sub OnInit { Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; $self->{notifier} = Slic3r::GUI::Notifier->new; + $self->{external_presets} = []; # locate or create data directory # Unix: ~/.Slic3r @@ -123,7 +121,7 @@ sub OnInit { my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") }; $Settings = $ini if $ini; $last_version = $Settings->{_}{version}; - $Settings->{_}{mode} ||= 'expert'; + delete $Settings->{_}{mode}; # handle legacy $Settings->{_}{autocenter} //= 1; $Settings->{_}{invert_zoom} //= 0; $Settings->{_}{background_processing} //= 1; @@ -133,13 +131,17 @@ sub OnInit { $Settings->{_}{version} = $Slic3r::VERSION; $self->save_settings; + if (-f "$enc_datadir/simple.ini") { + # The Simple Mode settings were already automatically duplicated to presets + # named "Simple Mode" in each group, so we already support retrocompatibility. + unlink "$enc_datadir/simple.ini"; + } + # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( - mode => $mode // $Settings->{_}{mode}, # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. no_controller => $no_controller // $Settings->{_}{no_controller}, - no_plater => $no_plater, ); $self->SetTopWindow($frame); @@ -277,20 +279,46 @@ sub save_settings { } sub presets { - my ($self, $section) = @_; + my ($self, $section, $force_default) = @_; + + my @presets = (); - my %presets = (); opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; foreach my $file (grep /\.ini$/i, readdir $dh) { $file = Slic3r::decode_path($file); my $name = basename($file); - $name =~ s/\.ini$//; - $presets{$name} = "$Slic3r::GUI::datadir/$section/$file"; + $name =~ s/\.ini$//i; + push @presets, Slic3r::GUI::Preset->new( + name => $name, + file => "$Slic3r::GUI::datadir/$section/$file", + ); } closedir $dh; - return %presets; + @presets = sort { $a->name cmp $b->name } + @presets, + (grep -e $_->file, @{$self->{external_presets}}); + + if ($force_default || !@presets) { + unshift @presets, Slic3r::GUI::Preset->new( + default => 1, + name => '- default -', + ); + } + + return @presets; +} + +sub add_external_preset { + my ($self, $file) = @_; + + push @{$self->{external_presets}}, my $preset = Slic3r::GUI::Preset->new( + name => basename($file), # keep .ini suffix + file => $file, + external => 1, + ); + return $preset; } sub have_version_check { diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index c6a568f82..7ebf3491d 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -7,6 +7,7 @@ use strict; use warnings; use utf8; +use List::Util qw(any); use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use base qw(Wx::ScrolledWindow Class::Accessor); @@ -39,14 +40,14 @@ sub new { EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; - my %presets = wxTheApp->presets('printer'); + my %presets = map { $_->name => $_ } wxTheApp->presets('printer'); # remove printers that already exist my @panels = $self->print_panels; delete $presets{$_} for map $_->printer_name, @panels; foreach my $preset_name (sort keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); + my $config = $presets{$preset_name}->load_config; next if !$config->serial_port; my $id = &Wx::NewId(); @@ -100,8 +101,8 @@ sub OnActivate { # get all available presets my %presets = (); { - my %all = wxTheApp->presets('printer'); - my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all; + my %all = map { $_->name => $_ } wxTheApp->presets('printer'); + my %configs = map { my $name = $_; $name => $all{$name}->load_config } keys %all; %presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all; } @@ -177,6 +178,12 @@ sub print_panels { map $_->GetWindow, $self->{sizer}->GetChildren; } +sub printing { + my ($self) = @_; + + return any { $_->printing } $self->print_panels; +} + sub update_presets { my $self = shift; my ($group, $presets, $selected, $is_dirty) = @_; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index a3c04cd25..93ca6bfae 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -28,11 +28,8 @@ sub new { } # store input params - $self->{mode} = $params{mode}; - $self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. $self->{no_controller} = $params{no_controller}; - $self->{no_plater} = $params{no_plater}; $self->{loaded} = 0; # initialize tabpanel and menubar @@ -80,9 +77,17 @@ sub new { EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { - $event->Veto; - return; + if ($event->CanVeto) { + my $veto = 0; + if ($self->{controller} && $self->{controller}->printing) { + my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?", + 'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + $veto = 1 if $confirm->ShowModal == wxID_YES; + } + if ($veto) { + $event->Veto; + return; + } } # save window size @@ -107,71 +112,10 @@ sub _init_tabpanel { $panel->OnActivate if $panel->can('OnActivate'); }); - if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); - } + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); } - $self->{options_tabs} = {}; - - my $simple_config; - if ($self->{mode} eq 'simple') { - $simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini") - if -e Slic3r::encode_path("$Slic3r::GUI::datadir/simple.ini"); - } - - my $class_prefix = $self->{mode} eq 'simple' ? "Slic3r::GUI::SimpleTab::" : "Slic3r::GUI::Tab::"; - for my $tab_name (qw(print filament printer)) { - my $tab; - $tab = $self->{options_tabs}{$tab_name} = ($class_prefix . ucfirst $tab_name)->new( - $panel, - no_controller => $self->{no_controller}); - $tab->on_value_change(sub { - my ($opt_key, $value) = @_; - - my $config = $tab->config; - if ($self->{plater}) { - $self->{plater}->on_config_change($config); # propagate config change events to the plater - $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; - } - if ($self->{loaded}) { # don't save while loading for the first time - if ($self->{mode} eq 'simple') { - # save config - $self->config->save("$Slic3r::GUI::datadir/simple.ini"); - - # save a copy into each preset section - # so that user gets the config when switching to expert mode - $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode'); - $Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini'; - wxTheApp->save_settings; - } - $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; - } - }); - $tab->on_presets_changed(sub { - if ($self->{plater}) { - $self->{plater}->update_presets($tab_name, @_); - $self->{plater}->on_config_change($tab->config); - if ($self->{controller}) { - $self->{controller}->update_presets($tab_name, @_); - } - } - }); - $tab->load_presets; - $panel->AddPage($tab, $tab->title); - $tab->load_config($simple_config) if $simple_config; - } - - if ($self->{plater}) { - $self->{plater}->on_select_preset(sub { - my ($group, $i) = @_; - $self->{options_tabs}{$group}->select_preset($i); - }); - - # load initial config - $self->{plater}->on_config_change($self->config); - } } sub _init_menubar { @@ -239,7 +183,7 @@ sub _init_menubar { } # Plater menu - unless ($self->{no_plater}) { + { my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; @@ -252,49 +196,27 @@ sub _init_menubar { $self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }, undef, 'brick_go.png'); - $self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub { - $plater->pause_background_process; - Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal; - $plater->resume_background_process; - }, undef, 'film.png'); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); } - # Window menu - my $windowMenu = Wx::Menu->new; + # Settings menu + my $settingsMenu = Wx::Menu->new; { - my $tab_offset = 0; - if (!$self->{no_plater}) { - $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { - $self->select_tab(0); - }, undef, 'application_view_tile.png'); - $tab_offset += 1; - } - if (!$self->{no_controller}) { - $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { - $self->select_tab(1); - }, undef, 'printer_empty.png'); - $tab_offset += 1; - } - if ($tab_offset > 0) { - $windowMenu->AppendSeparator(); - } - - $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { - $self->select_tab($tab_offset+0); + $self->_append_menu_item($settingsMenu, "P&rint Settings…\tCtrl+1", 'Show the print settings editor', sub { + $self->{plater}->show_preset_editor('print'); }, undef, 'cog.png'); - $self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { - $self->select_tab($tab_offset+1); + $self->_append_menu_item($settingsMenu, "&Filament Settings…\tCtrl+2", 'Show the filament settings editor', sub { + $self->{plater}->show_preset_editor('filament'); }, undef, 'spool.png'); - $self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { - $self->select_tab($tab_offset+2); + $self->_append_menu_item($settingsMenu, "Print&er Settings…\tCtrl+3", 'Show the printer settings editor', sub { + $self->{plater}->show_preset_editor('printer'); }, undef, 'printer_empty.png'); } # View menu - if (!$self->{no_plater}) { + { $self->{viewMenu} = Wx::Menu->new; $self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); }); $self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); }); @@ -305,6 +227,22 @@ sub _init_menubar { $self->_append_menu_item($self->{viewMenu}, "Right" , 'Right View' , sub { $self->select_view('right' ); }); } + # Window menu + my $windowMenu = Wx::Menu->new; + { + $self->_append_menu_item($windowMenu, "&Plater\tCtrl+T", 'Show the plater', sub { + $self->select_tab(0); + }, undef, 'application_view_tile.png'); + $self->_append_menu_item($windowMenu, "&Controller\tCtrl+Y", 'Show the printer controller', sub { + $self->select_tab(1); + }, undef, 'printer_empty.png') if !$self->{no_controller}; + $self->_append_menu_item($windowMenu, "DLP Projector…\tCtrl+P", 'Open projector window for DLP printing', sub { + $self->{plater}->pause_background_process; + Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal; + $self->{plater}->resume_background_process; + }, undef, 'film.png'); + } + # Help menu my $helpMenu = Wx::Menu->new; { @@ -336,8 +274,9 @@ sub _init_menubar { $menubar->Append($fileMenu, "&File"); $menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu}; $menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu}; - $menubar->Append($windowMenu, "&Window"); + $menubar->Append($settingsMenu, "&Settings"); $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; + $menubar->Append($windowMenu, "&Window"); $menubar->Append($helpMenu, "&Help"); $self->SetMenuBar($menubar); } @@ -363,7 +302,7 @@ sub quick_slice { my $progress_dialog; eval { # validate configuration - my $config = $self->config; + my $config = $self->{plater}->config; $config->validate; # select input file @@ -509,7 +448,7 @@ sub repair_stl { sub export_config { my $self = shift; - my $config = $self->config; + my $config = $self->{plater}->config; eval { # validate configuration $config->validate; @@ -535,7 +474,6 @@ sub load_config_file { my ($file) = @_; if (!$file) { - return unless $self->check_unsaved_changes; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); @@ -546,9 +484,10 @@ sub load_config_file { $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; $last_config = $file; - for my $tab (values %{$self->{options_tabs}}) { - $tab->load_config_file($file); - } + + my $preset = wxTheApp->add_external_preset($file); + $self->{plater}->load_presets; + $self->{plater}->select_preset_by_name($preset->name, $_) for qw(print filament printer); } sub export_configbundle { @@ -556,7 +495,7 @@ sub export_configbundle { eval { # validate current configuration in case it's dirty - $self->config->validate; + $self->{plater}->config->validate; }; Slic3r::GUI::catch_error($self) and return; @@ -571,18 +510,13 @@ sub export_configbundle { # leave default category empty to prevent the bundle from being parsed as a normal config file my $ini = { _ => {} }; - $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter mode); + $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter); $ini->{presets} = $Slic3r::GUI::Settings->{presets}; - if (-e "$Slic3r::GUI::datadir/simple.ini") { - my $config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini"); - $ini->{simple} = $config->as_ini->{_}; - } foreach my $section (qw(print filament printer)) { - my %presets = wxTheApp->presets($section); - foreach my $preset_name (keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); - $ini->{"$section:$preset_name"} = $config->as_ini->{_}; + my @presets = wxTheApp->presets($section); + foreach my $preset (@presets) { + $ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_}; } } @@ -617,15 +551,6 @@ sub load_configbundle { $Slic3r::GUI::Settings->{presets} = $ini->{presets}; wxTheApp->save_settings; } - if ($ini->{simple}) { - my $config = Slic3r::Config->load_ini_hash($ini->{simple}); - $config->save("$Slic3r::GUI::datadir/simple.ini"); - if ($self->{mode} eq 'simple') { - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config) for values %{$self->{options_tabs}}; - } - } - } my $imported = 0; INI_BLOCK: foreach my $ini_category (sort keys %$ini) { next unless $ini_category =~ /^(print|filament|printer):(.+)$/; @@ -634,11 +559,11 @@ sub load_configbundle { next if $skip_no_id && !$config->get($section . "_settings_id"); { - my %current_presets = Slic3r::GUI->presets($section); + my @current_presets = Slic3r::GUI->presets($section); my %current_ids = map { $_ => 1 } grep $_, - map Slic3r::Config->load($_)->get($section . "_settings_id"), - values %current_presets; + map $_->load_config->get($section . "_settings_id"), + @current_presets; next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; } @@ -646,150 +571,34 @@ sub load_configbundle { Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name; $imported++; } - if ($self->{mode} eq 'expert') { - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; - } - } + $self->{plater}->load_presets; return if !$imported; my $message = sprintf "%d presets successfully imported.", $imported; - if ($self->{mode} eq 'simple' && $Slic3r::GUI::Settings->{_}{mode} eq 'expert') { - Slic3r::GUI::show_info($self, "$message You need to restart Slic3r to make the changes effective."); - } else { - Slic3r::GUI::show_info($self, $message); - } + Slic3r::GUI::show_info($self, $message); } sub load_config { - my $self = shift; - my ($config) = @_; + my ($self, $config) = @_; - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config); - } - if ($self->{plater}) { - $self->{plater}->on_config_change($config); - } + $self->{plater}->load_config($config); } sub config_wizard { my $self = shift; - return unless $self->check_unsaved_changes; if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { - if ($self->{mode} eq 'expert') { - for my $tab (values %{$self->{options_tabs}}) { - $tab->select_default_preset; - } - } else { - # TODO: select default settings in simple mode - } - $self->load_config($config); - if ($self->{mode} eq 'expert') { - for my $tab (values %{$self->{options_tabs}}) { - $tab->save_preset('My Settings'); - } + foreach my $group (qw(print filament printer)) { + my $name = 'My Settings'; + $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $group, $name); + $Slic3r::GUI::Settings->{presets}{$group} = "$name.ini"; + $self->{plater}->load_presets; + $self->{plater}->select_preset_by_name($name, $group); } } } -=head2 config - -This method collects all config values from the tabs and merges them into a single config object. - -=cut - -sub config { - my $self = shift; - - return Slic3r::Config->new_from_defaults - if !exists $self->{options_tabs}{print} - || !exists $self->{options_tabs}{filament} - || !exists $self->{options_tabs}{printer}; - - # retrieve filament presets and build a single config object for them - my $filament_config; - if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { - $filament_config = $self->{options_tabs}{filament}->config; - } else { - my $i = -1; - foreach my $preset_idx ($self->{plater}->filament_presets) { - $i++; - my $config; - if ($preset_idx == $self->{options_tabs}{filament}->current_preset) { - # the selected preset for this extruder is the one in the tab - # use the tab's config instead of the preset in case it is dirty - # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. - $config = $self->{options_tabs}{filament}->config; - } else { - my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); - $config = $self->{options_tabs}{filament}->get_preset_config($preset); - } - if (!$filament_config) { - $filament_config = $config->clone; - next; - } - foreach my $opt_key (@{$config->get_keys}) { - my $value = $filament_config->get($opt_key); - next unless ref $value eq 'ARRAY'; - $value->[$i] = $config->get($opt_key)->[0]; - $filament_config->set($opt_key, $value); - } - } - } - - my $config = Slic3r::Config->merge( - Slic3r::Config->new_from_defaults, - $self->{options_tabs}{print}->config, - $self->{options_tabs}{printer}->config, - $filament_config, - ); - - if ($self->{mode} eq 'simple') { - # set some sensible defaults - $config->set('first_layer_height', $config->nozzle_diameter->[0]); - $config->set('avoid_crossing_perimeters', 1); - $config->set('infill_every_layers', 10); - } else { - my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; - $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) - for qw(perimeter infill solid_infill support_material support_material_interface); - } - - return $config; -} - -sub filament_preset_names { - my ($self) = @_; - - if ($self->{mode} eq 'simple') { - return ''; - } - - return map $self->{options_tabs}{filament}->get_preset($_)->name, - $self->{plater}->filament_presets; -} - -sub check_unsaved_changes { - my $self = shift; - - my @dirty = (); - foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->is_dirty; - } - - if (@dirty) { - my $titles = join ', ', @dirty; - my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", - 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return ($confirm->ShowModal == wxID_YES); - } - - return 1; -} - sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->SetSelection($tab); @@ -798,9 +607,8 @@ sub select_tab { # Set a camera direction, zoom to all objects. sub select_view { my ($self, $direction) = @_; - if (! $self->{no_plater}) { - $self->{plater}->select_view($direction); - } + + $self->{plater}->select_view($direction); } sub _append_menu_item { diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 2a00b1a0c..776f80ec3 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -1,4 +1,4 @@ -# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc. +# A dialog group object. Used by the PresetEditor, Preferences dialog, ManualControlDialog etc. package Slic3r::GUI::OptionsGroup; use Moo; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3ce193a7c..b93e49038 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -6,16 +6,17 @@ use warnings; use utf8; use File::Basename qw(basename dirname); -use List::Util qw(sum first max); +use List::Util qw(sum first max none any); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg); use LWP::UserAgent; use threads::shared qw(shared_clone); -use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc +use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox); -use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED - EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL +use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); -use base 'Wx::Panel'; +use base qw(Wx::Panel Class::Accessor); + +__PACKAGE__->mk_accessors(qw(presets)); use constant TB_ADD => &Wx::NewId; use constant TB_REMOVE => &Wx::NewId; @@ -42,15 +43,13 @@ our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; use constant FILAMENT_CHOOSERS_SPACING => 0; use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds -my $PreventListEvents = 0; - sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config->new_from_defaults(qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width - serial_port serial_speed octoprint_host octoprint_apikey + serial_port serial_speed octoprint_host octoprint_apikey overridable )); $self->{model} = Slic3r::Model->new; $self->{print} = Slic3r::Print->new; @@ -194,23 +193,6 @@ sub new { } } - $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, - wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); - $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); - $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); - $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); - EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); - EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); - EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); - EVT_KEY_DOWN($self->{list}, sub { - my ($list, $event) = @_; - if ($event->GetKeyCode == WXK_TAB) { - $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); - } else { - $event->Skip; - } - }); - # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self, -1, "Print…", wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -314,7 +296,7 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), - $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; + $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; @@ -365,32 +347,52 @@ sub new { $self->update; { - my $presets; - if ($self->GetFrame->{mode} eq 'expert') { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); - $presets->AddGrowableCol(1, 1); - $presets->SetFlexibleDirection(wxHORIZONTAL); - my %group_labels = ( - print => 'Print settings', - filament => 'Filament', - printer => 'Printer', - ); - $self->{preset_choosers} = {}; - for my $group (qw(print filament printer)) { - my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - $text->SetFont($Slic3r::GUI::small_font); - my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); - $self->{preset_choosers}{$group} = [$choice]; - # setup the listener - EVT_COMBOBOX($choice, $choice, sub { - my ($choice) = @_; - wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice); - }); + my $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 3, 1, 2); + $presets->AddGrowableCol(1, 1); + $presets->SetFlexibleDirection(wxHORIZONTAL); + my %group_labels = ( + print => 'Print settings', + filament => 'Filament', + printer => 'Printer', + ); + $self->{preset_choosers} = {}; + for my $group (qw(print filament printer)) { + # label + my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + $text->SetFont($Slic3r::GUI::small_font); + + # dropdown control + my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); + $self->{preset_choosers}{$group} = [$choice]; + # setup the listener + EVT_COMBOBOX($choice, $choice, sub { + my ($choice) = @_; + wxTheApp->CallAfter(sub { + $self->_on_select_preset($group); }); - $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); - } + }); + + # settings button + my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + EVT_BUTTON($self, $settings_btn, sub { + $self->show_preset_editor($group, 0); + }); + + $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); + $presets->Add($settings_btn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxLEFT, 3); + } + + { + $self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, + on_change => sub { + $self->config_changed; + }); + $self->{settings_override_panel}->set_editable(0); + $self->{settings_override_config} = Slic3r::Config->new; + $self->{settings_override_panel}->set_default_config($self->{settings_override_config}); + $self->{settings_override_panel}->set_config($self->{settings_override_config}); } my $object_info_sizer; @@ -405,6 +407,8 @@ sub new { $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( + name => "Name", + copies => "Copies", size => "Size", volume => "Volume", facets => "Facets", @@ -473,7 +477,7 @@ sub new { my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); + $right_sizer->Add($self->{settings_override_panel}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); $right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); $right_sizer->Hide($print_info_sizer); @@ -492,35 +496,83 @@ sub new { $self->SetSizer($sizer); } + $self->load_presets; + $self->_on_select_preset($_) for qw(printer filament print); + return $self; } -# sets the callback -sub on_select_preset { - my ($self, $cb) = @_; - $self->{on_select_preset} = $cb; +sub _on_select_preset { + my ($self, $group) = @_; + + my @presets = map $self->presets->{$group}[scalar $_->GetSelection], + @{$self->{preset_choosers}{$group}}; + + $Slic3r::GUI::Settings->{presets}{$group} = $presets[0]->name; + $Slic3r::GUI::Settings->{presets}{"${group}_${_}"} = $presets[$_]->name + for 1..$#presets; + + wxTheApp->save_settings; + + my $config = $self->config; + + $self->on_extruders_change(scalar @{$config->get('nozzle_diameter')}); + + foreach my $opt_key (@{$self->{config}->diff($config)}) { + # Ignore overrides. No need to set them in our config; we'll use them directly below. + next if $opt_key eq 'overrides'; + + $self->{config}->set($opt_key, $config->get($opt_key)); + + if ($opt_key eq 'bed_shape') { + $self->{canvas}->update_bed_size; + $self->{canvas3D}->update_bed_size if $self->{canvas3D}; + $self->{preview3D}->set_bed_shape($self->{config}->bed_shape) + if $self->{preview3D}; + $self->update; + } elsif ($opt_key eq 'serial_port') { + if ($config->get('serial_port')) { + $self->{btn_print}->Show; + } else { + $self->{btn_print}->Hide; + } + $self->Layout; + } elsif ($opt_key eq 'octoprint_host') { + if ($config->get('octoprint_host')) { + $self->{btn_send_gcode}->Show; + } else { + $self->{btn_send_gcode}->Hide; + } + } + } + + if ($group eq 'print') { + $self->{settings_override_config}->clear; + my $overridable = $config->get('overridable'); + if ($overridable) { + $self->{settings_override_config}->set($_, $config->get($_)) + for @$overridable; + } + $self->{settings_override_panel}->update_optgroup; + } elsif ($group eq 'printer') { + # reload print and filament settings to honor their compatible_printer options + $self->load_presets; + } + + return if !$self->GetFrame->is_loaded; + + $self->config_changed; } -sub _on_select_preset { - my $self = shift; - my ($group, $choice) = @_; - - # if user changed filament preset, don't propagate this to the tabs - if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - my @filament_presets = $self->filament_presets; - $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini"; - $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_]) - for 1 .. $#filament_presets; - wxTheApp->save_settings; - return; - } - - # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, scalar $choice->GetSelection) - if $self->{on_select_preset}; - - # get new config and generate on_config_change() event for updating plater and other things - $self->on_config_change($self->GetFrame->config); +sub load_config { + my ($self, $config) = @_; + + # This method is called with the CLI options. + # We add them to the visible overrides. + $self->{settings_override_config}->apply($config); + $self->{settings_override_panel}->update_optgroup; + + $self->config_changed; } sub GetFrame { @@ -528,60 +580,188 @@ sub GetFrame { return &Wx::GetTopLevelParent($self); } -sub update_presets { - my $self = shift; - my ($group, $presets, $selected, $is_dirty) = @_; +sub load_presets { + my ($self) = @_; - my @choosers = @{ $self->{preset_choosers}{$group} }; - foreach my $choice (@choosers) { - if ($group eq 'filament' && @choosers > 1) { - # if we have more than one filament chooser, keep our selection - # instead of importing the one from the tab - $selected = $choice->GetSelection; - $is_dirty = 0; - } - $choice->Clear; - foreach my $preset (@$presets) { - my $bitmap; - if ($group eq 'filament') { - my $config = $preset->config(['filament_colour']); - my $rgb_hex = $config->filament_colour->[0]; - if ($preset->default) { - $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); - } else { - $rgb_hex =~ s/^#//; - my @rgb = unpack 'C*', pack 'H*', $rgb_hex; - my $image = Wx::Image->new(16,16); - $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); - $bitmap = Wx::Bitmap->new($image); + $self->presets({}); + + my $selected_printer_name; + foreach my $group (qw(printer filament print)) { + my @presets = wxTheApp->presets($group); + if ($group eq 'filament' || $group eq 'print') { + my %printer_names = map { $_->name => 1 } @{ $self->presets->{printer} }; + # Skip presets not compatible with the selected printer, if they + # have other compatible printers configured (and at least one of them exists). + for (my $i = 0; $i <= $#presets; ++$i) { + my $config = $presets[$i]->config; + next if !$config->has('compatible_printers'); + my @compat = @{$config->compatible_printers}; + if (@compat + && (none { $_ eq $selected_printer_name } @compat) + && (any { $printer_names{$_} } @compat)) { + splice @presets, $i, 1; + --$i; } - } elsif ($group eq 'print') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); - } elsif ($group eq 'printer') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); } - $choice->AppendString($preset->name, $bitmap); + } + if (!@presets) { + unshift @presets, Slic3r::GUI::Preset->new( + default => 1, + name => '- default -', + ); } - if ($selected <= $#$presets) { - my $preset_name = $choice->GetString($selected); - if ($is_dirty) { - $choice->SetString($selected, "$preset_name (modified)"); + $self->presets->{$group} = [@presets]; + + # get the wxChoice objects for this group + my @choosers = @{ $self->{preset_choosers}{$group} }; + + # find the currently selected one(s) according to the saved file + my @sel = (); + if (my $current = $Slic3r::GUI::Settings->{presets}{$group}) { + push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets; + } + for my $i (1..(@choosers-1)) { + if (my $current = $Slic3r::GUI::Settings->{presets}{"${group}_$i"}) { + push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets; + } + } + @sel = (0) if !@sel; + + # populate the wxChoice objects + foreach my $choice (@choosers) { + $choice->Clear; + foreach my $preset (@presets) { + # load/generate the proper icon + my $bitmap; + if ($group eq 'filament') { + my $config = $preset->config; + if ($preset->default || !$config->has('filament_colour')) { + $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); + } else { + my $rgb_hex = $config->filament_colour->[0]; + + $rgb_hex =~ s/^#//; + my @rgb = unpack 'C*', pack 'H*', $rgb_hex; + my $image = Wx::Image->new(16,16); + $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); + $bitmap = Wx::Bitmap->new($image); + } + } elsif ($group eq 'print') { + $bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); + } elsif ($group eq 'printer') { + $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); + } + $choice->AppendString($preset->name, $bitmap); + } + + my $selected = shift @sel; + if ($selected <= $#presets) { + # call SetSelection() only after SetString() otherwise the new string + # won't be picked up as the visible string + $choice->SetSelection($selected); + + my $preset_name = $choice->GetString($selected); + $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); + $selected_printer_name = $preset_name if $group eq 'printer'; } - # call SetSelection() only after SetString() otherwise the new string - # won't be picked up as the visible string - $choice->SetSelection($selected); - - $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); } } + + #$self->_on_select_preset($_) for qw(printer filament print); } -sub filament_presets { - my $self = shift; +sub select_preset_by_name { + my ($self, $name, $group, $n) = @_; - # force scalar context for GetSelection() as it's context-aware - return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} }; + # $n is optional + + my $presets = $self->presets->{$group}; + my $i = first { $presets->[$_]->name eq $name } 0..$#$presets; + return if !defined $i; + + my $choosers = $self->{preset_choosers}{$group}; + if (defined $n && $n <= $#$choosers) { + $choosers->[$n]->SetSelection($i); + } else { + $_->SetSelection($i) for @$choosers; + } + $self->_on_select_preset($group); +} + +sub selected_presets { + my ($self, $group) = @_; + + my %presets; + foreach my $group (qw(printer filament print)) { + my @i = map scalar($_->GetSelection), @{ $self->{preset_choosers}{$group} }; + $presets{$group} = [ @{$self->presets->{$group}}[@i] ]; + } + return $group ? @{$presets{$group}} : %presets; +} + +sub show_preset_editor { + my ($self, $group, $i) = @_; + + my $class = "Slic3r::GUI::PresetEditorDialog::" . ucfirst($group); + my $dlg = $class->new($self); + + my @presets = $self->selected_presets($group); + $dlg->preset_editor->select_preset_by_name($presets[$i // 0]->name); + $dlg->ShowModal; + + # Re-load the presets as they might have changed. + $self->load_presets; + + # Select the preset that was last selected in the editor. + $self->select_preset_by_name + ($dlg->preset_editor->get_current_preset->name, $group, $i, 1); +} + +# Returns the current config by merging the selected presets and the overrides. +sub config { + my ($self) = @_; + + # use a DynamicConfig because FullPrintConfig is not enough + my $config = Slic3r::Config->new_from_defaults; + + # get defaults also for the values tracked by the Plater's config + # (for example 'overridable') + $config->apply(Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys})); + + my %classes = map { $_ => "Slic3r::GUI::PresetEditor::".ucfirst($_) } + qw(print filament printer); + + my %presets = $self->selected_presets; + $config->apply($_->config([ $classes{printer}->options ])) + for @{ $presets{printer} }; + if (@{ $presets{filament} }) { + my @opt_keys = ($classes{filament}->options, $classes{filament}->overriding_options); + my $filament_config = shift(@{ $presets{filament} }) + ->config(\@opt_keys); + + my $i = 1; + for my $preset (@{ $presets{filament} }) { + my $config = $preset->config(\@opt_keys); + foreach my $opt_key (@{$config->get_keys}) { + if ($filament_config->has($opt_key)) { + my $value = $filament_config->get($opt_key); + next unless ref $value eq 'ARRAY'; + $value->[$i] = $config->get($opt_key)->[0]; + $filament_config->set($opt_key, $value); + } + } + ++$i; + } + + $config->apply($filament_config); + } + $config->apply($_->config([ $classes{print}->options ])) + for @{ $presets{print} }; + + $config->apply($self->{settings_override_config}); + + return $config; } sub add { @@ -712,18 +892,7 @@ sub load_model_objects { ); } - foreach my $obj_idx (@obj_idx) { - my $object = $self->{objects}[$obj_idx]; - my $model_object = $self->{model}->objects->[$obj_idx]; - $self->{list}->InsertStringItem($obj_idx, $object->name); - $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) - if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() - - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); - - $self->make_thumbnail($obj_idx); - } + $self->make_thumbnail($_) for @obj_idx; $self->arrange if $need_arrange; $self->update; @@ -731,8 +900,6 @@ sub load_model_objects { $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; - $self->{list}->Update; - $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; @@ -766,7 +933,6 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); - $self->{list}->DeleteItem($obj_idx); $self->object_list_changed; $self->select_object(undef); @@ -786,7 +952,6 @@ sub reset { @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; - $self->{list}->DeleteAllItems; $self->object_list_changed; $self->select_object(undef); @@ -808,7 +973,6 @@ sub increase { ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; @@ -833,15 +997,10 @@ sub decrease { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } else { $self->remove; } - if ($self->{objects}[$obj_idx]) { - $self->{list}->Select($obj_idx, 0); - $self->{list}->Select($obj_idx, 1); - } $self->update; $self->schedule_background_process; } @@ -1006,8 +1165,8 @@ sub changescale { $model_instance->scaling_factor*100, $self); return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0; } - - $self->{list}->SetItem($obj_idx, 2, "$scale%"); + return if !$scale || $scale < 0; + $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; @@ -1035,7 +1194,7 @@ sub arrange { $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); + my $success = $self->{model}->arrange_objects($self->config->min_object_distance, $bb); # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed @@ -1086,6 +1245,17 @@ sub split_object { $self->load_model_objects(@model_objects); } +sub config_changed { + my ($self) = @_; + + if ($Slic3r::GUI::autosave) { + $self->config->save($Slic3r::GUI::autosave); + } + + # (re) start timer + $self->schedule_background_process; +} + sub schedule_background_process { my ($self) = @_; @@ -1117,7 +1287,7 @@ sub async_apply_config { $self->pause_background_process; # apply new config - my $invalidated = $self->{print}->apply_config($self->GetFrame->config); + my $invalidated = $self->{print}->apply_config($self->config); return if !$Slic3r::GUI::Settings->{_}{background_processing}; @@ -1151,7 +1321,7 @@ sub start_background_process { # don't start process thread if config is not valid eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + $self->config->validate; $self->{print}->validate; }; if ($@) { @@ -1239,14 +1409,14 @@ sub export_gcode { # (we assume that if it is running, config is valid) eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + $self->config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print - my $config = $self->GetFrame->config; + my $config = $self->config; eval { # this will throw errors if config is not valid $config->validate; @@ -1410,15 +1580,15 @@ sub on_export_completed { sub do_print { my ($self) = @_; - my $printer_tab = $self->GetFrame->{options_tabs}{printer}; - my $printer_name = $printer_tab->get_current_preset->name; + my $controller = $self->GetFrame->{controller} or return; - my $controller = $self->GetFrame->{controller}; - my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config); + my %current_presets = $self->selected_presets; + + my $printer_name = $current_presets{printer}->[0]->name; + my $printer_panel = $controller->add_printer($printer_name, $self->config); my $filament_stats = $self->{print}->filament_stats; - my @filament_names = $self->GetFrame->filament_preset_names; - $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; + $filament_stats = { map { $current_presets{filament}[$_]->name => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); $self->GetFrame->select_tab(1); @@ -1620,18 +1790,27 @@ sub on_extruders_change { # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; - # insert new choice into sizer - $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); - $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + # settings button + my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - # setup the listener + # insert new row into sizer + $self->{presets_sizer}->Insert(6 + ($#$choices-1)*3, 0, 0); + $self->{presets_sizer}->Insert(7 + ($#$choices-1)*3, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + $self->{presets_sizer}->Insert(8 + ($#$choices-1)*3, $settings_btn, 0, wxEXPAND | wxLEFT, 4); + + # setup the listeners EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice); + $self->_on_select_preset('filament'); }); }); + EVT_BUTTON($self, $settings_btn, sub { + $self->show_preset_editor('filament', $#$choices); + }); + # initialize selection my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; $choice->SetSelection($i || 0); @@ -1639,79 +1818,21 @@ sub on_extruders_change { # remove unused choices if any while (@$choices > $num_extruders) { - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice + my $i = 6 + ($#$choices-1)*3; + + $self->{presets_sizer}->Remove($i); # label + $self->{presets_sizer}->Remove($i); # wxChoice + + my $settings_btn = $self->{presets_sizer}->GetItem($i)->GetWindow; + $self->{presets_sizer}->Remove($i); # settings btn + $settings_btn->Destroy; + $choices->[-1]->Destroy; pop @$choices; } $self->Layout; } -sub on_config_change { - my $self = shift; - my ($config) = @_; - - foreach my $opt_key (@{$self->{config}->diff($config)}) { - $self->{config}->set($opt_key, $config->get($opt_key)); - if ($opt_key eq 'bed_shape') { - $self->{canvas}->update_bed_size; - $self->{canvas3D}->update_bed_size if $self->{canvas3D}; - $self->{preview3D}->set_bed_shape($self->{config}->bed_shape) - if $self->{preview3D}; - $self->update; - } elsif ($opt_key eq 'serial_port') { - if ($config->get('serial_port')) { - $self->{btn_print}->Show; - } else { - $self->{btn_print}->Hide; - } - $self->Layout; - } elsif ($opt_key eq 'octoprint_host') { - if ($config->get('octoprint_host')) { - $self->{btn_send_gcode}->Show; - } else { - $self->{btn_send_gcode}->Hide; - } - $self->Layout; - } - } - if ($self->{"right_sizer"}) { - $self->{"right_sizer"}->Hide($self->{"sliced_info_box"}); - $self->{"right_sizer"}->Layout; - } - - return if !$self->GetFrame->is_loaded; - - # (re)start timer - $self->schedule_background_process; -} - -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); - $self->refresh_canvases; - } -} - -sub list_item_selected { - my ($self, $event) = @_; - return if $PreventListEvents; - - my $obj_idx = $event->GetIndex; - $self->select_object($obj_idx); - $self->refresh_canvases; -} - -sub list_item_activated { - my ($self, $event, $obj_idx) = @_; - - $obj_idx //= $event->GetIndex; - $self->object_settings_dialog($obj_idx); -} - sub object_cut_dialog { my $self = shift; my ($obj_idx) = @_; @@ -1818,9 +1939,17 @@ sub selection_changed { if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { + $self->{object_info_name}->SetLabel($object->name); my $model_object = $self->{model}->objects->[$obj_idx]; + $self->{object_info_copies}->SetLabel($model_object->instances_count); my $model_instance = $model_object->instances->[0]; - $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); + { + my $size_string = sprintf "%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size}; + if ($model_instance->scaling_factor != 1) { + $size_string .= sprintf " (%s%%)", $model_instance->scaling_factor * 100; + } + $self->{object_info_size}->SetLabel($size_string); + } $self->{object_info_materials}->SetLabel($model_object->materials_count); my $raw_mesh = $model_object->raw_mesh; @@ -1845,7 +1974,7 @@ sub selection_changed { $self->{object_info_facets}->SetLabel($object->facets); } } else { - $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); + $self->{"object_info_$_"}->SetLabel("") for qw(name copies size volume facets materials manifold); $self->{object_info_manifold_warning_icon}->Hide; $self->{object_info_manifold}->SetToolTipString(""); } @@ -1862,15 +1991,6 @@ sub select_object { $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - - # We use this flag to avoid circular event handling - # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, - # whose event handler calls this method again and again and again - $PreventListEvents = 1; - $self->{list}->Select($obj_idx, 1); - $PreventListEvents = 0; - } else { - # TODO: deselect all in list } $self->selection_changed(1); } @@ -1895,7 +2015,7 @@ sub validate_config { my $self = shift; eval { - $self->GetFrame->config->validate; + $self->config->validate; }; return 0 if Slic3r::GUI::catch_error($self); return 1; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index e848b7638..5328cbff5 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -1,5 +1,4 @@ -# Included in ObjectSettingsDialog -> ObjectPartsPanel. -# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh. +# Maintains, displays, adds and removes overrides of slicing parameters. package Slic3r::GUI::Plater::OverrideSettingsPanel; use strict; @@ -19,10 +18,11 @@ use constant ICON_MODIFIERMESH => 2; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $params{size} // wxDefaultSize, wxTAB_TRAVERSAL); $self->{default_config} = Slic3r::Config->new; $self->{config} = Slic3r::Config->new; $self->{on_change} = $params{on_change}; + $self->{editable} = 1; $self->{fixed_options} = {}; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); @@ -64,17 +64,20 @@ sub new { return $self; } +# Sets the config used to get the default values for user-added options. sub set_default_config { my ($self, $config) = @_; $self->{default_config} = $config; } +# Sets the target config, whose options will be displayed in the OptionsGroup. sub set_config { my ($self, $config) = @_; $self->{config} = $config; $self->update_optgroup; } +# Sets the options listed in the Add button. sub set_opt_keys { my ($self, $opt_keys) = @_; @@ -85,6 +88,7 @@ sub set_opt_keys { $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; } +# Sets the options that user can't remove. sub set_fixed_options { my ($self, $opt_keys) = @_; $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; @@ -97,6 +101,8 @@ sub update_optgroup { $self->{options_sizer}->Clear(1); return if !defined $self->{config}; + $self->{btn_add}->Show($self->{editable}); + my %categories = (); foreach my $opt_key (@{$self->{config}->get_keys}) { my $category = $Slic3r::Config::Options->{$opt_key}{category}; @@ -104,7 +110,8 @@ sub update_optgroup { push @{$categories{$category}}, $opt_key; } foreach my $category (sort keys %categories) { - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( + my $optgroup; + $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => $category, config => $self->{config}, @@ -116,10 +123,11 @@ sub update_optgroup { extra_column => sub { my ($line) = @_; - my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line + my $opt_id = $line->get_options->[0]->opt_id; # we assume that we have one option per line + my ($opt_key, $opt_index) = @{ $optgroup->_opt_map->{$opt_id} }; # disallow deleting fixed options - return undef if $self->{fixed_options}{$opt_key}; + return undef if $self->{fixed_options}{$opt_key} || !$self->{editable}; my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); @@ -132,7 +140,9 @@ sub update_optgroup { }, ); foreach my $opt_key (sort @{$categories{$category}}) { - $optgroup->append_single_option_line($opt_key); + # For array options we override the first value. + my $opt_index = (ref($self->{config}->get($opt_key)) eq 'ARRAY') ? 0 : -1; + $optgroup->append_single_option_line($opt_key, $opt_index); } $self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0); } @@ -154,4 +164,11 @@ sub disable { $self->Disable; } +# Shows or hides the Add button. +sub set_editable { + my ($self, $editable) = @_; + + $self->{editable} = $editable; +} + 1; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 91465fa64..c6f2f1d13 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -20,16 +20,6 @@ sub new { }, label_width => 200, ); - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'mode', - type => 'select', - label => 'Mode', - tooltip => 'Choose between a simpler, basic mode and an expert mode with more options and more complicated interface.', - labels => ['Simple','Expert'], - values => ['simple','expert'], - default => $Slic3r::GUI::Settings->{_}{mode}, - width => 100, - )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'version_check', type => 'bool', diff --git a/lib/Slic3r/GUI/Preset.pm b/lib/Slic3r/GUI/Preset.pm new file mode 100644 index 000000000..35208f585 --- /dev/null +++ b/lib/Slic3r/GUI/Preset.pm @@ -0,0 +1,52 @@ +package Slic3r::GUI::Preset; +use Moo; +use Unicode::Normalize; + +has 'default' => (is => 'ro', default => sub { 0 }); +has 'external' => (is => 'ro', default => sub { 0 }); +has 'name' => (is => 'rw', required => 1); +has 'file' => (is => 'rw'); + +sub BUILD { + my ($self) = @_; + + $self->name(Unicode::Normalize::NFC($self->name)); +} + +sub config { + my ($self, $keys, $extra_keys) = @_; + + if ($self->default) { + return Slic3r::Config->new_from_defaults(@$keys); + } else { + if (!-e Slic3r::encode_path($self->file)) { + Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); + return undef; + } + my $external_config = $self->load_config; + if (!$keys) { + return $external_config; + } else { + # apply preset values on top of defaults + my $config = Slic3r::Config->new_from_defaults(@$keys); + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @$keys; + + # For extra_keys we don't populate defaults. + if ($extra_keys) { + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @$extra_keys; + } + + return $config; + } + } +} + +sub load_config { + my ($self) = @_; + + return Slic3r::Config->load($self->file); +} + +1; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/PresetEditor.pm similarity index 85% rename from lib/Slic3r/GUI/Tab.pm rename to lib/Slic3r/GUI/PresetEditor.pm index 1b61e3f38..da70a1840 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -1,7 +1,4 @@ -# The "Expert" tab at the right of the main tabbed window. -# The "Expert" is enabled by File->Preferences dialog. - -package Slic3r::GUI::Tab; +package Slic3r::GUI::PresetEditor; use strict; use warnings; use utf8; @@ -22,7 +19,7 @@ sub new { # horizontal sizer $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); - $self->{sizer}->SetSizeHints($self); + #$self->{sizer}->SetSizeHints($self); $self->SetSizer($self->{sizer}); # left vertical sizer @@ -89,7 +86,6 @@ sub new { EVT_CHOICE($parent, $self->{presets_choice}, sub { $self->on_select_preset; - $self->_on_presets_changed; }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); @@ -106,12 +102,15 @@ sub new { $self->{presets_choice}->Delete($i); $self->current_preset(undef); $self->select_preset(0); - $self->_on_presets_changed; }); # C++ instance DynamicPrintConfig $self->{config} = Slic3r::Config->new; # Initialize the DynamicPrintConfig by default keys/values. + $self->{config}->apply(Slic3r::Config->new_from_defaults($self->options)); + + + # Possible %params keys: no_controller $self->build(%params); $self->update_tree; @@ -128,6 +127,12 @@ sub get_current_preset { return $self->get_preset($self->current_preset); } +sub get_current_preset_config { + my ($self) = @_; + + return $self->get_preset_config($self->get_current_preset); +} + sub get_preset { my ($self, $i) = @_; return $self->{presets}[$i]; @@ -151,14 +156,15 @@ sub save_preset { default => $default_name, values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); - return unless $dlg->ShowModal == wxID_OK; + return 0 unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); $self->load_presets; $self->select_preset_by_name($name); - $self->_on_presets_changed; + + return 1; } sub on_value_change { @@ -166,11 +172,6 @@ sub on_value_change { $self->{on_value_change} = $cb; } -sub on_presets_changed { - my ($self, $cb) = @_; - $self->{on_presets_changed} = $cb; -} - # This method is supposed to be called whenever new values are loaded # or changed by user (so also when a preset is loaded). # propagate event to the parent @@ -183,40 +184,27 @@ sub _on_value_change { sub _update {} -sub _on_presets_changed { - my $self = shift; - - $self->{on_presets_changed}->( - $self->{presets}, - scalar($self->{presets_choice}->GetSelection), - $self->is_dirty, - ) if $self->{on_presets_changed}; -} - sub on_preset_loaded {} sub hidden_options {} sub config { $_[0]->{config}->clone } -sub select_default_preset { - my $self = shift; - $self->select_preset(0); -} - sub select_preset { - my $self = shift; - $self->{presets_choice}->SetSelection($_[0]); - $self->on_select_preset; + my ($self, $i, $force) = @_; + + $self->{presets_choice}->SetSelection($i); + $self->on_select_preset($force); } sub select_preset_by_name { - my ($self, $name) = @_; + my ($self, $name, $force) = @_; - $name = Unicode::Normalize::NFC($name); - $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); + my $i = first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}; + return if !defined $i; + $self->select_preset($i, $force); } -sub on_select_preset { - my $self = shift; +sub prompt_unsaved_changes { + my ($self) = @_; if ($self->is_dirty) { my $old_preset = $self->get_current_preset; @@ -233,27 +221,39 @@ sub on_select_preset { } my $changes = join "\n", map "- $_", @option_names; - my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", - 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if ($confirm->ShowModal == wxID_NO) { - $self->{presets_choice}->SetSelection($self->current_preset); - - # trigger the on_presets_changed event so that we also restore the previous value - # in the plater selector - $self->_on_presets_changed; - return; + my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDo you want to save them?", + 'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION); + $confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel'); + my $res = $confirm->ShowModal; + + if ($res == wxID_CANCEL) { + return 0; + } elsif ($res == wxID_YES) { + return $self->save_preset($old_preset->default ? undef : $old_preset->name); } } - $self->current_preset($self->{presets_choice}->GetSelection); + return 1; +} + +sub on_select_preset { + my ($self, $force) = @_; + + my $i = $self->{presets_choice}->GetSelection; + return if defined $self->current_preset && $i == $self->current_preset; + + if (!$force && defined $self->current_preset && !$self->prompt_unsaved_changes) { + $self->{presets_choice}->SetSelection($self->current_preset); + return; + } + + $self->current_preset($i); my $preset = $self->get_current_preset; my $preset_config = $self->get_preset_config($preset); eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - foreach my $opt_key (@{$self->{config}->get_keys}) { - $self->{config}->set($opt_key, $preset_config->get($opt_key)) - if $preset_config->has($opt_key); - } + $self->{config}->clear; + $self->{config}->apply($preset_config); ($preset->default || $preset->external) ? $self->{btn_delete_preset}->Disable : $self->{btn_delete_preset}->Enable; @@ -261,12 +261,10 @@ sub on_select_preset { $self->_update; $self->on_preset_loaded; $self->reload_config; - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; }; if ($@) { $@ = "I was unable to load the selected config file: $@"; Slic3r::GUI::catch_error($self); - $self->select_default_preset; } # use CallAfter because some field triggers schedule on_change calls using CallAfter, @@ -274,16 +272,8 @@ sub on_select_preset { # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { - $self->_on_presets_changed; $self->update_dirty; }); - - wxTheApp->save_settings; -} - -sub init_config_options { - my ($self, @opt_keys) = @_; - $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); } sub add_options_page { @@ -296,7 +286,7 @@ sub add_options_page { $self->{iconcount}++; } - my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); + my $page = Slic3r::GUI::PresetEditor::Page->new($self, $title, $self->{iconcount}); $page->Hide; $self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; @@ -345,7 +335,6 @@ sub update_dirty { } } $self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769 - $self->_on_presets_changed; } sub is_dirty { @@ -357,36 +346,23 @@ sub dirty_options { my $self = shift; return [] if !defined $self->current_preset; # happens during initialization - return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); + + my $preset_config = $self->get_current_preset_config; + return [ + (grep !$preset_config->has($_), @{$self->{config}->get_keys}), + (grep !$self->{config}->has($_), @{$preset_config->get_keys}), + @{ $self->{config}->diff($self->get_current_preset_config) }, + ]; } sub load_presets { my $self = shift; - $self->{presets} = [ - Slic3r::GUI::Tab::Preset->new( - default => 1, - name => '- default -', - ), - ]; - - my %presets = wxTheApp->presets($self->name); - foreach my $preset_name (sort keys %presets) { - push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( - name => $preset_name, - file => $presets{$preset_name}, - ); - } - + $self->{presets} = [ wxTheApp->presets($self->name, 1) ]; $self->current_preset(undef); $self->{presets_choice}->Clear; $self->{presets_choice}->Append($_->name) for @{$self->{presets}}; - { - # load last used preset - my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; - $self->select_preset($i || 0); - } - $self->_on_presets_changed; + $self->select_preset(0); } sub load_config_file { @@ -397,7 +373,7 @@ sub load_config_file { my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}}; if (!$i) { my $preset_name = basename($file); # keep the .ini suffix - push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( + push @{$self->{presets}}, Slic3r::GUI::Preset->new( file => $file, name => $preset_name, external => 1, @@ -407,7 +383,6 @@ sub load_config_file { } $self->{presets_choice}->SetSelection($i); $self->on_select_preset; - $self->_on_presets_changed; } sub load_config { @@ -424,7 +399,11 @@ sub load_config { sub get_preset_config { my ($self, $preset) = @_; - return $preset->config($self->{config}->get_keys); + + return $preset->config( + [ $self->options ], + [ $self->overriding_options ], + ); } sub get_field { @@ -448,19 +427,64 @@ sub set_value { return $changed; } -package Slic3r::GUI::Tab::Print; -use base 'Slic3r::GUI::Tab'; +sub _compatible_printers_widget { + my ($self) = @_; + + return sub { + my ($parent) = @_; + + my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG)); + } + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($btn); + + EVT_BUTTON($self, $btn, sub { + my @presets = map $_->name, grep !$_->default && !$_->external, + wxTheApp->presets('printer'); + + my $dlg = Wx::MultiChoiceDialog->new($self, + "Select the printers this profile is compatible with.\nIf none are selected, it will be considered compatible with all of them.", + "Compatible printers", \@presets); + + my @selections = (); + foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { + push @selections, first { $presets[$_] eq $preset_name } 0..$#presets; + } + $dlg->SetSelections(@selections); + + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @presets[$dlg->GetSelections] ]; + $self->{config}->set('compatible_printers', $value); + $self->update_dirty; + $self->_on_value_change('compatible_printers', $value); + } + }); + + return $sizer; + }; +} + +sub options { die "Unimplemented options()"; } +sub overridable_options { () } +sub overriding_options { () } + +package Slic3r::GUI::PresetEditor::Print; +use base 'Slic3r::GUI::PresetEditor'; use List::Util qw(first any); -use Wx qw(:icon :dialog :id); +use Wx qw(:icon :dialog :id :misc :button :sizer); +use Wx::Event qw(EVT_BUTTON); sub name { 'print' } sub title { 'Print Settings' } -sub build { - my $self = shift; - - $self->init_config_options(qw( +sub options { + return qw( layer_height first_layer_height perimeters spiral_vase top_solid_layers bottom_solid_layers @@ -497,9 +521,54 @@ sub build { external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width top_infill_extrusion_width support_material_extrusion_width infill_overlap bridge_flow_ratio - xy_size_compensation threads resolution - )); - $self->{config}->set('print_settings_id', ''); + xy_size_compensation threads resolution overridable compatible_printers + print_settings_id + ) +} + +sub build { + my $self = shift; + + my $overridable_widget = sub { + my ($parent) = @_; + + my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + } + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($btn); + + EVT_BUTTON($self, $btn, sub { + my %options = ( + map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } + map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new + ); + my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options; + + my $dlg = Wx::MultiChoiceDialog->new($self, "Selected options will be displayed in the plater screen for quick changes.", + "Overridable options", + [ map $options{$_}, @opt_keys ]); + + my @selections = (); + foreach my $opt_key (@{ $self->{config}->get('overridable') }) { + push @selections, first { $opt_keys[$_] eq $opt_key } 0..$#opt_keys; + } + $dlg->SetSelections(@selections); + + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @opt_keys[$dlg->GetSelections] ]; + $self->{config}->set('overridable', $value); + $self->update_dirty; + $self->_on_value_change('overridable', $value); + } + }); + + return $sizer; + }; { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); @@ -643,7 +712,7 @@ sub build { } { - my $page = $self->add_options_page('Multiple Extruders', 'funnel.png'); + my $page = $self->add_options_page('Multiple extruders', 'funnel.png'); { my $optgroup = $page->new_optgroup('Extruders'); $optgroup->append_single_option_line('perimeter_extruder'); @@ -728,7 +797,7 @@ sub build { } { - my $page = $self->add_options_page('Notes', 'note.png'); + my $page = $self->add_options_page('Notes and preferences', 'note.png'); { my $optgroup = $page->new_optgroup('Notes', label_width => 0, @@ -738,6 +807,23 @@ sub build { $option->height(250); $optgroup->append_single_option_line($option); } + { + my $optgroup = $page->new_optgroup('Profile preferences'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Overridable settings', + widget => $overridable_widget, + ); + $optgroup->append_line($line); + } + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } } } @@ -890,24 +976,34 @@ sub _update { sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } -package Slic3r::GUI::Tab::Filament; -use base 'Slic3r::GUI::Tab'; +package Slic3r::GUI::PresetEditor::Filament; +use base 'Slic3r::GUI::PresetEditor'; + +use Wx qw(wxTheApp); sub name { 'filament' } sub title { 'Filament Settings' } -sub build { - my $self = shift; - - $self->init_config_options(qw( +sub options { + return qw( filament_colour filament_diameter 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 + fan_always_on cooling compatible_printers 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 - )); - $self->{config}->set('filament_settings_id', ''); + filament_settings_id + ); +} + +sub overriding_options { + return ( + Slic3r::GUI::PresetEditor::Printer->overridable_options, + ); +} + +sub build { + my $self = shift; { my $page = $self->add_options_page('Filament', 'spool.png'); @@ -987,26 +1083,6 @@ sub build { } } - { - my $page = $self->add_options_page('Advanced', 'wrench.png'); - { - my $optgroup = $page->new_optgroup('Print speed override'); - $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); - } - } - - { - my $page = $self->add_options_page('Notes', 'note.png'); - { - my $optgroup = $page->new_optgroup('Notes', - label_width => 0, - ); - my $option = $optgroup->get_option('filament_notes', 0); - $option->full_width(1); - $option->height(250); - $optgroup->append_single_option_line($option); - } - } { my $page = $self->add_options_page('Custom G-code', 'cog.png'); { @@ -1028,6 +1104,81 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Overrides', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile preferences'); + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + { + my $optgroup = $page->new_optgroup('Overrides'); + $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + + # Populate the overrides config. + my @overridable = $self->overriding_options; + $self->{overrides_config} = Slic3r::Config->new; + + # Populate the defaults with the current preset. + $self->{overrides_default_config} = Slic3r::Config->new; + $self->{overrides_default_config}->apply_only + (wxTheApp->{mainframe}->{plater}->config, \@overridable); + + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => '', + full_width => 1, + widget => sub { + my ($parent) = @_; + + $self->{overrides_panel} = my $panel = Slic3r::GUI::Plater::OverrideSettingsPanel->new($parent, + size => [-1, 300], + on_change => sub { + $self->{config}->erase($_) for @overridable; + $self->{config}->apply($self->{overrides_config}); + $self->update_dirty; + }); + $panel->set_editable(1); + $panel->set_default_config($self->{overrides_default_config}); + $panel->set_config($self->{overrides_config}); + $panel->set_opt_keys([@overridable]); + + return $panel; + }, + ); + $optgroup->append_line($line); + } + } + + { + my $page = $self->add_options_page('Notes', 'note.png'); + { + my $optgroup = $page->new_optgroup('Notes', + label_width => 0, + ); + my $option = $optgroup->get_option('filament_notes', 0); + $option->full_width(1); + $option->height(250); + $optgroup->append_single_option_line($option); + } + } +} + +sub reload_config { + my ($self) = @_; + + $self->{overrides_config}->clear; + foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) { + if ($self->{config}->has($opt_key)) { + $self->{overrides_config}->set($opt_key, $self->{config}->get($opt_key)); + } + } + $self->{overrides_panel}->update_optgroup; + + $self->SUPER::reload_config; } sub _update { @@ -1071,19 +1222,16 @@ sub _update_description { $self->{description_line}->SetText($msg); } -package Slic3r::GUI::Tab::Printer; -use base 'Slic3r::GUI::Tab'; +package Slic3r::GUI::PresetEditor::Printer; +use base 'Slic3r::GUI::PresetEditor'; use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog); use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } sub title { 'Printer Settings' } -sub build { - my $self = shift; - my (%params) = @_; - - $self->init_config_options(qw( +sub options { + return qw( bed_shape z_offset z_steps_per_mm has_heatbed gcode_flavor use_relative_e_distances serial_port serial_speed @@ -1093,9 +1241,22 @@ sub build { start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe - retract_length_toolchange retract_restart_extra_toolchange - )); - $self->{config}->set('printer_settings_id', ''); + retract_length_toolchange retract_restart_extra_toolchange retract_lift_above retract_lift_below + printer_settings_id + ); +} + +sub overridable_options { + return qw( + pressure_advance + retract_length retract_lift retract_speed retract_restart_extra + retract_before_travel retract_layer_change wipe + ); +} + +sub build { + my $self = shift; + my (%params) = @_; $self->{extruders_count} = 1; @@ -1484,7 +1645,7 @@ sub load_config_file { if @{ $self->{config}->nozzle_diameter } > 1; } -package Slic3r::GUI::Tab::Page; +package Slic3r::GUI::PresetEditor::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; @@ -1603,33 +1764,4 @@ sub get_name { return $self->{chosen_name}; } -package Slic3r::GUI::Tab::Preset; -use Moo; - -has 'default' => (is => 'ro', default => sub { 0 }); -has 'external' => (is => 'ro', default => sub { 0 }); -has 'name' => (is => 'rw', required => 1); -has 'file' => (is => 'rw'); - -sub config { - my ($self, $keys) = @_; - - if ($self->default) { - return Slic3r::Config->new_from_defaults(@$keys); - } else { - if (!-e Slic3r::encode_path($self->file)) { - Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); - return undef; - } - - # apply preset values on top of defaults - my $config = Slic3r::Config->new_from_defaults(@$keys); - my $external_config = Slic3r::Config->load($self->file); - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$keys; - - return $config; - } -} - 1; diff --git a/lib/Slic3r/GUI/PresetEditorDialog.pm b/lib/Slic3r/GUI/PresetEditorDialog.pm new file mode 100644 index 000000000..349f1e871 --- /dev/null +++ b/lib/Slic3r/GUI/PresetEditorDialog.pm @@ -0,0 +1,65 @@ +package Slic3r::GUI::PresetEditorDialog; +use strict; +use warnings; +use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp); +use Wx::Event qw(EVT_CLOSE); +use base qw(Wx::Dialog Class::Accessor); +use utf8; + +__PACKAGE__->mk_accessors(qw(preset_editor)); + +sub new { + my ($class, $parent) = @_; + my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [800,500], + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxDIALOG_EX_METAL); + + $self->preset_editor($self->preset_editor_class->new($self)); + $self->SetTitle($self->preset_editor->title); + $self->preset_editor->load_presets; + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add($self->preset_editor, 1, wxEXPAND); + + $self->SetSizer($sizer); + #$sizer->SetSizeHints($self); + + if (0) { + # This does not call the EVT_CLOSE below + my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE); + $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + } + + EVT_CLOSE($self, sub { + my (undef, $event) = @_; + + if ($event->CanVeto && !$self->preset_editor->prompt_unsaved_changes) { + $event->Veto; + return; + } + + # propagate event + $event->Skip; + }); + + return $self; +} + + +package Slic3r::GUI::PresetEditorDialog::Printer; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Printer" } + + +package Slic3r::GUI::PresetEditorDialog::Filament; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Filament" } + + +package Slic3r::GUI::PresetEditorDialog::Print; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Print" } + +1; diff --git a/lib/Slic3r/GUI/Projector.pm b/lib/Slic3r/GUI/Projector.pm index 9023cb0c7..c41b7e8ad 100644 --- a/lib/Slic3r/GUI/Projector.pm +++ b/lib/Slic3r/GUI/Projector.pm @@ -44,7 +44,7 @@ sub new { $self->config(Slic3r::Config->new_from_defaults( qw(serial_port serial_speed bed_shape start_gcode end_gcode z_offset) )); - $self->config->apply(wxTheApp->{mainframe}->config); + $self->config->apply(wxTheApp->{mainframe}->{plater}->config); my @optgroups = (); { @@ -520,9 +520,6 @@ sub _close { } wxTheApp->{mainframe}->Show; - my $printer_tab = wxTheApp->{mainframe}{options_tabs}{printer}; - $printer_tab->load_config($self->config); - $self->EndModal(wxID_OK); } @@ -555,7 +552,7 @@ sub BUILD { # init print { my $print = Slic3r::SLAPrint->new(wxTheApp->{mainframe}->{plater}->{model}); - $print->apply_config(wxTheApp->{mainframe}->config); + $print->apply_config(wxTheApp->{mainframe}->{plater}->config); $self->_print($print); $self->screen->print($print); diff --git a/lib/Slic3r/GUI/SLAPrintOptions.pm b/lib/Slic3r/GUI/SLAPrintOptions.pm index 7b3348823..042ddc288 100644 --- a/lib/Slic3r/GUI/SLAPrintOptions.pm +++ b/lib/Slic3r/GUI/SLAPrintOptions.pm @@ -10,7 +10,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, "SLA/DLP Print", wxDefaultPosition, wxDefaultSize); $self->config(Slic3r::Config::SLAPrint->new); - $self->config->apply_dynamic(wxTheApp->{mainframe}->config); + $self->config->apply_dynamic(wxTheApp->{mainframe}->{plater}->config); my $sizer = Wx::BoxSizer->new(wxVERTICAL); my $new_optgroup = sub { @@ -103,8 +103,6 @@ sub _accept { return; } - wxTheApp->{mainframe}->load_config($self->config->dynamic); - $self->EndModal(wxID_OK); $self->Close; # needed on Linux @@ -113,6 +111,9 @@ sub _accept { # this double invocation is needed for properly hiding the MainFrame $projector->Show; $projector->ShowModal; + + # TODO: diff the new config with the selected presets and prompt the user for + # applying the changes to them. } 1; diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm deleted file mode 100644 index 6e7d00d09..000000000 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ /dev/null @@ -1,351 +0,0 @@ -# The "Simple" Print Settings tab. -# The "Simple" mode is enabled by File->Preferences dialog. - -package Slic3r::GUI::SimpleTab; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename); -use List::Util qw(first); -use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings); -use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN); -use base 'Wx::ScrolledWindow'; - -sub new { - my $class = shift; - my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - - $self->SetScrollbars(1, 1, 1, 1); - - $self->{config} = Slic3r::Config->new; - $self->{optgroups} = []; - - $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); - $self->SetSizer($self->{vsizer}); - $self->build; - $self->_update; - - { - my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize); - $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); - $self->{vsizer}->Add($label, 0, wxEXPAND | wxALL, 10); - } - - return $self; -} - -sub init_config_options { - my ($self, @opt_keys) = @_; - $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); -} - -sub new_optgroup { - my ($self, $title, %params) = @_; - - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( - parent => $self, - title => $title, - config => $self->{config}, - label_width => $params{label_width} // 200, - on_change => sub { $self->_on_value_change(@_) }, - ); - - push @{$self->{optgroups}}, $optgroup; - $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); - - return $optgroup; -} - -sub load_config_file { - my $self = shift; - my ($file) = @_; - - my $config = Slic3r::Config->load($file); - $self->load_config($config); -} - -sub load_config { - my $self = shift; - my ($config) = @_; - - foreach my $opt_key (@{$self->{config}->get_keys}) { - next unless $config->has($opt_key); - $self->{config}->set($opt_key, $config->get($opt_key)); - } - $_->reload_config for @{$self->{optgroups}}; - $self->_update; -} - -sub load_presets {} - -sub is_dirty { 0 } -sub config { $_[0]->{config}->clone } -sub _update {} - -sub on_value_change { - my ($self, $cb) = @_; - $self->{on_value_change} = $cb; -} - -sub on_presets_changed {} - -# propagate event to the parent -sub _on_value_change { - my $self = shift; - - $self->{on_value_change}->(@_) if $self->{on_value_change}; - $self->_update; -} - -sub get_field { - my ($self, $opt_key, $opt_index) = @_; - - foreach my $optgroup (@{ $self->{optgroups} }) { - my $field = $optgroup->get_fieldc($opt_key, $opt_index); - return $field if defined $field; - } - return undef; -} - -package Slic3r::GUI::SimpleTab::Print; -use base 'Slic3r::GUI::SimpleTab'; - -use Wx qw(:sizer); - -sub name { 'print' } -sub title { 'Print Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - layer_height perimeters top_solid_layers bottom_solid_layers - fill_density fill_pattern top_infill_pattern bottom_infill_pattern - support_material support_material_spacing raft_layers - support_material_contact_distance dont_support_bridges - perimeter_speed infill_speed travel_speed - brim_width - xy_size_compensation - )); - - { - my $optgroup = $self->new_optgroup('General'); - $optgroup->append_single_option_line('layer_height'); - $optgroup->append_single_option_line('perimeters'); - - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Solid layers', - ); - $line->append_option($optgroup->get_option('top_solid_layers')); - $line->append_option($optgroup->get_option('bottom_solid_layers')); - $optgroup->append_line($line); - } - - { - my $optgroup = $self->new_optgroup('Infill'); - $optgroup->append_single_option_line('fill_density'); - $optgroup->append_single_option_line('fill_pattern'); - $optgroup->append_single_option_line('top_infill_pattern'); - $optgroup->append_single_option_line('bottom_infill_pattern'); - } - - { - my $optgroup = $self->new_optgroup('Support material'); - $optgroup->append_single_option_line('support_material'); - $optgroup->append_single_option_line('support_material_spacing'); - $optgroup->append_single_option_line('support_material_contact_distance'); - $optgroup->append_single_option_line('dont_support_bridges'); - $optgroup->append_single_option_line('raft_layers'); - } - - { - my $optgroup = $self->new_optgroup('Speed'); - $optgroup->append_single_option_line('perimeter_speed'); - $optgroup->append_single_option_line('infill_speed'); - $optgroup->append_single_option_line('travel_speed'); - } - - { - my $optgroup = $self->new_optgroup('Brim'); - $optgroup->append_single_option_line('brim_width'); - } - - { - my $optgroup = $self->new_optgroup('Other'); - $optgroup->append_single_option_line('xy_size_compensation'); - } -} - -sub _update { - my ($self) = @_; - - my $config = $self->{config}; - - my $have_perimeters = $config->perimeters > 0; - $self->get_field($_)->toggle($have_perimeters) - for qw(perimeter_speed); - - my $have_infill = $config->fill_density > 0; - my $have_solid_infill = $config->top_solid_layers > 0 || $config->bottom_solid_layers > 0; - $self->get_field($_)->toggle($have_infill) - for qw(fill_pattern); - $self->get_field($_)->toggle($have_solid_infill) - for qw(top_infill_pattern bottom_infill_pattern); - $self->get_field($_)->toggle($have_infill || $have_solid_infill) - for qw(infill_speed); - - my $have_support_material = $config->support_material || $config->raft_layers > 0; - $self->get_field($_)->toggle($have_support_material) - for qw(support_material_spacing dont_support_bridges - support_material_contact_distance); -} - -package Slic3r::GUI::SimpleTab::Filament; -use base 'Slic3r::GUI::SimpleTab'; - -sub name { 'filament' } -sub title { 'Filament Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - filament_diameter extrusion_multiplier - temperature first_layer_temperature bed_temperature first_layer_bed_temperature - )); - - { - my $optgroup = $self->new_optgroup('Filament'); - $optgroup->append_single_option_line('filament_diameter', 0); - $optgroup->append_single_option_line('extrusion_multiplier', 0); - } - - { - my $optgroup = $self->new_optgroup('Temperature (°C)'); - - { - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Extruder', - ); - $line->append_option($optgroup->get_option('first_layer_temperature', 0)); - $line->append_option($optgroup->get_option('temperature', 0)); - $optgroup->append_line($line); - } - - { - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Bed', - ); - $line->append_option($optgroup->get_option('first_layer_bed_temperature')); - $line->append_option($optgroup->get_option('bed_temperature')); - $optgroup->append_line($line); - } - } -} - -package Slic3r::GUI::SimpleTab::Printer; -use base 'Slic3r::GUI::SimpleTab'; -use Wx qw(:sizer :button :bitmap :misc :id); -use Wx::Event qw(EVT_BUTTON); - -sub name { 'printer' } -sub title { 'Printer Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - bed_shape - z_offset - gcode_flavor - nozzle_diameter - retract_length retract_lift wipe - start_gcode - end_gcode - )); - - { - my $bed_shape_widget = sub { - my ($parent) = @_; - - my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - $btn->SetFont($Slic3r::GUI::small_font); - if ($Slic3r::GUI::have_button_icons) { - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); - } - - my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($btn); - - EVT_BUTTON($self, $btn, sub { - my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue; - $self->{config}->set('bed_shape', $value); - $self->_on_value_change('bed_shape', $value); - } - }); - - return $sizer; - }; - - my $optgroup = $self->new_optgroup('Size and coordinates'); - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Bed shape', - widget => $bed_shape_widget, - ); - $optgroup->append_line($line); - $optgroup->append_single_option_line('z_offset'); - } - - { - my $optgroup = $self->new_optgroup('Firmware'); - $optgroup->append_single_option_line('gcode_flavor'); - } - - { - my $optgroup = $self->new_optgroup('Extruder'); - $optgroup->append_single_option_line('nozzle_diameter', 0); - } - - { - my $optgroup = $self->new_optgroup('Retraction'); - $optgroup->append_single_option_line('retract_length', 0); - $optgroup->append_single_option_line('retract_lift', 0); - $optgroup->append_single_option_line('wipe', 0); - } - - { - my $optgroup = $self->new_optgroup('Start G-code', - label_width => 0, - ); - my $option = $optgroup->get_option('start_gcode'); - $option->full_width(1); - $option->height(150); - $optgroup->append_single_option_line($option); - } - - { - my $optgroup = $self->new_optgroup('End G-code', - label_width => 0, - ); - my $option = $optgroup->get_option('end_gcode'); - $option->full_width(1); - $option->height(150); - $optgroup->append_single_option_line($option); - } -} - -sub _update { - my ($self) = @_; - - my $config = $self->{config}; - - my $have_retraction = $config->retract_length->[0] > 0; - $self->get_field($_, 0)->toggle($have_retraction) - for qw(retract_lift wipe); -} - -1; diff --git a/slic3r.pl b/slic3r.pl index d4ebd5c48..3aa3292ba 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -35,8 +35,6 @@ my %cli_options = (); 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-controller' => \$opt{no_controller}, - 'no-plater' => \$opt{no_plater}, - 'gui-mode=s' => \$opt{gui_mode}, 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, @@ -77,28 +75,26 @@ if ($opt{load}) { $opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n"; } } + + # expand shortcuts before applying, otherwise destination values would be already filled with defaults + $_->normalize for @external_configs; } # process command line options -my $cli_config = Slic3r::Config->new; -foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) { - $c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults - $cli_config->apply($c); -} +my $cli_config = Slic3r::Config->new_from_cli(%cli_options); +$cli_config->normalize; # expand shortcuts # save configuration if ($opt{save}) { - if (@{$cli_config->get_keys} > 0) { - $cli_config->save($opt{save}); + my $config = $cli_config->clone; + $config->apply($_) for @external_configs; + if (@{$config->get_keys} > 0) { + $config->save($opt{save}); } else { Slic3r::Config->new_from_defaults->save($opt{save}); } } -# apply command line config on top of default config -my $config = Slic3r::Config->new_from_defaults; -$config->apply($cli_config); - # launch GUI my $gui; if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { @@ -106,9 +102,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { no warnings 'once'; $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); $Slic3r::GUI::no_controller = $opt{no_controller}; - $Slic3r::GUI::no_plater = $opt{no_plater}; - $Slic3r::GUI::mode = $opt{gui_mode}; - $Slic3r::GUI::autosave = $opt{autosave}; + $Slic3r::GUI::autosave = Slic3r::decode_path($opt{autosave} // ''); } $gui = Slic3r::GUI->new; setlocale(LC_NUMERIC, 'C'); @@ -117,7 +111,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { $gui->{mainframe}->load_config($cli_config); foreach my $input_file (@ARGV) { $input_file = Slic3r::decode_path($input_file); - $gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater}; + $gui->{mainframe}{plater}->load_file($input_file); } }); $gui->MainLoop; @@ -126,6 +120,9 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line + # apply command line config on top of default config + my $config = Slic3r::Config->new_from_defaults; + $config->apply($cli_config); $config->validate; if ($opt{repair}) { @@ -327,7 +324,6 @@ $j --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab - --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index ccb357cf2..d5a67702a 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -241,21 +241,24 @@ ConfigBase::has(const t_config_option_key &opt_key) { void ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) { - // get list of option keys to apply - t_config_option_keys opt_keys = other.keys(); - + // apply all options + this->apply_only(other, other.keys(), ignore_nonexistent); +} + +void +ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent) { // loop through options and apply them - for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { - ConfigOption* my_opt = this->option(*it, true); + for (const t_config_option_key &opt_key : opt_keys) { + ConfigOption* my_opt = this->option(opt_key, true); if (my_opt == NULL) { if (ignore_nonexistent == false) throw "Attempt to apply non-existent option"; continue; } // not the most efficient way, but easier than casting pointers to subclasses - bool res = my_opt->deserialize( other.option(*it)->serialize() ); + bool res = my_opt->deserialize( other.option(opt_key)->serialize() ); if (!res) { - std::string error = "Unexpected failure when deserializing serialized value for " + *it; + std::string error = "Unexpected failure when deserializing serialized value for " + opt_key; CONFESS(error.c_str()); } } @@ -500,6 +503,11 @@ DynamicConfig::erase(const t_config_option_key &opt_key) { this->options.erase(opt_key); } +void +DynamicConfig::clear() { + this->options.clear(); +} + void DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra) { diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 8e375701e..908d761a8 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -665,6 +665,7 @@ class ConfigBase virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; virtual t_config_option_keys keys() const = 0; void apply(const ConfigBase &other, bool ignore_nonexistent = false); + void apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent = false); bool equals(ConfigBase &other); t_config_option_keys diff(ConfigBase &other); std::string serialize(const t_config_option_key &opt_key) const; @@ -691,6 +692,7 @@ class DynamicConfig : public virtual ConfigBase virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); t_config_option_keys keys() const; void erase(const t_config_option_key &opt_key); + void clear(); void read_cli(const int argc, const char **argv, t_config_option_keys* extra); private: diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index caeaaf8da..4f0f071de 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -131,6 +131,10 @@ PrintConfigDef::PrintConfigDef() def->cli = "brim-width=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); + + def = this->add("compatible_printers", coStrings); + def->label = "Compatible printers"; + def->default_value = new ConfigOptionStrings(); def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; @@ -784,7 +788,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication."; def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); - + def = this->add("octoprint_host", coString); def->label = "Host or IP"; def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance."; @@ -816,6 +820,14 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); + + def = this->add("overridable", coStrings); + def->label = "Overridable options"; + { + ConfigOptionStrings* opt = new ConfigOptionStrings(); + opt->values.push_back("support_material"); + def->default_value = opt; + } def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; @@ -876,6 +888,7 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 60; + def->default_value = new ConfigOptionStrings(); def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); @@ -885,6 +898,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("pressure_advance", coFloat); def->label = "Pressure advance"; + def->category = "Extruder"; def->tooltip = "When set to a non-zero value, this experimental option enables pressure regulation. It's the K constant for the advance algorithm that pushes more or less filament upon speed changes. It's useful for Bowden-tube extruders. Reasonable values are in range 0-10."; def->cli = "pressure-advance=f"; def->min = 0; @@ -918,6 +932,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_before_travel", coFloats); def->label = "Minimum travel after retraction"; + def->category = "Retraction"; def->tooltip = "Retraction is not triggered when travel moves are shorter than this length."; def->sidetext = "mm"; def->cli = "retract-before-travel=f@"; @@ -929,6 +944,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_layer_change", coBools); def->label = "Retract on layer change"; + def->category = "Retraction"; def->tooltip = "This flag enforces a retraction whenever a Z move is done."; def->cli = "retract-layer-change!"; { @@ -939,6 +955,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_length", coFloats); def->label = "Length"; + def->category = "Retraction"; def->full_label = "Retraction Length"; def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; def->sidetext = "mm (zero to disable)"; @@ -963,6 +980,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift", coFloats); def->label = "Lift Z"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered."; def->sidetext = "mm"; def->cli = "retract-lift=f@"; @@ -975,6 +993,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift_above", coFloats); def->label = "Above Z"; def->full_label = "Only lift Z above"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-above=f@"; @@ -987,6 +1006,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift_below", coFloats); def->label = "Below Z"; def->full_label = "Only lift Z below"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-below=f@"; @@ -998,6 +1018,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_restart_extra", coFloats); def->label = "Extra length on restart"; + def->category = "Retraction"; def->tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed."; def->sidetext = "mm"; def->cli = "retract-restart-extra=f@"; @@ -1020,7 +1041,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_speed", coFloats); def->label = "Speed"; - def->full_label = "Retraction Speed"; + def->category = "Retraction"; def->tooltip = "The speed for retractions (it only applies to the extruder motor). If you use the Firmware Retraction option, please note this value still affects the auto-speed pressure regulator."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; @@ -1479,6 +1500,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe", coBools); def->label = "Wipe while retracting"; + def->category = "Retraction"; def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders."; def->cli = "wipe!"; { diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 96c4830e0..622ed6b01 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -27,14 +27,19 @@ double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; + void apply_only(DynamicPrintConfig* other, std::vector opt_keys) + %code{% THIS->apply_only(*other, opt_keys, true); %}; std::vector diff(DynamicPrintConfig* other) %code{% RETVAL = THIS->diff(*other); %}; + std::vector diff_static(StaticPrintConfig* other) + %code{% RETVAL = THIS->diff(*other); %}; bool equals(DynamicPrintConfig* other) %code{% RETVAL = THIS->equals(*other); %}; void apply_static(StaticPrintConfig* other) %code{% THIS->apply(*other, true); %}; %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); + void clear(); void normalize(); %name{setenv} void setenv_(); double min_object_distance(); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 1bd25edb4..afeb993c1 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -256,6 +256,12 @@ _constant() void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; + bool apply_static_config(StaticPrintConfig* config) + %code%{ + DynamicPrintConfig dpc; + dpc.apply(*config); + RETVAL = THIS->apply_config(dpc); + %}; bool has_infinite_skirt(); bool has_skirt(); std::string _validate()