diff options
-rw-r--r-- | lib/Slic3r.pm | 20 | ||||
-rw-r--r-- | lib/Slic3r/Config.pm | 65 | ||||
-rw-r--r-- | lib/Slic3r/GUI.pm | 156 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller/PrinterPanel.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/MainFrame.pm | 112 | ||||
-rw-r--r-- | lib/Slic3r/GUI/OptionsGroup/Field.pm | 4 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater.pm | 87 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/2D.pm | 7 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Preferences.pm | 20 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Tab.pm | 127 | ||||
-rwxr-xr-x | slic3r.pl | 13 | ||||
-rw-r--r-- | t/threads.t | 7 | ||||
-rw-r--r-- | xs/src/libslic3r/Config.hpp | 6 | ||||
-rw-r--r-- | xs/src/libslic3r/Utils.hpp | 1 | ||||
-rw-r--r-- | xs/src/libslic3r/utils.cpp | 7 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/AppConfig.cpp | 12 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.cpp | 81 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.hpp | 4 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Preset.cpp | 83 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Preset.hpp | 1 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetBundle.cpp | 66 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetBundle.hpp | 9 | ||||
-rw-r--r-- | xs/xsp/GUI.xsp | 3 | ||||
-rw-r--r-- | xs/xsp/GUI_AppConfig.xsp | 18 | ||||
-rw-r--r-- | xs/xsp/GUI_Preset.xsp | 68 |
26 files changed, 469 insertions, 512 deletions
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index dc870edf3..53b2e3663 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -23,25 +23,14 @@ sub debugf { our $loglevel = 0; # load threads before Moo as required by it -our $have_threads; BEGIN { # Test, whether the perl was compiled with ithreads support and ithreads actually work. use Config; - $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; - warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96; - - ### temporarily disable threads if using the broken Moo version use Moo; - $have_threads = 0 if $Moo::VERSION == 1.003000; - - # Disable multi threading completely by an environment value. - # This is useful for debugging as the Perl debugger does not work - # in multi-threaded context at all. - # A good interactive perl debugger is the ActiveState Komodo IDE - # or the EPIC http://www.epic-ide.org/ - $have_threads = 0 if (defined($ENV{'SLIC3R_SINGLETHREADED'}) && $ENV{'SLIC3R_SINGLETHREADED'} == 1); - print "Threading disabled\n" if !$have_threads; - + my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; + die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads; + die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96; + die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000; $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); print "Debugging output enabled\n" if $debug; } @@ -164,6 +153,7 @@ sub thread_cleanup { *Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; + *Slic3r::GUI::AppConfig::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index c5e49df6b..a9c822b96 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -12,14 +12,14 @@ use List::Util qw(first max); # The C++ counterpart is a constant singleton. our $Options = print_config_def(); -# overwrite the hard-coded readonly value (this information is not available in XS) -$Options->{threads}{readonly} = !$Slic3r::have_threads; - -# generate accessors +# Generate accessors. { no strict 'refs'; for my $opt_key (keys %$Options) { - *{$opt_key} = sub { $_[0]->get($opt_key) }; + *{$opt_key} = sub { + #print "Slic3r::Config::accessor $opt_key\n"; + $_[0]->get($opt_key) + }; } } @@ -64,61 +64,6 @@ sub new_from_cli { return $self; } -# CLASS METHODS: - -# Write a "Windows" style ini file with categories enclosed in squre brackets. -# Used by config-bundle-to-config.pl and to save slic3r.ini. -sub write_ini { - my $class = shift; - my ($file, $ini) = @_; - - Slic3r::open(\my $fh, '>', $file); - binmode $fh, ':utf8'; - my $localtime = localtime; - printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime"; - # make sure the _ category is the first one written - foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) { - printf $fh "\n[%s]\n", $category if $category ne '_'; - foreach my $key (sort keys %{$ini->{$category}}) { - printf $fh "%s = %s\n", $key, $ini->{$category}{$key}; - } - } - close $fh; -} - -# Parse a "Windows" style ini file with categories enclosed in squre brackets. -# Returns a hash of hashes over strings. -# {category}{name}=value -# Non-categorized entries are stored under a category '_'. -# Used by config-bundle-to-config.pl and to read slic3r.ini. -sub read_ini { - my $class = shift; - my ($file) = @_; - - local $/ = "\n"; - Slic3r::open(\my $fh, '<', $file) - or die "Unable to open $file: $!\n"; - binmode $fh, ':utf8'; - - my $ini = { _ => {} }; - my $category = '_'; - while (<$fh>) { - s/\R+$//; - next if /^\s+/; - next if /^$/; - next if /^\s*#/; - if (/^\[(.+?)\]$/) { - $category = $1; - next; - } - /^(\w+) *= *(.*)/ or die "Unreadable configuration file (invalid data at line $.)\n"; - $ini->{$category}{$1} = $2; - } - close $fh; - - return $ini; -} - package Slic3r::Config::Static; use parent 'Slic3r::Config'; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index a75b0d83b..baba3baa7 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -53,25 +53,9 @@ use constant FILE_WILDCARDS => { use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)}; # 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 $autosave; our @cb; -our $Settings = { - _ => { - version_check => 1, - autocenter => 1, - # Disable background processing by default as it is not stable. - 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, - # If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. - no_defaults => 1, - }, -}; - our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $small_font->SetPointSize(11) if &Wx::wxMAC; our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); @@ -89,91 +73,47 @@ sub OnInit { Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; $self->{notifier} = Slic3r::GUI::Notifier->new; + $self->{app_config} = Slic3r::GUI::AppConfig->new; $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; # just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory # supplied as argument to --datadir; in that case we should still run the wizard - my $enc_datadir = Slic3r::encode_path(Slic3r::data_dir); - Slic3r::debugf "Data directory: %s\n", $enc_datadir; - my $run_wizard = (-d $enc_datadir && -e "$enc_datadir/slic3r.ini") ? 0 : 1; - foreach my $dir ($enc_datadir, "$enc_datadir/print", "$enc_datadir/filament", "$enc_datadir/printer") { - next if -d $dir; - if (!mkdir $dir) { - my $error = "Slic3r was unable to create its data directory at $dir ($!)."; - warn "$error\n"; - fatal_error(undef, $error); - } + eval { $self->{preset_bundle}->setup_directories() }; + if ($@) { + warn $@ . "\n"; + fatal_error(undef, $@); } - + my $run_wizard = ! $self->{app_config}->exists; # load settings - my $last_version; - if (-f "$enc_datadir/slic3r.ini") { - my $ini = eval { Slic3r::Config->read_ini(Slic3r::data_dir . "/slic3r.ini") }; - $Settings = $ini if $ini; - $last_version = $Settings->{_}{version}; - $Settings->{_}{autocenter} //= 1; - $Settings->{_}{background_processing} //= 1; - # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - $Settings->{_}{no_controller} //= 1; - # If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. - $Settings->{_}{no_defaults} //= 1; - } - $Settings->{_}{version} = $Slic3r::VERSION; - $self->save_settings; + $self->{app_config}->load if ! $run_wizard; + $self->{app_config}->set('version', $Slic3r::VERSION); + $self->{app_config}->save; # Suppress the '- default -' presets. - $self->{preset_bundle}->set_default_suppressed($Slic3r::GUI::Settings->{_}{no_defaults} ? 1 : 0); - eval { $self->{preset_bundle}->load_presets(Slic3r::data_dir) }; + $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); + eval { + $self->{preset_bundle}->load_presets(Slic3r::data_dir); + $self->{preset_bundle}->load_selections($self->{app_config}); + $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; + }; # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( # 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_controller => $self->{app_config}->get('no_controller'), no_plater => $no_plater, ); $self->SetTopWindow($frame); - - # load init bundle - #FIXME this is undocumented and the use case is unclear. -# { -# my @dirs = ($FindBin::Bin); -# if (&Wx::wxMAC) { -# push @dirs, qw(); -# } elsif (&Wx::wxMSW) { -# push @dirs, qw(); -# } -# my $init_bundle = first { -e $_ } map "$_/.init_bundle.ini", @dirs; -# if ($init_bundle) { -# Slic3r::debugf "Loading config bundle from %s\n", $init_bundle; -# $self->{mainframe}->load_configbundle($init_bundle, 1); -# $run_wizard = 0; -# } -# } - - if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) { - # user was running another Slic3r version on this computer - if (!defined $last_version || $last_version =~ /^0\./) { - show_info($self->{mainframe}, "Hello! Support material was improved since the " - . "last version of Slic3r you used. It is strongly recommended to revert " - . "your support material settings to the factory defaults and start from " - . "those. Enjoy and provide feedback!", "Support Material"); - } - if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) { - show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was " - . "added. If the bed coordinates in the plater preview screen look wrong, go " - . "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape"); - } - } if ($run_wizard) { $self->{mainframe}->config_wizard; - eval { $self->{preset_bundle}->load_presets(Slic3r::data_dir) }; } EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } + $self->{app_config}->save if $self->{app_config}->dirty; }); return 1; @@ -265,11 +205,6 @@ sub notify { $self->{notifier}->notify($message); } -sub save_settings { - my ($self) = @_; - Slic3r::Config->write_ini(Slic3r::data_dir . "/slic3r.ini", $Settings); -} - # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings { @@ -277,22 +212,11 @@ sub update_ui_from_settings { $self->{mainframe}->update_ui_from_settings; } -sub output_path { - my ($self, $dir) = @_; - - return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path}) - ? $Settings->{_}{last_output_path} - : $dir; -} - sub open_model { my ($self, $window) = @_; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} - || $Slic3r::GUI::Settings->{recent}{config_directory} - || ''; - - my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', $dir, "", + my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', + $self->{app_config}->get_last_dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; @@ -308,31 +232,6 @@ sub CallAfter { push @cb, $cb; } -sub scan_serial_ports { - my ($self) = @_; - - my @ports = (); - - if ($^O eq 'MSWin32') { - # Windows - if (eval "use Win32::TieRegistry; 1") { - my $ts = Win32::TieRegistry->new("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM", - { Access => 'KEY_READ' }); - if ($ts) { - # when no serial ports are available, the registry key doesn't exist and - # TieRegistry->new returns undef - $ts->Tie(\my %reg); - push @ports, sort values %reg; - } - } - } else { - # UNIX and OS X - push @ports, glob '/dev/{ttyUSB,ttyACM,tty.,cu.,rfcomm}*'; - } - - return grep !/Bluetooth|FireFly/, @ports; -} - sub append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_; @@ -369,25 +268,24 @@ sub set_menu_item_icon { sub save_window_pos { my ($self, $window, $name) = @_; - $Settings->{_}{"${name}_pos"} = join ',', $window->GetScreenPositionXY; - $Settings->{_}{"${name}_size"} = join ',', $window->GetSizeWH; - $Settings->{_}{"${name}_maximized"} = $window->IsMaximized; - $self->save_settings; + $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); + $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); + $self->{app_config}->set("${name}_maximized", $window->IsMaximized); + $self->{app_config}->save; } sub restore_window_pos { my ($self, $window, $name) = @_; - - if (defined $Settings->{_}{"${name}_pos"}) { - my $size = [ split ',', $Settings->{_}{"${name}_size"}, 2 ]; + if ($self->{app_config}->has("${name}_pos")) { + my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; $window->SetSize($size); my $display = Wx::Display->new->GetClientArea(); - my $pos = [ split ',', $Settings->{_}{"${name}_pos"}, 2 ]; + my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { $window->Move($pos); } - $window->Maximize(1) if $Settings->{_}{"${name}_maximized"}; + $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); } } diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index c4a22cace..6f2636d20 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -121,7 +121,7 @@ sub OnActivate { } if (!%active) { # enable printers whose port is available - my %ports = map { $_ => 1 } wxTheApp->scan_serial_ports; + my %ports = map { $_ => 1 } Slic3r::GUI::scan_serial_ports; $active{$_} = 1 for grep exists $ports{$presets{$_}->serial_port}, keys %presets; } diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm index 1bf46c456..794ea4e4e 100644 --- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm +++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm @@ -348,7 +348,7 @@ sub update_serial_ports { my $cb = $self->{serial_port_combobox}; my $current = $cb->GetValue; $cb->Clear; - $cb->Append($_) for wxTheApp->scan_serial_ports; + $cb->Append($_) for Slic3r::GUI::scan_serial_ports; $cb->SetValue($current); } diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index a0a47c547..c7f3f1cf9 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -76,6 +76,9 @@ sub new { } # save window size wxTheApp->save_window_pos($self, "main_frame"); + # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, + # but in rare cases it may not have been called yet. + wxTheApp->{app_config}->save; # propagate event $event->Skip; }); @@ -140,9 +143,11 @@ sub _init_tabpanel { my ($group, $name) = @_; $self->{options_tabs}{$group}->select_preset($name); }); - # load initial config - $self->{plater}->on_config_change(wxTheApp->{preset_bundle}->full_config); + my $full_config = wxTheApp->{preset_bundle}->full_config; + $self->{plater}->on_config_change($full_config); + # Show a correct number of filament fields. + $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); } } @@ -340,14 +345,15 @@ sub quick_slice { my $progress_dialog; eval { # validate configuration - my $config = $self->config; + my $config = wxTheApp->{preset_bundle}->full_config(); $config->validate; # select input file my $input_file; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; if (!$params{reslice}) { - my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', + wxTheApp->{app_config}->get_last_dir, "", + &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -369,8 +375,7 @@ sub quick_slice { $input_file = $qs_last_input_file; } my $input_file_basename = basename($input_file); - $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_skein_dir(dirname($input_file)); my $print_center; { @@ -392,11 +397,9 @@ sub quick_slice { $sprint->apply_config($config); $sprint->set_model($model); - { - my $extra = $self->extra_variables; - $sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; - } - + # Copy the names of active presets into the placeholder parser. + wxTheApp->{preset_bundle}->export_selections_pp($sprint->placeholder_parser); + # select output file my $output_file; if ($params{reslice}) { @@ -405,7 +408,7 @@ sub quick_slice { $output_file = $sprint->output_filepath; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', - wxTheApp->output_path(dirname($output_file)), + wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)), basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; @@ -413,8 +416,7 @@ sub quick_slice { } $output_file = $dlg->GetPath; $qs_last_output_file = $output_file unless $params{export_svg}; - $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_last_output_dir(dirname($output_file)); $dlg->Destroy; } @@ -457,8 +459,9 @@ sub repair_stl { my $input_file; { - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', + wxTheApp->{app_config}->get_last_dir, "", + &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -487,15 +490,6 @@ sub repair_stl { Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); } -# Extra variables for the placeholder parser generating a G-code. -sub extra_variables { - my $self = shift; - my %extra_variables = (); - $extra_variables{"${_}_preset"} = wxTheApp->{preset_bundle}->{$_}->get_current_preset_name - for qw(print filament printer); - return { %extra_variables }; -} - sub export_config { my $self = shift; # Generate a cummulative configuration for the selected print, filaments and printer. @@ -504,15 +498,14 @@ sub export_config { eval { $config->validate; }; Slic3r::GUI::catch_error($self) and return; # Ask user for the file name for the config file. - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = $last_config ? basename($last_config) : "config.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, + my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + $last_config ? basename($last_config) : "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; if (defined $file) { - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; $config->save($file); } @@ -523,9 +516,10 @@ sub load_config_file { my ($self, $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", - 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "config.ini", + 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; @@ -534,49 +528,51 @@ sub load_config_file { # Dont proceed further if the config file cannot be loaded. return if Slic3r::GUI::catch_error($self); $_->load_current_preset for (values %{$self->{options_tabs}}); - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; } sub export_configbundle { - my $self = shift; + my ($self) = @_; + return unless $self->check_unsaved_changes; # validate current configuration in case it's dirty - eval { $self->config->validate; }; + eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; # Ask user for a file name. - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = "Slic3r_config_bundle.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, + my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "Slic3r_config_bundle.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; if (defined $file) { # Export the config bundle. - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; - eval { $self->{presets}->export_configbundle($file); }; + wxTheApp->{app_config}->update_config_dir(dirname($file)); + eval { wxTheApp->{preset_bundle}->export_configbundle($file); }; Slic3r::GUI::catch_error($self) and return; } } +# Loading a config bundle with an external file name used to be used +# to auto-install a config bundle on a fresh user account, +# but that behavior was not documented and likely buggy. sub load_configbundle { - my ($self, $file, $skip_no_id) = @_; - + my ($self, $file) = @_; + return unless $self->check_unsaved_changes; if (!$file) { - 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); + my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "config.ini", + &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; } - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_config_dir(dirname($file)); my $presets_imported = 0; - eval { $presets_imported = $self->{presets}->load_configbundle($file); }; + eval { $presets_imported = wxTheApp->{preset_bundle}->load_configbundle($file); }; Slic3r::GUI::catch_error($self) and return; # Load the currently selected preset into the GUI, update the preset selection box. @@ -597,16 +593,18 @@ sub load_config { } sub config_wizard { - my $self = shift; - + my ($self) = @_; + # Exit wizard if there are unsaved changes and the user cancels the action. return unless $self->check_unsaved_changes; if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { for my $tab (values %{$self->{options_tabs}}) { - # Select the first visible preset. - $tab->select_preset(undef); + # Select the first visible preset, force. + $tab->select_preset(undef, 1); } + # Load the config over the previously selected defaults. $self->load_config($config); for my $tab (values %{$self->{options_tabs}}) { + # Save the settings under a new name, select the name. $tab->save_preset('My Settings'); } } @@ -666,7 +664,7 @@ sub _set_menu_item_icon { # Update the UI based on the current preferences. sub update_ui_from_settings { my ($self) = @_; - $self->{menu_item_reslice_now}->Enable(! $Slic3r::GUI::Settings->{_}{background_processing}); + $self->{menu_item_reslice_now}->Enable(! wxTheApp->{app_config}->get("background_processing")); $self->{plater}->update_ui_from_settings if ($self->{plater}); } diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index 531e13a5d..4ef2ce2ca 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -124,6 +124,10 @@ sub BUILD { }); } +sub get_value { + my ($self) = @_; + return $self->wxWindow->GetValue ? 1 : 0; +} package Slic3r::GUI::OptionsGroup::Field::SpinCtrl; use Moo; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 91329e45e..06adbe525 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -63,12 +63,7 @@ sub new { $self->{print}->set_status_cb(sub { my ($percent, $message) = @_; - - if ($Slic3r::have_threads) { - Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); - } else { - $self->on_progress_event($percent, $message); - } + Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); }); # Initialize preview notebook @@ -119,7 +114,7 @@ sub new { $self->GetFrame->{options_tabs}{print}->load_config($cfg); }); $self->{canvas3D}->set_on_model_update(sub { - if ($Slic3r::GUI::Settings->{_}{background_processing}) { + if (wxTheApp->{app_config}->get("background_processing")) { $self->schedule_background_process; } else { # Hide the print info box, it is no more valid. @@ -330,7 +325,7 @@ sub new { $self->on_process_completed($event->GetData); }); - if ($Slic3r::have_threads) { + { my $timer_id = Wx::NewId(); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); EVT_TIMER($self, $timer_id, sub { @@ -448,7 +443,6 @@ sub new { $self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($self->{"print_info_$field"}, 0); } - } my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -510,13 +504,14 @@ sub _on_select_preset { wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); } if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - wxTheApp->save_settings; wxTheApp->{preset_bundle}->update_platter_filament_ui_colors($idx, $choice); } else { # call GetSelection() in scalar context as it's context-aware $self->{on_select_preset}->($group, $choice->GetStringSelection) if $self->{on_select_preset}; } + # Synchronize config.ini with the current selections. + wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); # get new config and generate on_config_change() event for updating plater and other things $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } @@ -548,8 +543,8 @@ sub GetFrame { sub update_ui_from_settings { my ($self) = @_; - if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! $Slic3r::GUI::Settings->{_}{background_processing})) { - $self->{buttons_sizer}->Show($self->{btn_reslice}, ! $Slic3r::GUI::Settings->{_}{background_processing}); + if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { + $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); $self->{buttons_sizer}->Layout; } } @@ -587,6 +582,8 @@ sub update_presets { $choice_idx += 1; } } + # Synchronize config.ini with the current selections. + wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); } sub add { @@ -655,8 +652,7 @@ sub load_files { } # Note the current directory for the file open dialog. - $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_files->[-1]); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1])); $process_dialog->Destroy; $self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files)); @@ -704,7 +700,7 @@ sub load_model_objects { } # if user turned autocentering off, automatic arranging would disappoint them - if (!$Slic3r::GUI::Settings->{_}{autocenter}) { + if (! wxTheApp->{app_config}->get("autocenter")) { $need_arrange = 0; } @@ -817,7 +813,7 @@ sub increase { # only autoarrange if user has autocentering enabled $self->stop_background_process; - if ($Slic3r::GUI::Settings->{_}{autocenter}) { + if (wxTheApp->{app_config}->get("autocenter")) { $self->arrange; } else { $self->update; @@ -1130,7 +1126,7 @@ sub async_apply_config { # Hide the slicing results if the current slicing status is no more valid. $self->{"print_info_box_show"}->(0) if $invalidated; - if ($Slic3r::GUI::Settings->{_}{background_processing}) { + if (wxTheApp->{app_config}->get("background_processing")) { if ($invalidated) { # kill current thread if any $self->stop_background_process; @@ -1152,7 +1148,6 @@ sub async_apply_config { sub start_background_process { my ($self) = @_; - return if !$Slic3r::have_threads; return if !@{$self->{objects}}; return if $self->{process_thread}; @@ -1171,11 +1166,8 @@ sub start_background_process { return; } - # apply extra variables - { - my $extra = $self->GetFrame->extra_variables; - $self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; - } + # Copy the names of active presets into the placeholder parser. + wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); # start thread @_ = (); @@ -1246,7 +1238,7 @@ sub reslice { # explicitly cancel a previous thread and start a new one. my ($self) = @_; # Don't reslice if export of G-code or sending to OctoPrint is running. - if ($Slic3r::have_threads && ! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { + if (! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { # Stop the background processing threads, stop the async update timer. $self->stop_background_process; # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update. @@ -1289,52 +1281,41 @@ sub export_gcode { $self->{print}->apply_config($config); $self->{print}->validate; }; - if (!$Slic3r::have_threads) { - Slic3r::GUI::catch_error($self) and return; - } + Slic3r::GUI::catch_error($self) and return; # select output file if ($output_file) { $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file); } else { my $default_output_file = $self->{print}->output_filepath($main::opt{output} // ''); - my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)), + my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', + wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; } my $path = $dlg->GetPath; - $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_last_output_dir(dirname($path)); $self->{export_gcode_output_file} = $path; $dlg->Destroy; } $self->statusbar->StartBusy; - if ($Slic3r::have_threads) { - $self->statusbar->SetCancelCallback(sub { - $self->stop_background_process; - $self->statusbar->SetStatusText("Export cancelled"); - $self->{export_gcode_output_file} = undef; - $self->{send_gcode_file} = undef; - - # this updates buttons status - $self->object_list_changed; - }); + $self->statusbar->SetCancelCallback(sub { + $self->stop_background_process; + $self->statusbar->SetStatusText("Export cancelled"); + $self->{export_gcode_output_file} = undef; + $self->{send_gcode_file} = undef; - # start background process, whose completion event handler - # will detect $self->{export_gcode_output_file} and proceed with export - $self->start_background_process; - } else { - eval { - $self->{print}->process; - $self->{print}->export_gcode(output_file => $self->{export_gcode_output_file}); - }; - my $result = !Slic3r::GUI::catch_error($self); - $self->on_export_completed($result); - } + # this updates buttons status + $self->object_list_changed; + }); + + # start background process, whose completion event handler + # will detect $self->{export_gcode_output_file} and proceed with export + $self->start_background_process; # this updates buttons status $self->object_list_changed; @@ -1577,7 +1558,7 @@ sub reset_thumbnail { sub update { my ($self, $force_autocenter) = @_; - if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { + if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } @@ -1604,9 +1585,7 @@ sub update { # and some reasonable default has to be selected for the additional extruders. sub on_extruders_change { my ($self, $num_extruders) = @_; - my $choices = $self->{preset_choosers}{filament}; - wxTheApp->{preset_bundle}->update_multi_material_filament_presets; while (int(@$choices) < $num_extruders) { # copy strings from first choice diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index 089b25947..a1e077591 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -9,7 +9,7 @@ use utf8; use List::Util qw(min max first); use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl); -use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); +use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE); use base 'Wx::Panel'; @@ -102,7 +102,7 @@ sub repaint { } # draw print center - if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) { + if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) { my $center = $self->unscaled_point_to_pixel($self->{print_center}); $dc->SetPen($self->{print_center_pen}); $dc->DrawLine($center->[X], 0, $center->[X], $size[Y]); @@ -197,7 +197,6 @@ sub repaint { sub mouse_event { my ($self, $event) = @_; - my $pos = $event->GetPosition; my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]] if ($event->ButtonDown) { @@ -257,7 +256,7 @@ sub mouse_event { } sub update_bed_size { - my $self = shift; + my ($self) = @_; # when the canvas is not rendered yet, its GetSize() method returns 0,0 my $canvas_size = $self->GetSize; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 4b6c665b7..ca953e215 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -10,6 +10,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize); $self->{values} = {}; + my $app_config = wxTheApp->{app_config}; my $optgroup; $optgroup = Slic3r::GUI::OptionsGroup->new( parent => $self, @@ -25,7 +26,7 @@ sub new { # type => 'bool', # label => 'Check for updates', # tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', -# default => $Slic3r::GUI::Settings->{_}{version_check} // 1, +# default => $app_config->get("version_check") // 1, # readonly => !wxTheApp->have_version_check, # )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -33,36 +34,35 @@ sub new { type => 'bool', label => 'Remember output directory', tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.', - default => $Slic3r::GUI::Settings->{_}{remember_output_path}, + default => $app_config->get("remember_output_path"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'autocenter', type => 'bool', label => 'Auto-center parts', tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.', - default => $Slic3r::GUI::Settings->{_}{autocenter}, + default => $app_config->get("autocenter"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'background_processing', type => 'bool', label => 'Background processing', tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.', - default => $Slic3r::GUI::Settings->{_}{background_processing}, - readonly => !$Slic3r::have_threads, + default => $app_config->get("background_processing"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_controller', type => 'bool', label => 'Disable USB/serial connection', tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.', - default => $Slic3r::GUI::Settings->{_}{no_controller}, + default => $app_config->get("no_controller"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_defaults', type => 'bool', label => 'Suppress "- default -" presets', tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.', - default => $Slic3r::GUI::Settings->{_}{no_defaults}, + default => $app_config->get("no_defaults"), )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); @@ -79,15 +79,15 @@ sub new { } sub _accept { - my $self = shift; + my ($self) = @_; if (defined($self->{values}{no_controller}) || defined($self->{values}{no_defaults})) { Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); } - $Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}}; - wxTheApp->save_settings; + my $app_config = wxTheApp->{app_config}; + $app_config->set($_, $self->{values}{$_}) for keys %{$self->{values}}; $self->EndModal(wxID_OK); $self->Close; # needed on Linux diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 1375bb92b..2718d4f5e 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -125,15 +125,9 @@ sub save_preset { $self->{treectrl}->SetFocus; if (!defined $name) { - my $preset = $self->{presets}->get_edited_preset; + my $preset = $self->{presets}->get_selected_preset; my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.[iI][nN][iI]$//; - - my @prsts = @{$self->{presets}}; - print "Num of presets: ". int(@prsts) . "\n"; - for my $pr (@prsts) { - print "Name: " . $pr->name . " default " . $pr->default . "\n"; - } my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, @@ -156,16 +150,16 @@ sub delete_preset { my ($self) = @_; my $current_preset = $self->{presets}->get_selected_preset; # Don't let the user delete the '- default -' configuration. + my $msg = 'Are you sure you want to ' . ($current_preset->external ? 'remove' : 'delete') . ' the selected preset?'; + my $title = ($current_preset->external ? 'Remove' : 'Delete') . ' Preset'; return if $current_preset->default || - wxID_YES != Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; + wxID_YES != Wx::MessageDialog->new($self, $msg, $title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; # Delete the file and select some other reasonable preset. # The 'external' presets will only be removed from the preset list, their files will not be deleted. eval { $self->{presets}->delete_current_preset; }; Slic3r::GUI::catch_error($self) and return; - # Delete the item from the UI component and activate another preset. - $self->{presets}->update_tab_ui($self->{presets_choice}); - # Update the selection boxes at the patter. - $self->_on_presets_changed; + # Load the newly selected preset into the UI, update selection combo boxes with their dirty flags. + $self->load_current_preset; } # Register the on_value_change callback. @@ -203,7 +197,6 @@ sub _update {} # to uddate number of "filament" selection boxes when the number of extruders change. sub _on_presets_changed { my ($self) = @_; - print "Tab::_on_presets_changed\n"; $self->{on_presets_changed}->($self->{presets}) if $self->{on_presets_changed}; } @@ -237,38 +230,34 @@ sub may_discard_current_preset_if_dirty } # Called by the UI combo box when the user switches profiles. -# Select a preset by a name. If ! defined(name), then the first visible preset is selected. +# Select a preset by a name. If ! defined(name), then the default preset is selected. # If the current profile is modified, user is asked to save the changes. sub select_preset { my ($self, $name, $force) = @_; - print "select_preset 1\n"; - if (! $self->may_discard_current_preset_if_dirty) { + $force //= 0; + if (! $force && ! $self->may_discard_current_preset_if_dirty) { $self->{presets}->update_tab_ui($self->{presets_choice}); # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector. $self->_on_presets_changed; return; } - print "select_preset 2\n"; - $self->{presets}->select_preset_by_name(defined $name ? $name : ""); - print "select_preset 3\n"; + if (defined $name) { + $self->{presets}->select_preset_by_name($name); + } else { + $self->{presets}->select_preset(0); + } # Initialize the UI from the current preset. $self->load_current_preset; - print "select_preset 4\n"; - # Save the current application settings with the newly selected preset name. - wxTheApp->save_settings; - print "select_preset 5\n"; } # Initialize the UI from the current preset. sub load_current_preset { my ($self) = @_; - print "load_current_preset 1\n"; $self->{presets}->update_tab_ui($self->{presets_choice}); - print "load_current_preset 2\n"; my $preset = $self->{presets}->get_current_preset; eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - my $method = ($preset->default || $preset->external) ? 'Disable' : 'Enable'; + my $method = $preset->default ? 'Disable' : 'Enable'; $self->{btn_delete_preset}->$method; $self->_update; # For the printer profile, generate the extruder pages. @@ -281,8 +270,9 @@ sub load_current_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; + #the following is called by update_dirty + #$self->_on_presets_changed; }); } @@ -404,7 +394,7 @@ sub build { my $self = shift; $self->{presets} = wxTheApp->{preset_bundle}->print; - $self->{config} = $self->{presets}->get_edited_preset->config_ref; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); @@ -608,7 +598,7 @@ sub build { $optgroup->append_single_option_line('clip_multipart_objects'); $optgroup->append_single_option_line('elefant_foot_compensation'); $optgroup->append_single_option_line('xy_size_compensation'); -# $optgroup->append_single_option_line('threads') if $Slic3r::have_threads; +# $optgroup->append_single_option_line('threads'); $optgroup->append_single_option_line('resolution'); } } @@ -675,7 +665,7 @@ sub _update { my ($self) = @_; $self->Freeze; - my $config = $self->{presets}->get_edited_preset->config_ref; + my $config = $self->{config}; if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 0)) { my $dialog = Wx::MessageDialog->new($self, @@ -882,7 +872,7 @@ sub build { my $self = shift; $self->{presets} = wxTheApp->{preset_bundle}->filament; - $self->{config} = $self->{presets}->get_edited_preset->config_ref; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Filament', 'spool.png'); @@ -1061,8 +1051,9 @@ sub build { my ($self, %params) = @_; $self->{presets} = wxTheApp->{preset_bundle}->printer; - $self->{config} = $self->{presets}->get_edited_preset->config_ref; - + $self->{config} = $self->{presets}->get_edited_preset->config; + $self->{extruders_count} = scalar @{$self->{config}->nozzle_diameter}; + my $bed_shape_widget = sub { my ($parent) = @_; @@ -1086,9 +1077,7 @@ sub build { return $sizer; }; - - $self->{extruders_count} = 1; - + { my $page = $self->add_options_page('General', 'printer_empty.png'); { @@ -1117,13 +1106,16 @@ sub build { $optgroup->append_single_option_line('single_extruder_multi_material'); } $optgroup->on_change(sub { - my ($opt_id) = @_; - if ($opt_id eq 'extruders_count') { - wxTheApp->CallAfter(sub { + my ($opt_key, $value) = @_; + wxTheApp->CallAfter(sub { + if ($opt_key eq 'extruders_count') { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); - }); - $self->update_dirty; - } + $self->update_dirty; + } else { + $self->update_dirty; + $self->_on_value_change($opt_key, $value); + } + }); }); } if (!$params{no_controller}) @@ -1326,43 +1318,23 @@ sub build { sub _update_serial_ports { my ($self) = @_; - $self->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]); + $self->get_field('serial_port')->set_values([ Slic3r::GUI::scan_serial_ports ]); } sub _extruders_count_changed { my ($self, $extruders_count) = @_; - $self->{extruders_count} = $extruders_count; + wxTheApp->{preset_bundle}->printer->get_edited_preset->set_num_extruders($extruders_count); + wxTheApp->{preset_bundle}->update_multi_material_filament_presets; $self->_build_extruder_pages; $self->_on_value_change('extruders_count', $extruders_count); } -sub _extruder_options { - qw(nozzle_diameter min_layer_height max_layer_height extruder_offset - retract_length retract_lift retract_lift_above retract_lift_below retract_speed deretract_speed - retract_before_wipe retract_restart_extra retract_before_travel wipe - retract_layer_change retract_length_toolchange retract_restart_extra_toolchange extruder_colour) } - sub _build_extruder_pages { - my $self = shift; - + my ($self) = @_; my $default_config = Slic3r::Config::Full->new; - + foreach my $extruder_idx (@{$self->{extruder_pages}} .. $self->{extruders_count}-1) { - # extend options - foreach my $opt_key ($self->_extruder_options) { - my $values = $self->{config}->get($opt_key); - if (!defined $values) { - $values = [ $default_config->get_at($opt_key, 0) ]; - } else { - # use last extruder's settings for the new one - my $last_value = $values->[-1]; - $values->[$extruder_idx] //= $last_value; - } - $self->{config}->set($opt_key, $values) - or die "Unable to extend $opt_key"; - } - # build page my $page = $self->{extruder_pages}[$extruder_idx] = $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png'); { @@ -1412,14 +1384,6 @@ sub _build_extruder_pages { splice @{$self->{extruder_pages}}, $self->{extruders_count}; } - # remove extra config values - foreach my $opt_key ($self->_extruder_options) { - my $values = $self->{config}->get($opt_key); - splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values; - $self->{config}->set($opt_key, $values) - or die "Unable to truncate $opt_key"; - } - # rebuild page list my @pages_without_extruders = (grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}); my $page_notes = pop @pages_without_extruders; @@ -1513,15 +1477,12 @@ sub _update { # this gets executed after preset is loaded and before GUI fields are updated sub on_preset_loaded { - my $self = shift; - + my ($self) = @_; # update the extruders count field - { - # update the GUI field according to the number of nozzle diameters supplied - my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; - $self->set_value('extruders_count', $extruders_count); - $self->_extruders_count_changed($extruders_count); - } + my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; + $self->set_value('extruders_count', $extruders_count); + # update the GUI field according to the number of nozzle diameters supplied + $self->_extruders_count_changed($extruders_count); } # Single Tab page containing a {vsizer} of {optgroups} @@ -241,16 +241,7 @@ if (@ARGV) { # slicing from command line sub usage { my ($exit_code) = @_; - my $config = Slic3r::Config::new_from_defaults->as_hash; - - my $j = ''; - if ($Slic3r::have_threads) { - $j = <<"EOF"; - -j, --threads <num> Number of threads to use (1+, default: $config->{threads}) -EOF - } - print <<"EOF"; Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/ @@ -275,8 +266,8 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... them as <name>_upper.stl and <name>_lower.stl --split Split the shells contained in given STL file into several STL files --info Output information about the supplied file(s) and exit - -$j + -j, --threads <num> Number of threads to use (1+, default: $config->{threads}) + GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) diff --git a/t/threads.t b/t/threads.t index 7fcd86f0e..7fede3328 100644 --- a/t/threads.t +++ b/t/threads.t @@ -1,4 +1,4 @@ -use Test::More; +use Test::More tests => 2; use strict; use warnings; @@ -12,11 +12,6 @@ use List::Util qw(first); use Slic3r; use Slic3r::Test; -if (!$Slic3r::have_threads) { - plan skip_all => "this perl is not compiled with threads"; -} -plan tests => 2; - { my $print = Slic3r::Test::init_print('20mm_cube'); { diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 2228b8e7c..30fc00b68 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -207,15 +207,15 @@ public: void resize(size_t n, const ConfigOption *opt_default = nullptr) override { assert(opt_default == nullptr || opt_default->is_vector()); - assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default)); +// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default)); assert(! this->values.empty() || opt_default != nullptr); if (n == 0) this->values.clear(); else if (n < this->values.size()) this->values.erase(this->values.begin() + n, this->values.end()); - else if (n > this->values.size()) + else if (n > this->values.size()) { if (this->values.empty()) { - if (opt_default == nullptr) { + if (opt_default == nullptr) throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); if (opt_default->type() != this->type()) throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 9e4043818..308a2a118 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -25,7 +25,6 @@ std::string config_path(const std::string &file_name); // The suffix ".ini" will be added if it is missing in the name. std::string config_path(const std::string §ion, const std::string &name); -extern std::locale locale_utf8; extern std::string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index e10e136d1..f4c03ef50 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -103,14 +103,14 @@ const std::string& data_dir() std::string config_path(const std::string &file_name) { - auto file = boost::filesystem::canonical(boost::filesystem::path(g_data_dir) / file_name).make_preferred(); + auto file = (boost::filesystem::path(g_data_dir) / file_name).make_preferred(); return file.string(); } std::string config_path(const std::string §ion, const std::string &name) { auto file_name = boost::algorithm::iends_with(name, ".ini") ? name : name + ".ini"; - auto file = boost::filesystem::canonical(boost::filesystem::path(g_data_dir) / file_name).make_preferred(); + auto file = (boost::filesystem::path(g_data_dir) / section / file_name).make_preferred(); return file.string(); } @@ -228,10 +228,9 @@ std::string decode_path(const char *src) #endif /* WIN32 */ } -std::locale locale_utf8(boost::locale::generator().generate("")); - std::string normalize_utf8_nfc(const char *src) { + static std::locale locale_utf8(boost::locale::generator().generate("")); return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index b61ce89d2..f978fcbb4 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -27,9 +27,7 @@ void AppConfig::reset() // Override missing or keys with their defaults. void AppConfig::set_defaults() { - // 2) Reset to defaults. - if (get("version_check").empty()) - set("version_check", "1"); + // Reset the empty fields to defaults. if (get("autocenter").empty()) set("autocenter", "1"); // Disable background processing by default as it is not stable. @@ -38,10 +36,13 @@ void AppConfig::set_defaults() // 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. if (get("no_controller").empty()) - set("no_controller", "0"); + set("no_controller", "1"); // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. if (get("no_defaults").empty()) set("no_defaults", "1"); + // Version check is enabled by default in the config, but it is not implemented yet. + if (get("version_check").empty()) + set("version_check", "1"); } void AppConfig::load() @@ -114,13 +115,11 @@ std::string AppConfig::get_last_dir() const void AppConfig::update_config_dir(const std::string &dir) { this->set("recent", "config_directory", dir); - this->save(); } void AppConfig::update_skein_dir(const std::string &dir) { this->set("recent", "skein_directory", dir); - this->save(); } std::string AppConfig::get_last_output_dir(const std::string &alt) const @@ -138,7 +137,6 @@ std::string AppConfig::get_last_output_dir(const std::string &alt) const void AppConfig::update_last_output_dir(const std::string &dir) { this->set("", "last_output_path", dir); - this->save(); } std::string AppConfig::config_path() diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 17da135a0..12bf48014 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1,9 +1,14 @@ #include "GUI.hpp" +#include <assert.h> + +#include <boost/algorithm/string/predicate.hpp> + #if __APPLE__ #import <IOKit/pwr_mgt/IOPMLib.h> #elif _WIN32 #include <Windows.h> +#include "boost/nowide/convert.hpp" #pragma comment(lib, "user32.lib") #endif @@ -34,6 +39,82 @@ void enable_screensaver() #endif } +std::vector<std::string> scan_serial_ports() +{ + std::vector<std::string> out; +#ifdef _WIN32 + // 1) Open the registry key SERIALCOM. + HKEY hKey; + LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey); + assert(lRes == ERROR_SUCCESS); + if (lRes == ERROR_SUCCESS) { + // 2) Get number of values of SERIALCOM key. + DWORD cValues; // number of values for key + { + TCHAR achKey[255]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys=0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + // Get the class name and the value count. + lRes = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + assert(lRes == ERROR_SUCCESS); + } + // 3) Read the SERIALCOM values. + { + DWORD dwIndex = 0; + for (int i = 0; i < cValues; ++ i, ++ dwIndex) { + wchar_t valueName[2048]; + DWORD valNameLen = 2048; + DWORD dataType; + wchar_t data[2048]; + DWORD dataSize = 4096; + lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize); + if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0) + out.emplace_back(boost::nowide::narrow(data)); + } + } + ::RegCloseKey(hKey); + } +#else + // UNIX and OS X + boost::filesystem::recursive_directory_iterator end; + std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" }; + for (boost::filesystem::recursive_directory_iterator it_path(boost::filesystem::path("/dev")); + it_path != end; ++ it_path) + for (const char *prefix : prefixes) + if (boost::starts_with(it_path->string(), std::string("/dev/") + prefix)) { + out.emplace_back(path); + break; + } +#endif + + out.erase(std::remove_if(out.begin(), out.end(), + [](const std::string &key){ + return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly"); + }), + out.end()); + return out; +} + bool debugged() { #ifdef _WIN32 diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 85656c0a3..955a1cd8d 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -1,10 +1,14 @@ #ifndef slic3r_GUI_hpp_ #define slic3r_GUI_hpp_ +#include <string> +#include <vector> + namespace Slic3r { namespace GUI { void disable_screensaver(); void enable_screensaver(); +std::vector<std::string> scan_serial_ports(); bool debugged(); void break_to_debugger(); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index d02653a4a..5b42b27a1 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -1,3 +1,6 @@ +//#undef NDEBUG +#include <cassert> + #include "Preset.hpp" #include <fstream> @@ -16,16 +19,9 @@ #include <wx/bmpcbox.h> #include <wx/wupdlock.h> +#include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Utils.hpp" -#if 0 -#define DEBUG -#define _DEBUG -#undef NDEBUG -#endif - -#include <assert.h> - namespace Slic3r { ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) @@ -94,6 +90,19 @@ void Preset::normalize(DynamicPrintConfig &config) if (nozzle_diameter != nullptr) // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); + if (config.option("filament_diameter") != nullptr) { + // This config contains single or multiple filament presets. + // Ensure that the filament preset vector options contain the correct number of values. + size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : Preset::filament_options()) { + auto *opt = config.option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key)); + } + } } // Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. @@ -247,7 +256,6 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri } } std::sort(m_presets.begin() + 1, m_presets.end()); - m_presets.front().is_visible = ! m_default_suppressed || m_presets.size() > 1; this->select_preset(first_visible_idx()); } @@ -255,19 +263,22 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri // and select it, losing previous modifications. Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select) { + DynamicPrintConfig cfg(this->default_preset().config); + cfg.apply(config, true); + return this->load_preset(path, name, std::move(cfg)); +} + +Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select) +{ Preset key(m_type, name); auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key); - if (it != m_presets.end() && it->name == name) { - // The preset with the same name was found. - it->is_dirty = false; - } else { + if (it == m_presets.end() || it->name != name) it = m_presets.emplace(it, Preset(m_type, name, false)); - } Preset &preset = *it; preset.file = path; - preset.config = this->default_preset().config; + preset.config = std::move(config); preset.loaded = true; - this->get_selected_preset().is_dirty = false; + preset.is_dirty = false; if (select) this->select_preset_by_name(name, true); return preset; @@ -275,6 +286,8 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string void PresetCollection::save_current_preset(const std::string &new_name) { + // 1) Find the preset with a new_name or create a new one, + // initialize it with the edited config. Preset key(m_type, new_name, false); auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key); if (it != m_presets.end() && it->name == key.name) { @@ -285,37 +298,39 @@ void PresetCollection::save_current_preset(const std::string &new_name) return; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); - m_idx_selected = it - m_presets.begin(); } else { // Creating a new preset. - m_idx_selected = m_presets.insert(it, m_edited_preset) - m_presets.begin(); - Preset &preset = m_presets[m_idx_selected]; + Preset &preset = *m_presets.insert(it, m_edited_preset); std::string file_name = new_name; if (! boost::iends_with(file_name, ".ini")) file_name += ".ini"; preset.name = new_name; preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } - m_edited_preset = m_presets[m_idx_selected]; - m_presets[m_idx_selected].save(); - m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected > 0; + // 2) Activate the saved preset. + this->select_preset_by_name(new_name, true); + // 2) Store the active preset to disk. + this->get_selected_preset().save(); } void PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); - if (selected.is_default || selected.is_external) + if (selected.is_default) return; - // Erase the preset file. - boost::nowide::remove(selected.file.c_str()); + if (! selected.is_external) { + // Erase the preset file. + boost::nowide::remove(selected.file.c_str()); + } // Remove the preset from the list. m_presets.erase(m_presets.begin() + m_idx_selected); // Find the next visible preset. - m_presets.front().is_visible = ! m_default_suppressed || m_presets.size() > 1; - for (; m_idx_selected < m_presets.size() && ! m_presets[m_idx_selected].is_visible; ++ m_idx_selected) ; - if (m_idx_selected == m_presets.size()) - m_idx_selected = this->first_visible_idx(); - m_edited_preset = m_presets[m_idx_selected]; + size_t new_selected_idx = m_idx_selected; + if (new_selected_idx < m_presets.size()) + for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; + if (new_selected_idx == m_presets.size()) + for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); + this->select_preset(new_selected_idx); } bool PresetCollection::load_bitmap_default(const std::string &file_name) @@ -337,7 +352,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. size_t PresetCollection::first_visible_idx() const { - size_t idx = 0; + size_t idx = m_default_suppressed ? 1 : 0; for (; idx < this->m_presets.size(); ++ idx) if (m_presets[idx].is_visible) break; @@ -438,11 +453,13 @@ bool PresetCollection::update_dirty_ui(wxChoice *ui) Preset& PresetCollection::select_preset(size_t idx) { + for (Preset &preset : m_presets) + preset.is_dirty = false; if (idx >= m_presets.size()) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; - m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected > 0; + m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected == 0; return m_presets[idx]; } @@ -458,7 +475,7 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b idx = it - m_presets.begin(); else { // Find the first visible preset. - for (size_t i = 0; i < m_presets.size(); ++ i) + for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i) if (m_presets[i].is_visible) { idx = i; break; diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 85280eccf..a9bc8812c 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -121,6 +121,7 @@ public: // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); + Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true); // Save the preset under a new name. If the name is different from the old one, // a new preset is stored into the list of presets. diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 4abb7a88f..4cfeb5fad 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -1,3 +1,6 @@ +//#undef NDEBUGc +#include <cassert> + #include "PresetBundle.hpp" #include <fstream> @@ -17,10 +20,10 @@ #include <wx/bmpcbox.h> #include <wx/wupdlock.h> +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/PlaceholderParser.hpp" #include "../../libslic3r/Utils.hpp" -#include <assert.h> - namespace Slic3r { PresetBundle::PresetBundle() : @@ -117,6 +120,15 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "printer", printers.get_selected_preset().name); } +void PresetBundle::export_selections(PlaceholderParser &pp) +{ + assert(filament_presets.size() >= 1); + assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); + pp.set("print_preset", prints.get_selected_preset().name); + pp.set("filament_preset", filament_presets); + pp.set("printer_preset", printers.get_selected_preset().name); +} + bool PresetBundle::load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible) { bool loaded_compatible = m_bitmapCompatible ->LoadFile( @@ -193,6 +205,15 @@ DynamicPrintConfig PresetBundle::full_config() const // If the file is loaded successfully, its print / filament / printer profiles will be activated. void PresetBundle::load_config_file(const std::string &path) { + if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config.load_from_gcode(path); + Preset::normalize(config); + load_config_file_config(path, std::move(config)); + return; + } + // 1) Try to load the config file into a boost property tree. boost::property_tree::ptree tree; try { @@ -211,36 +232,37 @@ void PresetBundle::load_config_file(const std::string &path) throw std::runtime_error(std::string("Unknown configuration file type: ") + path); case CONFIG_FILE_TYPE_APP_CONFIG: throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); - case CONFIG_FILE_TYPE_CONFIG: - load_config_file_config(path, tree); - break; + case CONFIG_FILE_TYPE_CONFIG: + { + // Initialize a config from full defaults. + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config.load(tree); + Preset::normalize(config); + load_config_file_config(path, std::move(config)); + break; + } case CONFIG_FILE_TYPE_CONFIG_BUNDLE: - load_config_file_config_bundle(path, tree); + load_config_file_config_bundle(path, tree); break; } } // Load a config file from a boost property_tree. This is a private method called from load_config_file. -void PresetBundle::load_config_file_config(const std::string &path, const boost::property_tree::ptree &tree) +void PresetBundle::load_config_file_config(const std::string &path, const DynamicPrintConfig &config) { - // 1) Initialize a config from full defaults. - DynamicPrintConfig config; - config.apply(FullPrintConfig()); - config.load(tree); - Preset::normalize(config); - - // 2) Create a name from the file name. + // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. std::string name = boost::filesystem::path(path).filename().string(); - // 3) If the loading succeeded, split and load the config into print / filament / printer settings. + // 2) If the loading succeeded, split and load the config into print / filament / printer settings. // First load the print and printer presets. for (size_t i_group = 0; i_group < 2; ++ i_group) { PresetCollection &presets = (i_group == 0) ? this->prints : this->printers; presets.load_preset(path, name, config).is_external = true; } - // Now load the filaments. If there are multiple filament presets, split them and load them. + // 3) Now load the filaments. If there are multiple filament presets, split them and load them. auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter")); auto *filament_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("filament_diameter")); size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size()); @@ -253,7 +275,7 @@ void PresetBundle::load_config_file_config(const std::string &path, const boost: std::vector<DynamicPrintConfig> configs(num_extruders, this->filaments.default_preset().config); // loop through options and scatter them into configs. for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { - const ConfigOption *other_opt = config.option(key, false); + const ConfigOption *other_opt = config.option(key); if (other_opt == nullptr) continue; if (other_opt->is_scalar()) { @@ -273,7 +295,7 @@ void PresetBundle::load_config_file_config(const std::string &path, const boost: else sprintf(suffix, " (%d)", i); // Load all filament presets, but only select the first one in the preset dialog. - this->filaments.load_preset(path, name + suffix, configs[i], i == 0).is_external = true; + this->filaments.load_preset(path, name + suffix, std::move(configs[i]), i == 0).is_external = true; filament_presets.emplace_back(name + suffix); } } @@ -301,7 +323,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const // Generate a new unique name. } if (! new_name.empty()) - this->prints.load_preset(path, new_name, tmp_bundle.prints.get_selected_preset().config); + this->prints.load_preset(path, new_name, std::move(tmp_bundle.prints.get_selected_preset().config)); } } @@ -369,8 +391,9 @@ size_t PresetBundle::load_configbundle(const std::string &path) DynamicPrintConfig config(presets->default_preset().config); for (auto &kvp : section.second) config.set_deserialize(kvp.first, kvp.second.data()); + Preset::normalize(config); // Load the preset into the list of presets, save it to disk. - presets->load_preset(Slic3r::config_path(presets->name(), preset_name), preset_name, config, false).save(); + presets->load_preset(Slic3r::config_path(presets->name(), preset_name), preset_name, std::move(config), false).save(); ++ presets_loaded; } } @@ -386,7 +409,7 @@ size_t PresetBundle::load_configbundle(const std::string &path) this->update_multi_material_filament_presets(); for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) - this->filament_presets[i] = filaments.first_visible().name; + this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; return presets_loaded; } @@ -396,7 +419,6 @@ void PresetBundle::update_multi_material_filament_presets() auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); // Verify validity of the current filament presets. - printf("PresetBundle::update_multi_material_filament_presets, old: %d, new: %d\n", int(this->filament_presets.size()), int(num_extruders)); for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; // Append the rest of filament presets. diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index 7add3055d..b1077c11c 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -6,6 +6,8 @@ namespace Slic3r { +class PlaceholderParser; + // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -23,6 +25,8 @@ public: void load_selections(const AppConfig &config); // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); + // Export selections (current print, current filaments, current printer) into a placeholder parser. + void export_selections(PlaceholderParser &pp); PresetCollection prints; PresetCollection filaments; @@ -31,6 +35,9 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector<std::string> filament_presets; + bool has_defauls_only() const + { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } + DynamicPrintConfig full_config() const; // Load an external config file containing the print, filament and printer presets. @@ -66,7 +73,7 @@ public: void update_multi_material_filament_presets(); private: - void load_config_file_config(const std::string &path, const boost::property_tree::ptree &tree); + void load_config_file_config(const std::string &path, const DynamicPrintConfig &config); void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); bool load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 875c49e26..ce3c178a1 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -14,6 +14,9 @@ void disable_screensaver() void enable_screensaver() %code{% Slic3r::GUI::enable_screensaver(); %}; +std::vector<std::string> scan_serial_ports() + %code{% RETVAL=Slic3r::GUI::scan_serial_ports(); %}; + bool debugged() %code{% RETVAL=Slic3r::GUI::debugged(); %}; diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp index d220c3580..8162b9a5e 100644 --- a/xs/xsp/GUI_AppConfig.xsp +++ b/xs/xsp/GUI_AppConfig.xsp @@ -13,8 +13,22 @@ void reset(); void set_defaults(); - void load(); - void save(); + void load() + %code%{ + try { + THIS->load(); + } catch (std::exception& e) { + croak("Loading an application config file failed:\n%s\n", e.what()); + } + %}; + void save() + %code%{ + try { + THIS->save(); + } catch (std::exception& e) { + croak("Saving an application config file failed:\n%s\n", e.what()); + } + %}; bool exists(); bool dirty(); diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 34184a588..e76b5c704 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -52,10 +52,25 @@ bool update_dirty_ui(SV *ui) %code%{ RETVAL = THIS->update_dirty_ui((wxChoice*)wxPli_sv_2_object(aTHX_ ui, "Wx::Choice")); %}; + void select_preset(int idx); bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %}; - void save_current_preset(char *new_name); - void delete_current_preset(); + void save_current_preset(char *new_name) + %code%{ + try { + THIS->save_current_preset(new_name); + } catch (std::exception& e) { + croak("Error saving a preset %s:\n%s\n", new_name, e.what()); + } + %}; + void delete_current_preset() + %code%{ + try { + THIS->delete_current_preset(); + } catch (std::exception& e) { + croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what()); + } + %}; %{ @@ -88,24 +103,61 @@ PresetCollection::presets_hash() %} }; - %name{Slic3r::GUI::PresetBundle} class PresetBundle { PresetBundle(); ~PresetBundle(); - void setup_directories(); - void load_presets(const char *dir_path); - void load_config_file(const char *path); - size_t load_configbundle(const char *path); - void export_configbundle(char *path); + void setup_directories() + %code%{ + try { + THIS->setup_directories(); + } catch (std::exception& e) { + croak("Cannot create configuration directories:\n%s\n", e.what()); + } + %}; + void load_presets(const char *dir_path) + %code%{ + try { + THIS->load_presets(dir_path); + } catch (std::exception& e) { + croak("Loading of Slic3r presets from %s failed:\n%s\n", dir_path, e.what()); + } + %}; + void load_config_file(const char *path) + %code%{ + try { + THIS->load_config_file(path); + } catch (std::exception& e) { + croak("Loading a configuration file %s failed:\n%s\n", path, e.what()); + } + %}; + size_t load_configbundle(const char *path) + %code%{ + try { + RETVAL = THIS->load_configbundle(path); + } catch (std::exception& e) { + croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); + } + %}; + void export_configbundle(char *path) + %code%{ + try { + THIS->export_configbundle(path); + } catch (std::exception& e) { + croak("Export of a config bundle %s failed:\n%s\n", path, e.what()); + } + %}; + void set_default_suppressed(bool default_suppressed); void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %}; void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %}; + void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %}; Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %}; Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %}; Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %}; + bool has_defauls_only(); std::vector<std::string> filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; void set_filament_preset(int idx, const char *name); |