diff options
author | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
---|---|---|
committer | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
commit | 50a45949d1a2878e114cd5e8728275c2586a60e2 (patch) | |
tree | 3f7fa3dbd70d5b6b2bac0b6b3c5226a0fa775810 /lib | |
parent | bea9628be08d9c2be9ba49c7538112d39b1d6fa8 (diff) | |
parent | 19388285205ff46379ce0f9b0291aff1badd6568 (diff) |
merge with master
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Slic3r.pm | 26 | ||||
-rw-r--r-- | lib/Slic3r/Config.pm | 300 | ||||
-rw-r--r-- | lib/Slic3r/GUI.pm | 251 | ||||
-rw-r--r-- | lib/Slic3r/GUI/3DScene.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/AboutDialog.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/ConfigWizard.pm | 93 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller.pm | 58 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller/ManualControlDialog.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller/PrinterPanel.pm | 28 | ||||
-rw-r--r-- | lib/Slic3r/GUI/MainFrame.pm | 393 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Notifier.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/OptionsGroup/Field.pm | 4 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater.pm | 452 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/2D.pm | 7 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/3D.pm | 11 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 28 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm | 4 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Preferences.pm | 31 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Tab.pm | 915 | ||||
-rw-r--r-- | lib/Slic3r/Print.pm | 19 | ||||
-rw-r--r-- | lib/Slic3r/Print/Object.pm | 3 |
21 files changed, 968 insertions, 1663 deletions
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 7729b6126..22a6ee389 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; } @@ -50,8 +39,10 @@ warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n" if $^V == v5.16; use FindBin; -# Path to the images. -our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] }; + +# Let the XS module know where the GUI resources reside. +set_resources_dir(decode_path($FindBin::Bin) . (($^O eq 'darwin') ? '/../Resources' : '/resources')); +set_var_dir(resources_dir() . "/icons"); use Moo 1.003001; @@ -163,6 +154,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 638a02c9b9..a9c822b96 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -8,42 +8,19 @@ use utf8; use List::Util qw(first max); -# cemetery of old config settings -our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration - adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid - rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang - randomize_start seal_position bed_size print_center g0 vibration_limit gcode_arcs pressure_advance); - # C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. # 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) }; - } -} - -# Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults -# provided by the C++ class Slic3r::FullPrintConfig. -# Used by the UI. -sub new_from_defaults { - my ($class, @opt_keys) = @_; - my $self = $class->new; - # Instantiating the C++ class Slic3r::FullPrintConfig. - my $defaults = Slic3r::Config::Full->new; - if (@opt_keys) { - $self->set($_, $defaults->get($_)) - for grep $defaults->has($_), @opt_keys; - } else { - $self->apply_static($defaults); + *{$opt_key} = sub { + #print "Slic3r::Config::accessor $opt_key\n"; + $_[0]->get($opt_key) + }; } - return $self; } # From command line parameters, used by slic3r.pl @@ -87,273 +64,6 @@ sub new_from_cli { return $self; } -sub merge { - my $class = shift; - my $config = $class->new; - $config->apply($_) for @_; - return $config; -} - -# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class, -# convert legacy configuration names. -sub load { - my ($class, $file) = @_; - # Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed - # explicitely to avoid having to bundle the UTF8 Perl library. - if ($file =~ /\.[gG][cC][oO][dD][eE]/ || $file =~ /\.[gG]/) { - my $config = $class->new; - $config->_load_from_gcode($file); - return $config; - } else { - my $config = $class->new; - $config->_load($file); - return $config; - } -} - -# Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class, -# convert legacy configuration names. -# Used to load a config bundle. -sub load_ini_hash { - my ($class, $ini_hash) = @_; - my $config = $class->new; - $config->set_deserialize($_, $ini_hash->{$_}) for keys %$ini_hash; - return $config; -} - -sub clone { - my $self = shift; - my $new = (ref $self)->new; - $new->apply($self); - return $new; -} - -sub get_value { - my ($self, $opt_key) = @_; - return $Options->{$opt_key}{ratio_over} - ? $self->get_abs_value($opt_key) - : $self->get($opt_key); -} - -# Create a hash of hashes from the underlying C++ Slic3r::DynamicPrintConfig. -# The first hash key is '_' meaning no category. -# Used to create a config bundle. -sub as_ini { - my ($self) = @_; - my $ini = { _ => {} }; - foreach my $opt_key (sort @{$self->get_keys}) { - next if $Options->{$opt_key}{shortcut}; - $ini->{_}{$opt_key} = $self->serialize($opt_key); - } - return $ini; -} - -# this method is idempotent by design and only applies to ::DynamicConfig or ::Full -# objects because it performs cross checks -sub validate { - my $self = shift; - - # -j, --threads - die "Invalid value for --threads\n" - if $self->threads < 1; - - # --layer-height - die "Invalid value for --layer-height\n" - if $self->layer_height <= 0; - die "--layer-height must be a multiple of print resolution\n" - if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0; - - # --first-layer-height - die "Invalid value for --first-layer-height\n" - if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/; - die "Invalid value for --first-layer-height\n" - if $self->get_value('first_layer_height') <= 0; - - # --filament-diameter - die "Invalid value for --filament-diameter\n" - if grep $_ < 1, @{$self->filament_diameter}; - - # --nozzle-diameter - die "Invalid value for --nozzle-diameter\n" - if grep $_ < 0, @{$self->nozzle_diameter}; - - # --perimeters - die "Invalid value for --perimeters\n" - if $self->perimeters < 0; - - # --solid-layers - die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0; - die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0; - die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0; - - # --gcode-flavor - die "Invalid value for --gcode-flavor\n" - if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; - - die "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware\n" - if $self->use_firmware_retraction && $self->gcode_flavor ne 'smoothie' - && $self->gcode_flavor ne 'reprap' - && $self->gcode_flavor ne 'machinekit' - && $self->gcode_flavor ne 'repetier'; - - die "--use-firmware-retraction is not compatible with --wipe\n" - if $self->use_firmware_retraction && first {$_} @{$self->wipe}; - - # --fill-pattern - die "Invalid value for --fill-pattern\n" - if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}}; - - # --external-fill-pattern - die "Invalid value for --external-fill-pattern\n" - if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}}; - - # --fill-density - die "The selected fill pattern is not supposed to work at 100% density\n" - if $self->fill_density == 100 - && !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}}; - - # --infill-every-layers - die "Invalid value for --infill-every-layers\n" - if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1; - - # --skirt-height - die "Invalid value for --skirt-height\n" - if $self->skirt_height < -1; # -1 means as tall as the object - - # --bridge-flow-ratio - die "Invalid value for --bridge-flow-ratio\n" - if $self->bridge_flow_ratio <= 0; - - # extruder clearance - die "Invalid value for --extruder-clearance-radius\n" - if $self->extruder_clearance_radius <= 0; - die "Invalid value for --extruder-clearance-height\n" - if $self->extruder_clearance_height <= 0; - - # --extrusion-multiplier - die "Invalid value for --extrusion-multiplier\n" - if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; - - # --default-acceleration - die "Invalid zero value for --default-acceleration when using other acceleration settings\n" - if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration) - && !$self->default_acceleration; - - # --spiral-vase - if ($self->spiral_vase) { - # Note that we might want to have more than one perimeter on the bottom - # solid layers. - die "Can't make more than one perimeter when spiral vase mode is enabled\n" - if $self->perimeters > 1; - - die "Can't make less than one perimeter when spiral vase mode is enabled\n" - if $self->perimeters < 1; - - die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n" - if $self->fill_density > 0; - - die "Spiral vase mode is not compatible with top solid layers\n" - if $self->top_solid_layers > 0; - - die "Spiral vase mode is not compatible with support material\n" - if $self->support_material || $self->support_material_enforce_layers > 0; - } - - # extrusion widths - { - my $max_nozzle_diameter = max(@{ $self->nozzle_diameter }); - die "Invalid extrusion width (too large)\n" - if defined first { $_ > 10 * $max_nozzle_diameter } - map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter), - qw(perimeter infill solid_infill top_infill support_material first_layer); - } - - # general validation, quick and dirty - foreach my $opt_key (@{$self->get_keys}) { - my $opt = $Options->{$opt_key}; - next unless defined $self->$opt_key; - next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/; - my $type = $1; - my @values = (); - if ($type =~ s/\@$//) { - die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY'; - @values = @{ $self->$opt_key }; - } else { - @values = ($self->$opt_key); - } - foreach my $value (@values) { - if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') { - $value =~ s/%$// if $opt->{type} eq 'percent'; - die "Invalid value for $opt_key\n" - if ($type eq 'i' && $value !~ /^-?\d+$/) - || (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/) - || (defined $opt->{min} && $value < $opt->{min}) - || (defined $opt->{max} && $value > $opt->{max}); - } elsif ($type eq 's' && $opt->{type} eq 'select') { - die "Invalid value for $opt_key\n" - unless first { $_ eq $value } @{ $opt->{values} }; - } - } - } - - return 1; -} - -# 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 dc93e513f..5176c9c91 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -52,27 +52,12 @@ use constant FILE_WILDCARDS => { }; use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)}; +# Datadir provided on the command line. 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 $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); @@ -82,134 +67,71 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); our $grey = Wx::Colour->new(200,200,200); -#our $VERSION_CHECK_EVENT : shared = Wx::NewEventType; - -our $DLP_projection_screen; - sub OnInit { my ($self) = @_; - $self->SetAppName('Slic3r'); + $self->SetAppName('Slic3rPE'); $self->SetAppDisplayName('Slic3r Prusa Edition'); Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; - - $self->{notifier} = Slic3r::GUI::Notifier->new; - $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; - # locate or create data directory + # Set the Slic3r data directory at the Slic3r XS module. # Unix: ~/.Slic3r # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" # Mac: "~/Library/Application Support/Slic3r" - $datadir ||= Wx::StandardPaths::Get->GetUserDataDir; - my $enc_datadir = Slic3r::encode_path($datadir); - Slic3r::debugf "Data directory: %s\n", $datadir; + Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir); + Slic3r::GUI::set_wxapp($self); + + $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 $datadir is not enough: it may be an empty directory + # 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 $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("$datadir/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; + $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($self->{app_config}->get('no_defaults') ? 1 : 0); + eval { $self->{preset_bundle}->load_presets }; + if ($@) { + warn $@ . "\n"; + show_error(undef, $@); } - $Settings->{_}{version} = $Slic3r::VERSION; - $self->save_settings; + eval { $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); + Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || 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 - { - 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; } - $self->{mainframe}->config_wizard if $run_wizard; - eval { $self->{preset_bundle}->load_presets($datadir) }; - -# $self->check_version -# if $self->have_version_check -# && ($Settings->{_}{version_check} // 1) -# && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); - + EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } + $self->{app_config}->save if $self->{app_config}->dirty; }); -# EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub { -# my ($self, $event) = @_; -# my ($success, $response, $manual_check) = @{$event->GetData}; -# -# if ($success) { -# if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) { -# my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?", -# 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal; -# Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES; -# } else { -# Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check; -# } -# $Settings->{_}{last_version_check} = time(); -# $self->save_settings; -# } else { -# Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check; -# } -# }); - return 1; } sub about { my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; @@ -217,7 +139,6 @@ sub about { sub system_info { my ($self) = @_; - my $slic3r_info = Slic3r::slic3r_info(format => 'html'); my $copyright_info = Slic3r::copyright_info(format => 'html'); my $system_info = Slic3r::system_info(format => 'html'); @@ -295,11 +216,6 @@ sub notify { $self->{notifier}->notify($message); } -sub save_settings { - my ($self) = @_; - Slic3r::Config->write_ini("$datadir/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 { @@ -307,64 +223,11 @@ sub update_ui_from_settings { $self->{mainframe}->update_ui_from_settings; } -sub presets { - my ($self, $section) = @_; - - my %presets = (); - opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") - or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; - # Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed - # explicitely to avoid having to bundle the UTF8 Perl library. - foreach my $file (grep /\.[iI][nN][iI]$/, readdir $dh) { - $file = Slic3r::decode_path($file); - my $name = basename($file); - $name =~ s/\.ini$//; - $presets{$name} = "$Slic3r::GUI::datadir/$section/$file"; - } - closedir $dh; - - return %presets; -} - -#sub have_version_check { -# my ($self) = @_; -# -# # return an explicit 0 -# return ($Slic3r::have_threads && $Slic3r::build && $have_LWP) || 0; -#} - -#sub check_version { -# my ($self, $manual_check) = @_; -# -# Slic3r::debugf "Checking for updates...\n"; -# -# @_ = (); -# threads->create(sub { -# my $ua = LWP::UserAgent->new; -# $ua->timeout(10); -# my $response = $ua->get('http://slic3r.org/updatecheck'); -# Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT, -# threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ]))); -# Slic3r::thread_cleanup(); -# })->detach; -#} - -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; @@ -380,31 +243,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) = @_; @@ -434,32 +272,31 @@ sub set_menu_item_icon { # SetBitmap was not available on OS X before Wx 0.9927 if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); + $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); } } 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/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index bb77ebd57..51c822590 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1390,7 +1390,7 @@ sub _load_image_set_texture { my ($self, $file_name) = @_; # Load a PNG with an alpha channel. my $img = Wx::Image->new; - $img->LoadFile($Slic3r::var->($file_name), wxBITMAP_TYPE_PNG); + $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG); # Get RGB & alpha raw data from wxImage, interleave them into a Perl array. my @rgb = unpack 'C*', $img->GetData(); my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 398fde97d..0879ea35b 100644 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ b/lib/Slic3r/GUI/AboutDialog.pm @@ -97,7 +97,7 @@ sub new { my $class = shift; my $self = $class->SUPER::new(@_); - $self->{logo} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); + $self->{logo} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); EVT_PAINT($self, \&repaint); diff --git a/lib/Slic3r/GUI/ConfigWizard.pm b/lib/Slic3r/GUI/ConfigWizard.pm index 3d94f9a68..c18741396 100644 --- a/lib/Slic3r/GUI/ConfigWizard.pm +++ b/lib/Slic3r/GUI/ConfigWizard.pm @@ -14,14 +14,14 @@ our $wizard = 'Wizard'; $wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; sub new { - my $class = shift; - my ($parent) = @_; + my ($class, $parent, $presets) = @_; my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); # initialize an empty repository $self->{config} = Slic3r::Config->new; - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self)); + my $welcome_page = Slic3r::GUI::ConfigWizard::Page::Welcome->new($self); + $self->add_page($welcome_page); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self)); @@ -32,12 +32,13 @@ sub new { $_->build_index for @{$self->{pages}}; + $welcome_page->set_selection_presets([@{$presets}, 'Other']); + return $self; } sub add_page { - my $self = shift; - my ($page) = @_; + my ($self, $page) = @_; my $n = push @{$self->{pages}}, $page; # add first page to the page area sizer @@ -48,13 +49,13 @@ sub add_page { } sub run { - my $self = shift; - + my ($self) = @_; + my $result = undef; if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { - - # it would be cleaner to have these defined inside each page class, - # in some event getting called before leaving the page - { + my $preset_name = $self->{pages}[0]->{preset_name}; + if ($preset_name eq 'Other') { + # it would be cleaner to have these defined inside each page class, + # in some event getting called before leaving the page # set first_layer_height + layer_height based on nozzle_diameter my $nozzle = $self->{config}->nozzle_diameter; $self->{config}->set('first_layer_height', $nozzle->[0]); @@ -66,14 +67,13 @@ sub run { # set first_layer_bed_temperature to temperature + 5 $self->{config}->set('first_layer_bed_temperature', [ ($self->{config}->bed_temperature->[0] > 0) ? ($self->{config}->bed_temperature->[0] + 5) : 0 ]); + $result = $self->{config}; + } else { + $result = $preset_name; } - - $self->Destroy; - return $self->{config}; - } else { - $self->Destroy; - return undef; } + $self->Destroy; + return $result; } package Slic3r::GUI::ConfigWizard::Index; @@ -89,11 +89,11 @@ sub new { push @{$self->{titles}}, $title; $self->{own_index} = 0; - $self->{bullets}->{before} = Wx::Bitmap->new($Slic3r::var->("bullet_black.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{own} = Wx::Bitmap->new($Slic3r::var->("bullet_blue.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{after} = Wx::Bitmap->new($Slic3r::var->("bullet_white.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{before} = Wx::Bitmap->new(Slic3r::var("bullet_black.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{own} = Wx::Bitmap->new(Slic3r::var("bullet_blue.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{after} = Wx::Bitmap->new(Slic3r::var("bullet_white.png"), wxBITMAP_TYPE_PNG); - $self->{background} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); + $self->{background} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); EVT_PAINT($self, \&repaint); @@ -127,6 +127,8 @@ sub repaint { $dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index}; $dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h)); + # Only show the first bullet if this is the only wizard page to be displayed. + last if $i == 0 && $self->{just_welcome}; $i++; } @@ -202,7 +204,7 @@ sub append_option { # populate repository with the factory default my ($opt_key, $opt_index) = split /#/, $full_key, 2; - $self->config->apply(Slic3r::Config->new_from_defaults($opt_key)); + $self->config->apply(Slic3r::Config::new_from_defaults_keys([$opt_key])); # draw the control my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( @@ -263,19 +265,58 @@ sub config { package Slic3r::GUI::ConfigWizard::Page::Welcome; use base 'Slic3r::GUI::ConfigWizard::Page'; +use Wx qw(:misc :sizer wxID_FORWARD); +use Wx::Event qw(EVT_ACTIVATE EVT_CHOICE); sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); + $self->{full_wizard_workflow} = 1; + + $self->append_text('Hello, welcome to Slic3r Prusa Edition! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); + $self->append_text('Please select your printer vendor and printer type. If your printer is not listed, you may try your luck and select a similar one. If you select "Other", this ' . lc($wizard) . ' will let you set the basic 3D printer parameters.'); + # To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); + $self->append_text('If you received a configuration file or a config bundle from your 3D printer vendor, cancel this '.lc($wizard).' and use the "File->Load Config" or "File->Load Config Bundle" menu.'); + + $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); + $self->{vsizer}->Add($choice, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); + + EVT_CHOICE($parent, $choice, sub { + my $sel = $self->{choice}->GetStringSelection; + $self->{preset_name} = $sel; + $self->set_full_wizard_workflow(($sel eq 'Other') || ($sel eq '')); + }); - $self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); - $self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); - $self->append_text('To continue, click Next.'); + EVT_ACTIVATE($parent, sub { + $self->set_full_wizard_workflow($self->{preset_name} eq 'Other'); + }); return $self; } +sub set_full_wizard_workflow { + my ($self, $full_workflow) = @_; + $self->{full_wizard_workflow} = $full_workflow; + $self->{index}->{just_welcome} = !$full_workflow; + $self->{index}->Refresh; + my $next_button = $self->GetParent->FindWindow(wxID_FORWARD); + $next_button->SetLabel($full_workflow ? "&Next >" : "&Finish"); +} + +# Set the preset names, select the first item. +sub set_selection_presets { + my ($self, $names) = @_; + $self->{choice}->Append($names); + $self->{choice}->SetSelection(0); + $self->{preset_name} = $names->[0]; +} + +sub GetNext { + my $self = shift; + return $self->{full_wizard_workflow} ? $self->{next_page} : undef; +} + package Slic3r::GUI::ConfigWizard::Page::Firmware; use base 'Slic3r::GUI::ConfigWizard::Page'; @@ -300,7 +341,7 @@ sub new { $self->append_text('Set the shape of your printer\'s bed, then click Next.'); - $self->config->apply(Slic3r::Config->new_from_defaults('bed_shape')); + $self->config->apply(Slic3r::Config::new_from_defaults_keys(['bed_shape'])); $self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape); $self->{bed_shape_panel}->on_change(sub { $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 8c4dcbec7..6aa7b34cb 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -10,6 +10,7 @@ use utf8; 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); +use List::Util qw(first); __PACKAGE__->mk_accessors(qw(_selected_printer_preset)); @@ -32,27 +33,25 @@ sub new { # button for adding new printer panels { - my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), + my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); $btn->SetToolTipString("Add printer…") if $btn->can('SetToolTipString'); EVT_LEFT_DOWN($btn, sub { - my $menu = Wx::Menu->new; - my %presets = wxTheApp->presets('printer'); - + my $menu = Wx::Menu->new; + my @panels = $self->print_panels; # 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}); - next if !$config->serial_port; - + # update configs of currently loaded print panels + foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) { + my $preset_name = $preset->name; + next if ! $preset->config->serial_port || + defined first { defined $_ && $_->printer_name eq $preset_name } @panels; + my $myconfig = $preset->config->clone_only(\@ConfigOptions); my $id = &Wx::NewId(); $menu->Append($id, $preset_name); EVT_MENU($menu, $id, sub { - $self->add_printer($preset_name, $config); + $self->add_printer($preset_name, $myconfig); }); } $self->PopupMenu($menu, $btn->GetPosition); @@ -98,12 +97,8 @@ sub OnActivate { my ($self) = @_; # get all available presets - my %presets = (); - { - my %all = wxTheApp->presets('printer'); - my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all; - %presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all; - } + my %presets = map { $_->name => $_->config->clone_only(\@ConfigOptions) } + grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer}; # decide which ones we want to keep my %active = (); @@ -121,7 +116,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; } @@ -177,26 +172,19 @@ sub print_panels { map $_->GetWindow, $self->{sizer}->GetChildren; } -# Called by -# Slic3r::GUI::Tab::Print::_on_presets_changed -# Slic3r::GUI::Tab::Filament::_on_presets_changed -# Slic3r::GUI::Tab::Printer::_on_presets_changed -# when the presets are loaded or the user select another preset. +# Called by Slic3r::GUI::Tab::Printer::_on_presets_changed +# when the presets are loaded or the user selects another preset. sub update_presets { - my $self = shift; - my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_; - + my ($self, $presets) = @_; # update configs of currently loaded print panels + my @presets = @$presets; foreach my $panel ($self->print_panels) { - foreach my $preset (@$presets) { - if ($panel->printer_name eq $preset->name) { - my $config = $preset->config(\@ConfigOptions); - $panel->config->apply($config); - } - } + my $preset = $presets->find_preset($panel->printer_name, 0); + $panel->config($preset->config->clone_only(\@ConfigOptions)) + if defined $preset; } - - $self->_selected_printer_preset($presets->[$selected]->name); + + $self->_selected_printer_preset($presets->get_selected_preset->name); } 1; diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm index 8dcc05424..de0565255 100644 --- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm +++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm @@ -34,7 +34,7 @@ sub new { my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("$icon.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("$icon.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapPosition($pos); EVT_BUTTON($self, $btn, $handler); $sizer->Add($btn, 1, wxEXPAND | wxALL, 0); diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm index 6dba614e3..794ea4e4e 100644 --- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm +++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm @@ -103,7 +103,7 @@ sub new { $serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1); } { - $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), + $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE); $btn->SetToolTipString("Rescan serial ports") if $btn->can('SetToolTipString'); @@ -127,7 +127,7 @@ sub new { { $self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG)); $serial_speed_sizer->Add($btn, 0, wxLEFT, 5); EVT_BUTTON($self, $btn, \&disconnect); } @@ -140,7 +140,7 @@ sub new { my $font = $btn->GetFont; $font->SetPointSize($font->GetPointSize + 2); $btn->SetFont($font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("arrow_up.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG)); $left_sizer->Add($btn, 0, wxTOP, 15); EVT_BUTTON($self, $btn, \&connect); } @@ -160,7 +160,7 @@ sub new { { $self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG)); $btn->Hide; $left_sizer->Add($btn, 0, wxTOP, 15); EVT_BUTTON($self, $btn, sub { @@ -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); } @@ -577,7 +577,7 @@ sub new { $btn->SetToolTipString("Delete this job from print queue") if $btn->can('SetToolTipString'); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG)); if ($job->printing) { $btn->Hide; } @@ -597,8 +597,8 @@ sub new { my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, $button_style); $btn->SetFont($Slic3r::GUI::small_bold_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG)); #$btn->SetBitmapPosition(wxRIGHT); $btn->Hide; $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); @@ -616,8 +616,8 @@ sub new { if (!$job->printing || $job->paused) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_pause.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_pause_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { @@ -633,8 +633,8 @@ sub new { if (!$job->printing || !$job->paused) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { @@ -650,8 +650,8 @@ sub new { if (!$job->printing) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_stop_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index ad2eca91d..a6baef8f9 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -6,6 +6,7 @@ use warnings; use utf8; use File::Basename qw(basename dirname); +use FindBin; use List::Util qw(min); use Slic3r::Geometry qw(X Y); use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog @@ -21,13 +22,14 @@ sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); + Slic3r::GUI::set_main_frame($self); if ($^O eq 'MSWin32') { - # Load the icon either from the exe, or fron the ico file. - my $iconfile = $Slic3r::var->('..\slic3r.exe'); - $iconfile = $Slic3r::var->("Slic3r.ico") unless -f $iconfile; + # Load the icon either from the exe, or from the ico file. + my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe'; + $iconfile = Slic3r::var("Slic3r.ico") unless -f $iconfile; $self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO)); } else { - $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); + $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); } # store input params @@ -69,15 +71,15 @@ sub new { # declare events EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { $event->Veto; return; } - # 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; }); @@ -91,6 +93,8 @@ sub _init_tabpanel { my ($self) = @_; $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + Slic3r::GUI::set_tab_panel($panel); + EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub { my $panel = $self->{tabpanel}->GetCurrentPage; $panel->OnActivate if $panel->can('OnActivate'); @@ -112,7 +116,7 @@ sub _init_tabpanel { # Callback to be executed after any of the configuration fields (Perl class Slic3r::GUI::OptionsGroup::Field) change their value. $tab->on_value_change(sub { my ($opt_key, $value) = @_; - my $config = $tab->config; + my $config = $tab->{presets}->get_current_preset->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'; @@ -126,24 +130,38 @@ sub _init_tabpanel { if ($self->{plater}) { # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. $self->{plater}->update_presets($tab_name, @_); - $self->{plater}->on_config_change($tab->config); - if ($self->{controller}) { - $self->{controller}->update_presets($tab_name, @_); + if ($tab_name eq 'printer') { + # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. + wxTheApp->{preset_bundle}->print->update_tab_ui( + $self->{options_tabs}{'print'}->{presets_choice}, + $self->{options_tabs}{'print'}->{show_incompatible_presets}); + wxTheApp->{preset_bundle}->filament->update_tab_ui( + $self->{options_tabs}{'filament'}->{presets_choice}, + $self->{options_tabs}{'filament'}->{show_incompatible_presets}); + # Update the controller printers. + $self->{controller}->update_presets(@_) if $self->{controller}; } + $self->{plater}->on_config_change($tab->{presets}->get_current_preset->config); } }); - $tab->load_presets; + # Load the currently selected preset into the GUI, update the preset selection box. + $tab->load_current_preset; $panel->AddPage($tab, $tab->title); } + +#TODO this is an example of a Slic3r XS interface call to add a new preset editor page to the main view. +# Slic3r::GUI::create_preset_tab("print"); if ($self->{plater}) { $self->{plater}->on_select_preset(sub { - my ($group, $i) = @_; - $self->{options_tabs}{$group}->select_preset($i); + my ($group, $name) = @_; + $self->{options_tabs}{$group}->select_preset($name); }); - # load initial config - $self->{plater}->on_config_change($self->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})); } } @@ -153,6 +171,9 @@ sub _init_menubar { # File menu my $fileMenu = Wx::Menu->new; { + wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub { + $self->{plater}->add if $self->{plater}; + }, undef, undef); #'brick_add.png'); $self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->load_config_file; }, undef, 'plugin_add.png'); @@ -262,13 +283,14 @@ sub _init_menubar { # \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, # as the simple numeric accelerators spoil all numeric data entry. # The camera control accelerators are captured by 3DScene Perl module instead. - $self->_append_menu_item($self->{viewMenu}, "Iso\t\xA00" , 'Iso View' , sub { $self->select_view('iso' ); }); - $self->_append_menu_item($self->{viewMenu}, "Top\t\xA01" , 'Top View' , sub { $self->select_view('top' ); }); - $self->_append_menu_item($self->{viewMenu}, "Bottom\t\xA02" , 'Bottom View' , sub { $self->select_view('bottom' ); }); - $self->_append_menu_item($self->{viewMenu}, "Front\t\xA03" , 'Front View' , sub { $self->select_view('front' ); }); - $self->_append_menu_item($self->{viewMenu}, "Rear\t\xA04" , 'Rear View' , sub { $self->select_view('rear' ); }); - $self->_append_menu_item($self->{viewMenu}, "Left\t\xA05" , 'Left View' , sub { $self->select_view('left' ); }); - $self->_append_menu_item($self->{viewMenu}, "Right\t\xA06" , 'Right View' , sub { $self->select_view('right' ); }); + my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; + $self->_append_menu_item($self->{viewMenu}, $accel->('Iso', '0'), 'Iso View' , sub { $self->select_view('iso' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Top', '1'), 'Top View' , sub { $self->select_view('top' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Bottom', '2'), 'Bottom View' , sub { $self->select_view('bottom' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Front', '3'), 'Front View' , sub { $self->select_view('front' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Rear', '4'), 'Rear View' , sub { $self->select_view('rear' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Left', '5'), 'Left View' , sub { $self->select_view('left' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Right', '6'), 'Right View' , sub { $self->select_view('right' ); }); } # Help menu @@ -317,6 +339,8 @@ sub _init_menubar { $menubar->Append($windowMenu, "&Window"); $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; $menubar->Append($helpMenu, "&Help"); + # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. + Slic3r::GUI::add_debug_menu($menubar); $self->SetMenuBar($menubar); } } @@ -329,7 +353,6 @@ sub is_loaded { # Selection of a 3D object changed on the platter. sub on_plater_selection_changed { my ($self, $have_selection) = @_; - return if !defined $self->{object_menu}; $self->{object_menu}->Enable($_->GetId, $have_selection) for $self->{object_menu}->GetMenuItems; @@ -337,20 +360,20 @@ sub on_plater_selection_changed { # To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". sub quick_slice { - my $self = shift; - my %params = @_; + my ($self, %params) = @_; 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; @@ -372,8 +395,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; { @@ -395,20 +417,19 @@ 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}) { $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { + # The following line may die if the output_filename_format template substitution fails. $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; @@ -416,8 +437,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; } @@ -452,9 +472,7 @@ sub quick_slice { sub reslice_now { my ($self) = @_; - if ($self->{plater}) { - $self->{plater}->reslice; - } + $self->{plater}->reslice if $self->{plater}; } sub repair_stl { @@ -462,8 +480,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; @@ -492,36 +511,25 @@ sub repair_stl { Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); } -sub extra_variables { - my $self = shift; - my %extra_variables = (); - $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name - for qw(print filament printer); - return { %extra_variables }; -} - sub export_config { my $self = shift; - - my $config = $self->config; - eval { - # validate configuration - $config->validate; - }; + # Generate a cummulative configuration for the selected print, filaments and printer. + my $config = wxTheApp->{preset_bundle}->full_config(); + # Validate the cummulative configuration. + eval { $config->validate; }; Slic3r::GUI::catch_error($self) and return; - - 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, + # Ask user for the file name for the config file. + 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); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; + $dlg->Destroy; + if (defined $file) { + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; $config->save($file); } - $dlg->Destroy; } # Load a config file containing a Print, Filament & Printer preset. @@ -529,222 +537,139 @@ 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; } - for my $tab (values %{$self->{options_tabs}}) { - # Dont proceed further if the config file cannot be loaded. - return undef if ! $tab->load_config_file($file); - } - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + eval { wxTheApp->{preset_bundle}->load_config_file($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}}); + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; } sub export_configbundle { - my $self = shift; - - eval { - # validate current configuration in case it's dirty - $self->config->validate; - }; + my ($self) = @_; + return unless $self->check_unsaved_changes; + # validate current configuration in case it's dirty + eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; - - 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, + # Ask user for a file name. + 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); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; - - # 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); - $ini->{presets} = $Slic3r::GUI::Settings->{presets}; - - 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->{_}; - } - } - - Slic3r::Config->write_ini($file, $ini); - } + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; + if (defined $file) { + # Export the config bundle. + 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; - - # load .ini file - my $ini = Slic3r::Config->read_ini($file); - - if ($ini->{settings}) { - $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; - wxTheApp->save_settings; - } - if ($ini->{presets}) { - $Slic3r::GUI::Settings->{presets} = $ini->{presets}; - wxTheApp->save_settings; - } + wxTheApp->{app_config}->update_config_dir(dirname($file)); - my $imported = 0; - INI_BLOCK: foreach my $ini_category (sort keys %$ini) { - next unless $ini_category =~ /^(print|filament|printer):(.+)$/; - my ($section, $preset_name) = ($1, $2); - my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); - next if $skip_no_id && !$config->get($section . "_settings_id"); - - { - my %current_presets = Slic3r::GUI->presets($section); - my %current_ids = map { $_ => 1 } - grep $_, - map Slic3r::Config->load($_)->get($section . "_settings_id"), - values %current_presets; - next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; - } - - $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name); - Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name; - $imported++; - } + my $presets_imported = 0; + 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. foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; + $tab->load_current_preset; } - return if !$imported; - - my $message = sprintf "%d presets successfully imported.", $imported; + my $message = sprintf "%d presets successfully imported.", $presets_imported; Slic3r::GUI::show_info($self, $message); } # Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. # Also update the platter with the new presets. sub load_config { - my $self = shift; - my ($config) = @_; - - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config); - } - if ($self->{plater}) { - $self->{plater}->on_config_change($config); - } + my ($self, $config) = @_; + $_->load_config($config) foreach values %{$self->{options_tabs}}; + $self->{plater}->on_config_change($config) if $self->{plater}; } 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}}) { - $tab->select_default_preset; - } - $self->load_config($config); - for my $tab (values %{$self->{options_tabs}}) { - $tab->save_preset('My Settings'); + # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. + my $directory = Slic3r::resources_dir() . "/profiles"; + my @profiles = (); + if (opendir(DIR, Slic3r::encode_path($directory))) { + while (my $file = readdir(DIR)) { + if ($file =~ /\.ini$/) { + $file =~ s/\.ini$//; + push @profiles, Slic3r::decode_path($file); + } } + closedir(DIR); } -} - -=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) { - $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); + # Open the wizard. + if (my $config = Slic3r::GUI::ConfigWizard->new($self, \@profiles)->run) { + if (ref($config)) { + # Wizard returned a config. Add the config to each of the preset types. + for my $tab (values %{$self->{options_tabs}}) { + # Select the first visible preset, force. + $tab->select_preset(undef, 1); } - if (!$filament_config) { - $filament_config = $config->clone; - next; + # 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'); } - 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); + } else { + # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. + eval { wxTheApp->{preset_bundle}->load_configbundle($directory . '/' . $config . '.ini'); }; + Slic3r::GUI::catch_error($self) and return; + # Load the currently selected preset into the GUI, update the preset selection box. + foreach my $tab (values %{$self->{options_tabs}}) { + $tab->load_current_preset; } } } - - my $config = Slic3r::Config->merge( - Slic3r::Config->new_from_defaults, - $self->{options_tabs}{print}->config, - $self->{options_tabs}{printer}->config, - $filament_config, - ); - - 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) = @_; - return map $self->{options_tabs}{filament}->get_preset($_)->name, - $self->{plater}->filament_presets; } +# This is called when closing the application, when loading a config file or when starting the config wizard +# to notify the user whether he is aware that some preset changes will be lost. sub check_unsaved_changes { my $self = shift; my @dirty = (); foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->is_dirty; + push @dirty, $tab->title if $tab->{presets}->current_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 $confirm->ShowModal == wxID_YES; } return 1; @@ -765,21 +690,18 @@ sub select_view { sub _append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; - $id //= &Wx::NewId(); my $item = $menu->Append($id, $string, $description); $self->_set_menu_item_icon($item, $icon); - EVT_MENU($self, $id, $cb); return $item; } sub _set_menu_item_icon { my ($self, $menuItem, $icon) = @_; - # SetBitmap was not available on OS X before Wx 0.9927 if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); + $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); } } @@ -787,8 +709,11 @@ 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}); + for my $tab_name (qw(print filament printer)) { + $self->{options_tabs}{$tab_name}->update_ui_from_settings; + } } 1; diff --git a/lib/Slic3r/GUI/Notifier.pm b/lib/Slic3r/GUI/Notifier.pm index eb548d014..54afffae0 100644 --- a/lib/Slic3r/GUI/Notifier.pm +++ b/lib/Slic3r/GUI/Notifier.pm @@ -6,7 +6,7 @@ use Moo; has 'growler' => (is => 'rw'); -my $icon = $Slic3r::var->("Slic3r.png"); +my $icon = Slic3r::var("Slic3r.png"); sub BUILD { my ($self) = @_; 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 0f56da7ca..f687dcec4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -46,15 +46,14 @@ use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds my $PreventListEvents = 0; sub new { - my $class = shift; - my ($parent) = @_; + my ($class, $parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{config} = Slic3r::Config->new_from_defaults(qw( + $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour - )); + )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm @@ -64,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 @@ -105,6 +99,7 @@ sub new { $self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_double_click($on_double_click); $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); + $self->{canvas3D}->set_on_arrange(sub { $self->arrange }); $self->{canvas3D}->set_on_rotate_object_left(sub { $self->rotate(-45, Z, 'relative') }); $self->{canvas3D}->set_on_rotate_object_right(sub { $self->rotate( 45, Z, 'relative') }); $self->{canvas3D}->set_on_scale_object_uniformly(sub { $self->changescale(undef) }); @@ -120,7 +115,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. @@ -166,22 +161,22 @@ sub new { if (!&Wx::wxMSW) { Wx::ToolTip::Enable(1); $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); - $self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new($Slic3r::var->("cross.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new($Slic3r::var->("bricks.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); + $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); } else { my %tbar_buttons = ( add => "Add…", @@ -256,7 +251,7 @@ sub new { settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { - $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icons{$_}), wxBITMAP_TYPE_PNG)); + $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG)); } $self->selection_changed(0); $self->object_list_changed; @@ -331,7 +326,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 { @@ -366,20 +361,17 @@ sub new { # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - # Boolean indicating whether the '- default -' preset is shown by the combo box. - $self->{preset_choosers_default_suppressed} = {}; 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); EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); $self->{preset_choosers}{$group} = [$choice]; - $self->{preset_choosers_default_suppressed}{$group} = 0; # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice); + $self->_on_select_preset($group, $choice, 0); }); }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); @@ -414,7 +406,7 @@ sub new { $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new($Slic3r::var->("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); $self->{object_info_manifold_warning_icon}->Hide; my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -438,6 +430,7 @@ sub new { $grid_sizer->AddGrowableCol(3, 1); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( + fil_m => "Used Filament (m)", fil_mm3 => "Used Filament (mm^3)", fil_g => "Used Filament (g)", cost => "Cost", @@ -453,7 +446,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); @@ -505,30 +497,26 @@ sub on_select_preset { $self->{on_select_preset} = $cb; } +# Called from the platter combo boxes selecting the active print, filament or printer. sub _on_select_preset { - my $self = shift; - my ($group, $choice) = @_; - + my ($self, $group, $choice, $idx) = @_; # If user changed a filament preset and the selected machine is equipped with multiple extruders, # there are multiple filament selection combo boxes shown at the platter. In that case # don't propagate the filament selection changes to the tab. - my $default_suppressed = $self->{preset_choosers_default_suppressed}{$group}; + if ($group eq 'filament') { + wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); + } if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - # Indices of the filaments selected. - my @filament_presets = $self->filament_presets; - $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0] - $default_suppressed) . ".ini"; - $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_] - $default_suppressed) - for 1 .. $#filament_presets; - wxTheApp->save_settings; - $self->update_filament_colors_preview($choice); + wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, scalar($choice->GetSelection) + $default_suppressed) + $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($self->GetFrame->config); + $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } sub on_layer_editing_toggled { @@ -558,8 +546,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; } } @@ -573,128 +561,41 @@ sub update_ui_from_settings # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - my $self = shift; - # $presets: one of qw(print filament printer) - # $selected: index of the selected preset in the array. This may not correspond - # with the index of selection in the UI element, where not all items are displayed. - my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_; - - my @choosers = @{ $self->{preset_choosers}{$group} }; - my $choice_idx = 0; - 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 + $self->{preset_choosers_default_suppressed}{$group}; - $is_dirty = 0; - } - $choice->Clear; - foreach my $preset (@$presets) { - next if ($preset->default && $default_suppressed); - my $bitmap; - if ($group eq 'filament') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); - } 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 ($selected <= $#$presets) { - my $idx = $selected - $default_suppressed; - if ($idx >= 0) { - if ($is_dirty) { - $choice->SetString($idx, $choice->GetString($idx) . " (modified)"); - } - # call SetSelection() only after SetString() otherwise the new string - # won't be picked up as the visible string - $choice->SetSelection($idx); - } + # $group: one of qw(print filament printer) + # $presets: PresetCollection + my ($self, $group, $presets) = @_; + my @choosers = @{$self->{preset_choosers}{$group}}; + if ($group eq 'filament') { + my $choice_idx = 0; + if (int(@choosers) == 1) { + # Single filament printer, synchronize the filament presets. + wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); } - $choice_idx += 1; - } - - $self->{preset_choosers_default_suppressed}{$group} = $default_suppressed; - - wxTheApp->CallAfter(sub { $self->update_filament_colors_preview }) if $group eq 'filament' || $group eq 'printer'; -} - -# Update the color icon in front of each filament selection on the platter. -# If the extruder has a preview color assigned, apply the extruder color to the active selection. -# Always apply the filament color to the non-active selections. -sub update_filament_colors_preview { - my ($self, $extruder_idx) = shift; - - my @choosers = @{$self->{preset_choosers}{filament}}; - - if (ref $extruder_idx) { - # $extruder_idx is the chooser. - foreach my $chooser (@choosers) { - if ($extruder_idx == $chooser) { - $extruder_idx = $chooser; - last; - } + foreach my $choice (@choosers) { + wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + $choice_idx += 1; } - } - - my @extruder_colors = @{$self->{config}->extruder_colour}; - - my @extruder_list; - if (defined $extruder_idx) { - @extruder_list = ($extruder_idx); - } else { - # Collect extruder indices. - @extruder_list = (0..$#extruder_colors); - } - - my $filament_tab = $self->GetFrame->{options_tabs}{filament}; - my $presets = $filament_tab->{presets}; - my $default_suppressed = $filament_tab->{default_suppressed}; - - foreach my $extruder_idx (@extruder_list) { - my $chooser = $choosers[$extruder_idx]; - my $extruder_color = $self->{config}->extruder_colour->[$extruder_idx]; - my $preset_idx = 0; - my $selection_idx = $chooser->GetSelection; - foreach my $preset (@$presets) { - my $bitmap; - if ($preset->default) { - next if $default_suppressed; - } else { - # Assign an extruder color to the selected item if the extruder color is defined. - my $filament_rgb = $preset->config(['filament_colour'])->filament_colour->[0]; - my $extruder_rgb = ($preset_idx == $selection_idx && $extruder_color =~ m/^#[[:xdigit:]]{6}/) ? $extruder_color : $filament_rgb; - $filament_rgb =~ s/^#//; - $extruder_rgb =~ s/^#//; - my $image = Wx::Image->new(24,16); - if ($filament_rgb ne $extruder_rgb) { - my @rgb = unpack 'C*', pack 'H*', $extruder_rgb; - $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); - @rgb = unpack 'C*', pack 'H*', $filament_rgb; - $image->SetRGB(Wx::Rect->new(16,0,8,16), @rgb); - } else { - my @rgb = unpack 'C*', pack 'H*', $filament_rgb; - $image->SetRGB(Wx::Rect->new(0,0,24,16), @rgb); - } - $bitmap = Wx::Bitmap->new($image); - } - $chooser->SetItemBitmap($preset_idx, $bitmap) if $bitmap; - $preset_idx += 1; + } elsif ($group eq 'print') { + wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'printer') { + # Update the print choosers to only contain the compatible presets, update the dirty flags. + wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); + # Update the printer choosers, update the dirty flags. + wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); + # Update the filament choosers to only contain the compatible presets, update the color preview, + # update the dirty flags. + my $choice_idx = 0; + foreach my $choice (@{$self->{preset_choosers}{filament}}) { + wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + $choice_idx += 1; } } -} - -# Return a vector of indices of filaments selected by the $self->{preset_choosers}{filament} combo boxes. -sub filament_presets { - my $self = shift; - # force scalar context for GetSelection() as it's context-aware - return map scalar($_->GetSelection) + $self->{preset_choosers_default_suppressed}{filament}, @{ $self->{preset_choosers}{filament} }; + # Synchronize config.ini with the current selections. + wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); } sub add { - my $self = shift; + my ($self) = @_; my @input_files = wxTheApp->open_model($self); $self->load_files(\@input_files); } @@ -759,8 +660,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)); @@ -808,7 +708,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; } @@ -921,7 +821,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; @@ -1164,7 +1064,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(wxTheApp->{preset_bundle}->full_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 @@ -1225,7 +1125,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(wxTheApp->{preset_bundle}->full_config); # Just redraw the 3D canvas without reloading the scene. # $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled); @@ -1234,7 +1134,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; @@ -1256,7 +1156,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}; @@ -1267,7 +1166,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; + wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; if ($@) { @@ -1275,11 +1174,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 @_ = (); @@ -1350,7 +1246,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. @@ -1379,66 +1275,57 @@ 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; + wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print - my $config = $self->GetFrame->config; + my $config = wxTheApp->{preset_bundle}->full_config; eval { # this will throw errors if config is not valid $config->validate; $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); + $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) }; + Slic3r::GUI::catch_error($self) and return; } 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 $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return; + 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; @@ -1542,6 +1429,7 @@ sub on_export_completed { $self->{"print_info_fil_g"}->SetLabel(sprintf("%.2f" , $self->{print}->total_weight)); $self->{"print_info_fil_mm3"}->SetLabel(sprintf("%.2f" , $self->{print}->total_extruded_volume)); $self->{"print_info_time"}->SetLabel(sprintf("%.2f" , $self->{print}->estimated_print_time)); + $self->{"print_info_fil_m"}->SetLabel(sprintf("%.2f" , $self->{print}->total_used_filament / 1000)); $self->{"print_info_box_show"}->(1); # this updates buttons status @@ -1551,14 +1439,12 @@ 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}; - my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config); + my $printer_preset = wxTheApp->{preset_bundle}->printer->get_edited_preset; + my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config); my $filament_stats = $self->{print}->filament_stats; - my @filament_names = $self->GetFrame->filament_preset_names; + my @filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); @@ -1599,11 +1485,11 @@ sub send_gcode { } sub export_stl { - my $self = shift; - + my ($self) = @_; return if !@{$self->{objects}}; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('STL') or return; + # Store a binary STL. $self->{model}->store_stl($output_file, 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); } @@ -1615,6 +1501,7 @@ sub reload_from_disk { return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; + #FIXME convert to local file encoding return if !$model_object->input_file || !-e $model_object->input_file; @@ -1625,59 +1512,55 @@ sub reload_from_disk { my $o = $self->{model}->objects->[$new_obj_idx]; $o->clear_instances; $o->add_instance($_) for @{$model_object->instances}; + #$o->invalidate_bounding_box; if ($o->volumes_count == $model_object->volumes_count) { for my $i (0..($o->volumes_count-1)) { $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); } } + #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, } $self->remove($obj_idx); } sub export_object_stl { - my $self = shift; - + my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; - my $model_object = $self->{model}->objects->[$obj_idx]; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('STL') or return; $model_object->mesh->write_binary($output_file); $self->statusbar->SetStatusText("STL file exported to $output_file"); } sub export_amf { - my $self = shift; - + my ($self) = @_; return if !@{$self->{objects}}; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('AMF') or return; $self->{model}->store_amf($output_file); $self->statusbar->SetStatusText("AMF file exported to $output_file"); } +# Ask user to select an output file for a given file format (STl, AMF, 3MF). +# Propose a default file name based on the 'output_filename_format' configuration value. sub _get_export_file { - my $self = shift; - my ($format) = @_; - + my ($self, $format) = @_; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; - - my $output_file = $main::opt{output}; - { - $output_file = $self->{print}->output_filepath($output_file); - $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; - my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), - basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return undef; - } - $output_file = $dlg->GetPath; + my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return undef; + $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; + my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), + basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; + return undef; } + $output_file = $dlg->GetPath; + $dlg->Destroy; return $output_file; } @@ -1691,7 +1574,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); } @@ -1714,11 +1597,13 @@ sub update { } # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. +# Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly +# 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}; - while (@$choices < $num_extruders) { + + while (int(@$choices) < $num_extruders) { # copy strings from first choice my @presets = $choices->[0]->GetStrings; @@ -1727,25 +1612,20 @@ sub on_extruders_change { my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; - # 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); - # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice); + $self->_on_select_preset('filament', $choice, $extruder_idx); }); }); - # initialize selection - my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; - $choice->SetSelection($i || 0); + wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice); } # remove unused choices if any @@ -1759,8 +1639,7 @@ sub on_extruders_change { } sub on_config_change { - my $self = shift; - my ($config) = @_; + my ($self, $config) = @_; my $update_scheduled; foreach my $opt_key (@{$self->{config}->diff($config)}) { @@ -1817,7 +1696,6 @@ sub on_config_change { sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; - if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; @@ -1829,7 +1707,6 @@ sub list_item_deselected { sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; - my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; @@ -1861,19 +1738,18 @@ sub filament_color_box_lmouse_down my $dialog = Wx::ColourDialog->new($self->GetFrame, $data); if ($dialog->ShowModal == wxID_OK) { my $cfg = Slic3r::Config->new; - my $colors = $self->GetFrame->config->get('extruder_colour'); + my $colors = wxTheApp->{preset_bundle}->full_config->get('extruder_colour'); $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); $cfg->set('extruder_colour', $colors); $self->GetFrame->{options_tabs}{printer}->load_config($cfg); - $self->update_filament_colors_preview($extruder_idx); + wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $combobox); } $dialog->Destroy(); } } sub object_cut_dialog { - my $self = shift; - my ($obj_idx) = @_; + my ($self, $obj_idx) = @_; if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; @@ -1898,23 +1774,20 @@ sub object_cut_dialog { } sub object_settings_dialog { - my $self = shift; - my ($obj_idx) = @_; - - if (!defined $obj_idx) { - ($obj_idx, undef) = $self->selected_object; - } + my ($self, $obj_idx) = @_; + ($obj_idx, undef) = $self->selected_object if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; # validate config before opening the settings dialog because # that dialog can't be closed if validation fails, but user # can't fix any error which is outside that dialog - return unless $self->validate_config; + eval { wxTheApp->{preset_bundle}->full_config->validate; }; + return if Slic3r::GUI::catch_error($_[0]); my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], model_object => $model_object, - config => $self->GetFrame->config, + config => wxTheApp->{preset_bundle}->full_config, ); $self->pause_background_process; $dlg->ShowModal; @@ -1965,9 +1838,9 @@ sub object_list_changed { for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } +# Selection of an active 3D object changed. sub selection_changed { - my $self = shift; - + my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; @@ -2028,7 +1901,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 @@ -2042,26 +1914,13 @@ sub select_object { } sub selected_object { - my $self = shift; - + my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; - return undef if !defined $obj_idx; - return ($obj_idx, $self->{objects}[$obj_idx]), -} - -sub validate_config { - my $self = shift; - - eval { - $self->GetFrame->config->validate; - }; - return 0 if Slic3r::GUI::catch_error($self); - return 1; + return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } sub statusbar { - my $self = shift; - return $self->GetFrame->{statusbar}; + return $_[0]->GetFrame->{statusbar}; } sub object_menu { @@ -2069,23 +1928,24 @@ sub object_menu { my $frame = $self->GetFrame; my $menu = Wx::Menu->new; - $frame->_append_menu_item($menu, "Delete\t\xA0Del", 'Remove the selected object', sub { + my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; + $frame->_append_menu_item($menu, $accel->('Delete', 'Del'), 'Remove the selected object', sub { $self->remove; }, undef, 'brick_delete.png'); - $frame->_append_menu_item($menu, "Increase copies\t\xA0+", 'Place one more copy of the selected object', sub { + $frame->_append_menu_item($menu, $accel->('Increase copies', '+'), 'Place one more copy of the selected object', sub { $self->increase; }, undef, 'add.png'); - $frame->_append_menu_item($menu, "Decrease copies\t\xA0-", 'Remove one copy of the selected object', sub { + $frame->_append_menu_item($menu, $accel->('Decrease copies', '-'), 'Remove one copy of the selected object', sub { $self->decrease; }, undef, 'delete.png'); $frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub { $self->set_number_of_copies; }, undef, 'textfield.png'); $menu->AppendSeparator(); - $frame->_append_menu_item($menu, "Rotate 45° clockwise\t\xA0l", 'Rotate the selected object by 45° clockwise', sub { + $frame->_append_menu_item($menu, $accel->('Rotate 45° clockwise', 'l'), 'Rotate the selected object by 45° clockwise', sub { $self->rotate(-45, Z, 'relative'); }, undef, 'arrow_rotate_clockwise.png'); - $frame->_append_menu_item($menu, "Rotate 45° counter-clockwise\t\xA0r", 'Rotate the selected object by 45° counter-clockwise', sub { + $frame->_append_menu_item($menu, $accel->('Rotate 45° counter-clockwise', 'r'), 'Rotate the selected object by 45° counter-clockwise', sub { $self->rotate(+45, Z, 'relative'); }, undef, 'arrow_rotate_anticlockwise.png'); @@ -2118,7 +1978,7 @@ sub object_menu { my $scaleMenu = Wx::Menu->new; my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); $frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); - $frame->_append_menu_item($scaleMenu, "Uniformly…\t\xA0s", 'Scale the selected object along the XYZ axes', sub { + $frame->_append_menu_item($scaleMenu, $accel->('Uniformly…', 's'), 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef); }); $frame->_append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { @@ -2187,24 +2047,19 @@ use Wx::DND; use base 'Wx::FileDropTarget'; sub new { - my $class = shift; - my ($window) = @_; + my ($class, $window) = @_; my $self = $class->SUPER::new; $self->{window} = $window; return $self; } sub OnDropFiles { - my $self = shift; - my ($x, $y, $filenames) = @_; - + my ($self, $x, $y, $filenames) = @_; # stop scalars leaking on older perl # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 @_ = (); - # only accept STL, OBJ and AMF files return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; - $self->{window}->load_files($filenames); } @@ -2220,10 +2075,8 @@ has 'selected' => (is => 'rw', default => sub { 0 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; - # make method idempotent $self->thumbnail->clear; - # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. my $mesh = $model->objects->[$obj_idx]->raw_mesh; #FIXME The "correct" variant could be extremely slow. @@ -2239,7 +2092,6 @@ sub make_thumbnail { my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); $self->thumbnail->append($convex_hull); # } - return $self->thumbnail; } 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/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 503a3d159..1c123e741 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -9,7 +9,7 @@ use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR); use base qw(Slic3r::GUI::3DScene Class::Accessor); __PACKAGE__->mk_accessors(qw( - on_rotate_object_left on_rotate_object_right on_scale_object_uniformly + on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly on_remove_object on_increase_objects on_decrease_objects)); sub new { @@ -88,7 +88,9 @@ sub new { $event->Skip; } else { my $key = $event->GetKeyCode; - if ($key == ord('l')) { + if ($key == ord('a')) { + $self->on_arrange->() if $self->on_arrange; + } elsif ($key == ord('l')) { $self->on_rotate_object_left->() if $self->on_rotate_object_left; } elsif ($key == ord('r')) { $self->on_rotate_object_right->() if $self->on_rotate_object_right; @@ -122,6 +124,11 @@ sub set_on_right_click { $self->on_right_click($cb); } +sub set_on_arrange { + my ($self, $cb) = @_; + $self->on_arrange($cb); +} + sub set_on_rotate_object_left { my ($self, $cb) = @_; $self->on_rotate_object_left($cb); diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 707929e33..8a8e6064c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -42,9 +42,9 @@ sub new { { $self->{tree_icons} = Wx::ImageList->new(16, 16, 1); $tree->AssignImageList($self->{tree_icons}); - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH my $rootId = $tree->AddRoot("Object", ICON_OBJECT); $tree->SetPlData($rootId, { type => 'object' }); @@ -58,13 +58,13 @@ sub new { $self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); - $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_delete}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_split}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); # buttons sizer my $buttons_sizer = Wx::GridSizer->new(2, 3); @@ -312,7 +312,7 @@ sub selection_changed { $config = $self->{model_object}->config; } # get default values - my $default_config = Slic3r::Config->new_from_defaults(@opt_keys); + my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); # append default extruder push @opt_keys, 'extruder'; @@ -490,12 +490,12 @@ sub CanClose { # validate options before allowing user to dismiss the dialog # the validate method only works on full configs so we have # to merge our settings with the default ones - my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config); + my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone; eval { + $config->apply($self->model_object->config); $config->validate; }; - return 0 if Slic3r::GUI::catch_error($self); - return 1; + return ! Slic3r::GUI::catch_error($self); } sub PartsChanged { diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 2f8b8ce73..3b10ed99f 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -46,7 +46,7 @@ sub new { # option selector { # create the button - my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), + my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; @@ -146,7 +146,7 @@ sub update_optgroup { # disallow deleting fixed options return undef if $self->{fixed_options}{$opt_key}; - my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), + my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_BUTTON($self, $btn, sub { $self->{config}->erase($opt_key); diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index b19d623cd..d20ccb53f 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,43 @@ 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"), + )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'show_incompatible_presets', + type => 'bool', + label => 'Show incompatible print and filament presets', + tooltip => 'When checked, the print and filament presets are shown in the preset editor even ' . + 'if they are marked as incompatible with the active printer', + default => $app_config->get("show_incompatible_presets"), )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); @@ -79,14 +87,15 @@ sub new { } sub _accept { - my $self = shift; + my ($self) = @_; - if (defined($self->{values}{no_controller})) { + 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 fb9d7e8b0..30429e88b 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -20,16 +20,12 @@ use utf8; use File::Basename qw(basename); use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window - :button wxTheApp); -use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED); + :button wxTheApp wxCB_READONLY); +use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED); use base qw(Wx::Panel Class::Accessor); -# Index of the currently active preset. -__PACKAGE__->mk_accessors(qw(current_preset)); - sub new { - my $class = shift; - my ($parent, %params) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); # Vertical sizer to hold the choice menu and the rest of the page. @@ -41,23 +37,33 @@ sub new { { # choice menu - $self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [270, -1], []); + $self->{presets_choice} = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, [270, -1], [], wxCB_READONLY); $self->{presets_choice}->SetFont($Slic3r::GUI::small_font); # buttons - $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("disk.png"), wxBITMAP_TYPE_PNG), + $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("disk.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), + $self->{show_incompatible_presets} = 0; + $self->{bmp_show_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-red-icon.png"), wxBITMAP_TYPE_PNG); + $self->{bmp_hide_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-green-icon.png"), wxBITMAP_TYPE_PNG); + $self->{btn_hide_incompatible_presets} = Wx::BitmapButton->new($self, -1, + $self->{bmp_hide_incompatible_presets}, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title)); $self->{btn_delete_preset}->SetToolTipString("Delete this preset"); $self->{btn_delete_preset}->Disable; - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); + my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->Add($hsizer, 0, wxBOTTOM, 3); $hsizer->Add($self->{presets_choice}, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); + $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL); + $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL); + $hsizer->AddSpacer(16); + $hsizer->Add($self->{btn_hide_incompatible_presets}, 0, wxALIGN_CENTER_VERTICAL); } # Horizontal sizer to hold the tree and the selected page. @@ -72,8 +78,11 @@ sub new { $self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [185, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); $left_sizer->Add($self->{treectrl}, 1, wxEXPAND); $self->{icons} = Wx::ImageList->new(16, 16, 1); + # Map from an icon file name to its index in $self->{icons}. + $self->{icon_index} = {}; + # Index of the last icon inserted into $self->{icons}. + $self->{icon_count} = -1; $self->{treectrl}->AssignImageList($self->{icons}); - $self->{iconcount} = -1; $self->{treectrl}->AddRoot("root"); $self->{pages} = []; $self->{treectrl}->SetIndent(0); @@ -96,76 +105,28 @@ sub new { } }); - EVT_CHOICE($parent, $self->{presets_choice}, sub { - $self->on_select_preset; - $self->_on_presets_changed; + EVT_COMBOBOX($parent, $self->{presets_choice}, sub { + $self->select_preset($self->{presets_choice}->GetStringSelection); }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); + EVT_BUTTON($self, $self->{btn_delete_preset}, sub { $self->delete_preset }); + EVT_BUTTON($self, $self->{btn_hide_incompatible_presets}, sub { $self->_toggle_show_hide_incompatible }); - EVT_BUTTON($self, $self->{btn_delete_preset}, sub { - my $i = $self->current_preset; - # Don't let the user delete the '- default -' configuration. - # This shall not happen as the 'delete' button is disabled for the '- default -' entry, - # but better be safe than sorry. - return if ($i == 0 && $self->get_current_preset->{default}); - my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; - return unless $res == wxID_YES; - # Delete the file. - my $path = $self->{presets}[$i]->file; - my $enc_path = Slic3r::encode_path($path); - if (-e $enc_path && ! unlink $enc_path) { - # Cannot delete the file, therefore the item will not be removed from the selection. - Slic3r::GUI::show_error($self, "Cannot delete file $path : $!"); - return; - } - # Delete the preset. - splice @{$self->{presets}}, $i, 1; - # Delete the item from the UI component. - $self->{presets_choice}->Delete($i - $self->{default_suppressed}); - $self->current_preset(undef); - if ($self->{default_suppressed} && scalar(@{$self->{presets}}) == 1) { - # Empty selection. Add the '- default -' item into the drop down selection. - $self->{presets_choice}->Append($self->{presets}->[0]->name); - # and remember that the '- default -' is shown. - $self->{default_suppressed} = 0; - } - # Select the 0th item. If default is suppressed, select the first valid. - $self->select_preset($self->{default_suppressed}); - $self->_on_presets_changed; - }); - - # C++ instance DynamicPrintConfig - $self->{config} = Slic3r::Config->new; # Initialize the DynamicPrintConfig by default keys/values. # Possible %params keys: no_controller $self->build(%params); - $self->update_tree; + $self->rebuild_page_tree; $self->_update; - if ($self->hidden_options) { - $self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options)); - } return $self; } -# Are the '- default -' selections suppressed by the Slic3r GUI preferences? -sub no_defaults { - return $Slic3r::GUI::Settings->{_}{no_defaults} ? 1 : 0; -} - -# Get a currently active preset (Perl class Slic3r::GUI::Tab::Preset). -sub get_current_preset { - my $self = shift; - return $self->get_preset($self->current_preset); -} - -# Get a preset (Perl class Slic3r::GUI::Tab::Preset) with an index $i. -sub get_preset { - my ($self, $i) = @_; - return $self->{presets}[$i]; -} - +# Save the current preset into file. +# This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, +# and activates the new preset. +# Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method +# opens a Slic3r::GUI::SavePresetWindow dialog. sub save_preset { my ($self, $name) = @_; @@ -175,10 +136,9 @@ sub save_preset { $self->{treectrl}->SetFocus; if (!defined $name) { - my $preset = $self->get_current_preset; + my $preset = $self->{presets}->get_selected_preset; my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.[iI][nN][iI]$//; - my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, @@ -187,184 +147,238 @@ sub save_preset { return 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); + # Save the preset into Slic3r::data_dir/presets/section_name/preset_name.ini + eval { $self->{presets}->save_current_preset($name); }; + Slic3r::GUI::catch_error($self) and return; + # Add the new item into the UI component, remove dirty flags and activate the saved item. + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + # Update the selection boxes at the platter. $self->_on_presets_changed; } +# Called for a currently selected preset. +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, $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; + # Load the newly selected preset into the UI, update selection combo boxes with their dirty flags. + $self->load_current_preset; +} + +sub _toggle_show_hide_incompatible { + my ($self) = @_; + $self->{show_incompatible_presets} = ! $self->{show_incompatible_presets}; + $self->_update_show_hide_incompatible_button; + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); +} + +sub _update_show_hide_incompatible_button { + my ($self) = @_; + $self->{btn_hide_incompatible_presets}->SetBitmap($self->{show_incompatible_presets} ? + $self->{bmp_show_incompatible_presets} : $self->{bmp_hide_incompatible_presets}); + $self->{btn_hide_incompatible_presets}->SetToolTipString($self->{show_incompatible_presets} ? + "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : + "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); +} + +# Register the on_value_change callback. sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } +# Register the on_presets_changed callback. sub on_presets_changed { my ($self, $cb) = @_; $self->{on_presets_changed} = $cb; } # This method is called whenever an option field is changed by the user. -# Propagate event to the parent through the 'on_value_changed' callback +# Propagate event to the parent through the 'on_value_change' callback # and call _update. +# The on_value_change callback triggers Platter::on_config_change() to configure the 3D preview +# (colors, wipe tower positon etc) and to restart the background slicing process. sub _on_value_change { my ($self, $key, $value) = @_; $self->{on_value_change}->($key, $value) if $self->{on_value_change}; - $self->_update({ $key => 1 }); + $self->_update; } # Override this to capture changes of configuration caused either by loading or switching a preset, # or by a user changing an option field. +# This callback is useful for cross-validating configuration values of a single preset. sub _update {} +# Call a callback to update the selection of presets on the platter: +# To update the content of the selection boxes, +# to update the filament colors of the selection boxes, +# to update the "dirty" flags of the selection boxes, +# to uddate number of "filament" selection boxes when the number of extruders change. sub _on_presets_changed { - my $self = shift; - - $self->{on_presets_changed}->( - $self->{presets}, - $self->{default_suppressed}, - scalar($self->{presets_choice}->GetSelection) + $self->{default_suppressed}, - $self->is_dirty, - ) if $self->{on_presets_changed}; + my ($self) = @_; + $self->{on_presets_changed}->($self->{presets}) if $self->{on_presets_changed}; } +# For the printer profile, generate the extruder pages after a preset is loaded. 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, $i) = @_; - if ($self->{default_suppressed} && $i == 0) { - # Selecting the '- default -'. Add it to the combo box. - $self->{default_suppressed} = 0; - $self->{presets_choice}->Insert($self->{presets}->[0]->name, 0); - } elsif ($self->no_defaults && ! $self->{default_suppressed} && $i > 0) { - # The user wants to hide the '- default -' items and a non-default item has been added to the presets. - # Hide the '- default -' item. - $self->{presets_choice}->Delete(0); - $self->{default_suppressed} = 1; +# If the current preset is dirty, the user is asked whether the changes may be discarded. +# if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. +sub may_discard_current_dirty_preset +{ + my ($self, $presets, $new_printer_name) = @_; + $presets //= $self->{presets}; + # Display a dialog showing the dirty options in a human readable form. + my $old_preset = $presets->get_current_preset; + my $type_name = $presets->name; + my $name = $old_preset->default ? + ('Default ' . $type_name . ' preset') : + ($type_name . " preset \"" . $old_preset->name . "\""); + # Collect descriptions of the dirty options. + my @option_names = (); + foreach my $opt_key (@{$presets->current_dirty_options}) { + my $opt = $Slic3r::Config::Options->{$opt_key}; + my $name = $opt->{full_label} // $opt->{label}; + $name = $opt->{category} . " > $name" if $opt->{category}; + push @option_names, $name; } - $self->{presets_choice}->SetSelection($i - $self->{default_suppressed}); - $self->on_select_preset; -} - -sub select_preset_by_name { - my ($self, $name) = @_; - - $name = Slic3r::normalize_utf8_nfc($name); - $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); + # Show a confirmation dialog with the list of dirty options. + my $changes = join "\n", map "- $_", @option_names; + my $message = (defined $new_printer_name) ? + "$name is not compatible with printer \"$new_printer_name\"\n and it has unsaved changes:" : + "$name has unsaved changes:"; + my $confirm = Wx::MessageDialog->new($self, + $message . "\n$changes\n\nDiscard changes and continue anyway?", + 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + return $confirm->ShowModal == wxID_YES; } -sub on_select_preset { - my $self = shift; - - if ($self->is_dirty) { - # Display a dialog showing the dirty options in a human readable form. - my $old_preset = $self->get_current_preset; - my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\""; - - my @option_names = (); - foreach my $opt_key (@{$self->dirty_options}) { - my $opt = $Slic3r::Config::Options->{$opt_key}; - my $name = $opt->{full_label} // $opt->{label}; - if ($opt->{category}) { - $name = $opt->{category} . " > $name"; +# Called by the UI combo box when the user switches profiles. +# Select a preset by a name. If ! defined(name), then the default preset is selected. +# If the current profile is modified, user is asked to save the changes. +sub select_preset { + my ($self, $name, $force) = @_; + $force //= 0; + my $current_dirty = $self->{presets}->current_is_dirty; + my $canceled = 0; + my $printer_tab = $self->{presets}->name eq 'printer'; + if (! $force && $current_dirty && ! $self->may_discard_current_dirty_preset) { + $canceled = 1; + } elsif ($printer_tab) { + # Before switching the printer to a new one, verify, whether the currently active print and filament + # are compatible with the new printer. + # If they are not compatible and the the current print or filament are dirty, let user decide + # whether to discard the changes or keep the current printer selection. + my $new_printer_name = $name // ''; + my $new_printer_preset = $self->{presets}->find_preset($new_printer_name, 1); + # my $new_nozzle_dmrs = $new_printer_preset->config->get('nozzle_diameter'); + my $print_presets = wxTheApp->{preset_bundle}->print; + if ($print_presets->current_is_dirty && + ! $print_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) { + if ($self->may_discard_current_dirty_preset($print_presets, $new_printer_name)) { + $canceled = 1; + } else { + $print_presets->discard_current_changes; } - push @option_names, $name; } - - 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 - $self->{default_suppressed}); - - # trigger the on_presets_changed event so that we also restore the previous value - # in the plater selector - $self->_on_presets_changed; - return; + my $filament_presets = wxTheApp->{preset_bundle}->filament; + # if ((@$new_nozzle_dmrs <= 1) && + if (! $canceled && $filament_presets->current_is_dirty && + ! $filament_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) { + if ($self->may_discard_current_dirty_preset($filament_presets, $new_printer_name)) { + $canceled = 1; + } else { + $filament_presets->discard_current_changes; + } } } - - $self->current_preset($self->{presets_choice}->GetSelection + $self->{default_suppressed}); - my $preset = $self->get_current_preset; - my $preset_config = $self->get_preset_config($preset); + if ($canceled) { + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector. + $self->_on_presets_changed; + } else { + if (defined $name) { + $self->{presets}->select_preset_by_name($name); + } else { + $self->{presets}->select_preset(0); + } + # Mark the print & filament enabled if they are compatible with the currently selected preset. + wxTheApp->{preset_bundle}->update_compatible_with_printer(1) + if $current_dirty || $printer_tab; + # Initialize the UI from the current preset. + $self->load_current_preset; + } +} + +# Initialize the UI from the current preset. +sub load_current_preset { + my ($self) = @_; + my $preset = $self->{presets}->get_current_preset; eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - my %keys_modified = (); - foreach my $opt_key (@{$self->{config}->get_keys}) { - if ($preset_config->has($opt_key)) { - if ($self->{config}->serialize($opt_key) ne $preset_config->serialize($opt_key)) { - $self->{config}->set($opt_key, $preset_config->get($opt_key)); - $keys_modified{$opt_key} = 1; - } - } - } - ($preset->default || $preset->external) - ? $self->{btn_delete_preset}->Disable - : $self->{btn_delete_preset}->Enable; - - $self->_update(\%keys_modified); + my $method = $preset->default ? 'Disable' : 'Enable'; + $self->{btn_delete_preset}->$method; + $self->_update; + # For the printer profile, generate the extruder pages. $self->on_preset_loaded; - $self->reload_config; - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; + # Reload preset pages with the new configuration values. + $self->_reload_config; }; - 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, # and we don't want them to be called after this update_dirty() as they would mark the # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); $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 { - my $self = shift; - my ($title, $icon, %params) = @_; - + my ($self, $title, $icon, %params) = @_; + # Index of $icon in an icon list $self->{icons}. + my $icon_idx = 0; if ($icon) { - my $bitmap = Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG); - $self->{icons}->Add($bitmap); - $self->{iconcount}++; + $icon_idx = $self->{icon_index}->{$icon}; + if (! defined $icon_idx) { + # Add a new icon to the icon list. + my $bitmap = Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG); + $self->{icons}->Add($bitmap); + $icon_idx = $self->{icon_count} + 1; + $self->{icon_count} = $icon_idx; + $self->{icon_index}->{$icon} = $icon_idx; + } } - - my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); + # Initialize the page. + my $page = Slic3r::GUI::Tab::Page->new($self, $title, $icon_idx); $page->Hide; $self->{hsizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; return $page; } -sub reload_config { - my $self = shift; +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { + my ($self) = @_; + $self->Freeze; $_->reload_config for @{$self->{pages}}; + $self->Thaw; } -sub update_tree { +# Regerenerate content of the page tree. +sub rebuild_page_tree { my ($self) = @_; - + $self->Freeze; # get label of the currently selected item - my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); - + my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); my $rootItem = $self->{treectrl}->GetRootItem; $self->{treectrl}->DeleteChildren($rootItem); my $have_selection = 0; @@ -377,147 +391,158 @@ sub update_tree { $have_selection = 1; } } - if (!$have_selection) { # this is triggered on first load, so we don't disable the sel change event $self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem)); } + $self->Thaw; } # Update the combo box label of the selected preset based on its "dirty" state, # comparing the selected preset config with $self->{config}. sub update_dirty { my ($self) = @_; - my $list_updated; - foreach my $i ($self->{default_suppressed}..$#{$self->{presets}}) { - my $preset = $self->get_preset($i); - my $label = ($i == $self->current_preset && $self->is_dirty) ? $preset->name . " (modified)" : $preset->name; - my $idx = $i - $self->{default_suppressed}; - if ($self->{presets_choice}->GetString($idx) ne $label) { - $self->{presets_choice}->SetString($idx, $label); - $list_updated = 1; - } - } - $self->{presets_choice}->SetSelection($self->current_preset - $self->{default_suppressed}) if ($list_updated); # http://trac.wxwidgets.org/ticket/13769 - $self->_on_presets_changed; -} - -# Has the selected preset been modified? -sub is_dirty { - my ($self) = @_; - return @{$self->dirty_options} > 0; -} - -# Which options of the selected preset were modified? -sub dirty_options { - my ($self) = @_; - return [] if !defined $self->current_preset; # happens during initialization - return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); -} - -# Search all ini files in the presets directory, add them into the list of $self->{presets} in the form of Slic3r::GUI::Tab::Preset. -# Initialize the drop down list box. -sub load_presets { - my ($self) = @_; - - $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->current_preset(undef); - $self->{default_suppressed} = Slic3r::GUI::Tab->no_defaults && scalar(@{$self->{presets}}) > 1; - $self->{presets_choice}->Clear; - foreach my $preset (@{$self->{presets}}) { - next if ($preset->default && $self->{default_suppressed}); - $self->{presets_choice}->Append($preset->name); - } - { - # 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 || $self->{default_suppressed}); - } + $self->{presets}->update_dirty_ui($self->{presets_choice}); $self->_on_presets_changed; } -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my ($self, $file) = @_; - # look for the loaded config among the existing menu items - 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 - my $preset_new = Slic3r::GUI::Tab::Preset->new( - file => $file, - name => $preset_name, - external => 1, - ); - # Try to load the config file before it is entered into the list. If the loading fails, an undef is returned. - return undef if ! defined $preset_new->config; - push @{$self->{presets}}, $preset_new; - $self->{presets_choice}->Append($preset_name); - $i = $#{$self->{presets}}; - } - $self->{presets_choice}->SetSelection($i - $self->{default_suppressed}); - $self->on_select_preset; - $self->_on_presets_changed; - return 1; -} - # Load a provied DynamicConfig into the tab, modifying the active preset. # This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. sub load_config { my ($self, $config) = @_; - - my %keys_modified = (); + my $modified = 0; foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); - $keys_modified{$opt_key} = 1; + $modified = 1; } - if (keys(%keys_modified)) { + if ($modified) { $self->update_dirty; # Initialize UI components with the config values. - $self->reload_config; - $self->_update(\%keys_modified); + $self->_reload_config; + $self->_update; } } -# Load and return a config from the file associated with the $preset (Perl type Slic3r::GUI::Tab::Preset). -sub get_preset_config { - my ($self, $preset) = @_; - return $preset->config($self->{config}->get_keys); +# To be called by custom widgets, load a value into a config, +# update the preset selection boxes (the dirty flags) +sub _load_key_value { + my ($self, $opt_key, $value) = @_; + $self->{config}->set($opt_key, $value); + # Mark the print & filament enabled if they are compatible with the currently selected preset. + if ($opt_key eq 'compatible_printers') { + wxTheApp->{preset_bundle}->update_compatible_with_printer(0); + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + } else { + $self->{presets}->update_dirty_ui($self->{presets_choice}); + } + $self->_on_presets_changed; + $self->_update; } +# Find a field with an index over all pages of this tab. +# This method is used often and everywhere, therefore it shall be quick. sub get_field { my ($self, $opt_key, $opt_index) = @_; - - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { my $field = $page->get_field($opt_key, $opt_index); return $field if defined $field; } return undef; } +# Set a key/value pair on this page. Return true if the value has been modified. +# Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer +# after a preset is loaded. sub set_value { - my $self = shift; - my ($opt_key, $value) = @_; - + my ($self, $opt_key, $value) = @_; my $changed = 0; - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { $changed = 1 if $page->set_value($opt_key, $value); } return $changed; } +# Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. +sub _compatible_printers_widget { + my ($self) = @_; + + return sub { + my ($parent) = @_; + + my $checkbox = $self->{compatible_printers_checkbox} = Wx::CheckBox->new($parent, -1, "All"); + + my $btn = $self->{compatible_printers_btn} = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG)); + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($checkbox, 0, wxALIGN_CENTER_VERTICAL); + $sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL); + + EVT_CHECKBOX($self, $checkbox, sub { + my $method = $checkbox->GetValue ? 'Disable' : 'Enable'; + $btn->$method; + # All printers have been made compatible with this preset. + $self->_load_key_value('compatible_printers', []) if $checkbox->GetValue; + }); + + EVT_BUTTON($self, $btn, sub { + # Collect names of non-default non-external printer profiles. + my @presets = map $_->name, grep !$_->default && !$_->external, + @{wxTheApp->{preset_bundle}->printer}; + my $dlg = Wx::MultiChoiceDialog->new($self, + "Select the printers this profile is compatible with.", + "Compatible printers", \@presets); + # Collect and set indices of printers marked as compatible. + my @selections = (); + foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { + my $idx = first { $presets[$_] eq $preset_name } 0..$#presets; + push @selections, $idx if defined $idx; + } + $dlg->SetSelections(@selections); + # Show the dialog. + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @presets[$dlg->GetSelections] ]; + if (!@$value) { + $checkbox->SetValue(1); + $btn->Disable; + } + # All printers have been made compatible with this preset. + $self->_load_key_value('compatible_printers', $value); + } + }); + + return $sizer; + }; +} + +sub _reload_compatible_printers_widget { + my ($self) = @_; + my $has_any = int(@{$self->{config}->get('compatible_printers')}) > 0; + my $method = $has_any ? 'Enable' : 'Disable'; + $self->{compatible_printers_checkbox}->SetValue(! $has_any); + $self->{compatible_printers_btn}->$method; +} + +sub update_ui_from_settings { + my ($self) = @_; + # Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled + # in application preferences. + my $show = wxTheApp->{app_config}->get("show_incompatible_presets") && $self->{presets}->name ne 'printer'; + my $method = $show ? 'Show' : 'Hide'; + $self->{btn_hide_incompatible_presets}->$method; + # If the 'show / hide presets' button is hidden, hide the incompatible presets. + if ($show) { + $self->_update_show_hide_incompatible_button; + } else { + if ($self->{show_incompatible_presets}) { + $self->{show_incompatible_presets} = 0; + $self->{presets}->update_tab_ui($self->{presets_choice}, 0); + } + } +} + package Slic3r::GUI::Tab::Print; use base 'Slic3r::GUI::Tab'; @@ -530,7 +555,8 @@ sub title { 'Print Settings' } sub build { my $self = shift; - $self->{config}->apply(wxTheApp->{preset_bundle}->prints->default_preset->config); + $self->{presets} = wxTheApp->{preset_bundle}->print; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); @@ -734,7 +760,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'); } } @@ -787,19 +813,33 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Dependencies', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile dependencies'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } + } } -sub reload_config { +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { my ($self) = @_; -# $self->_reload_compatible_printers_widget; - $self->SUPER::reload_config; + $self->_reload_compatible_printers_widget; + $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Print::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; - $keys_modified //= {}; + my ($self) = @_; + $self->Freeze; my $config = $self->{config}; @@ -879,10 +919,6 @@ sub _update { $self->load_config($new_conf); } - if ($keys_modified->{'layer_height'}) { - # If the user had set the variable layer height, reset it and let the user know. - } - if ($config->support_material) { # Ask only once. if (! $self->{support_material_overhangs_queried}) { @@ -997,9 +1033,9 @@ sub _update { my $have_wipe_tower = $config->wipe_tower; $self->get_field($_)->toggle($have_wipe_tower) for qw(wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe); -} -#sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } + $self->Thaw; +} package Slic3r::GUI::Tab::Filament; use base 'Slic3r::GUI::Tab'; @@ -1011,7 +1047,8 @@ sub title { 'Filament Settings' } sub build { my $self = shift; - $self->{config}->apply(wxTheApp->{preset_bundle}->filaments->default_preset->config); + $self->{presets} = wxTheApp->{preset_bundle}->filament; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Filament', 'spool.png'); @@ -1059,7 +1096,7 @@ sub build { full_width => 1, widget => sub { my ($parent) = @_; - return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); + return $self->{cooling_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); }, ); $optgroup->append_line($line); @@ -1098,6 +1135,16 @@ sub build { $optgroup = $page->new_optgroup('Print speed override'); $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => '', + full_width => 1, + widget => sub { + my ($parent) = @_; + return $self->{volumetric_speed_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); + }, + ); + $optgroup->append_line($line); } } @@ -1135,14 +1182,37 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Dependencies', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile dependencies'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } + } +} + +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { + my ($self) = @_; + $self->_reload_compatible_printers_widget; + $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Filament::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; + my ($self) = @_; - $self->_update_description; + $self->{cooling_description_line}->SetText( + Slic3r::GUI::PresetHints::cooling_description($self->{presets}->get_edited_preset)); + $self->{volumetric_speed_description_line}->SetText( + Slic3r::GUI::PresetHints::maximum_volumetric_flow_description(wxTheApp->{preset_bundle})); my $cooling = $self->{config}->cooling->[0]; my $fan_always_on = $cooling || $self->{config}->fan_always_on->[0]; @@ -1152,35 +1222,6 @@ sub _update { for qw(min_fan_speed disable_fan_first_layers); } -sub _update_description { - my $self = shift; - - my $config = $self->config; - - my $msg = ""; - my $fan_other_layers = $config->fan_always_on->[0] - ? sprintf "will always run at %d%%%s.", $config->min_fan_speed->[0], - ($config->disable_fan_first_layers->[0] > 1 - ? " except for the first " . $config->disable_fan_first_layers->[0] . " layers" - : $config->disable_fan_first_layers->[0] == 1 - ? " except for the first layer" - : "") - : "will be turned off."; - - if ($config->cooling->[0]) { - $msg = sprintf "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", - $config->slowdown_below_layer_time->[0], $config->max_fan_speed->[0], $config->slowdown_below_layer_time->[0], $config->min_print_speed->[0]; - if ($config->fan_below_layer_time->[0] > $config->slowdown_below_layer_time->[0]) { - $msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", - $config->fan_below_layer_time->[0], $config->max_fan_speed->[0], $config->min_fan_speed->[0]; - } - $msg .= "\nDuring the other layers, fan $fan_other_layers" - } else { - $msg = "Fan $fan_other_layers"; - } - $self->{description_line}->SetText($msg); -} - package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog); @@ -1190,37 +1231,31 @@ sub name { 'printer' } sub title { 'Printer Settings' } sub build { - my $self = shift; - my (%params) = @_; - - $self->{config}->apply(wxTheApp->{preset_bundle}->printers->default_preset->config); + my ($self, %params) = @_; + $self->{presets} = wxTheApp->{preset_bundle}->printer; + $self->{config} = $self->{presets}->get_edited_preset->config; + $self->{extruders_count} = scalar @{$self->{config}->nozzle_diameter}; + my $bed_shape_widget = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + $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 $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->update_dirty; - $self->_on_value_change('bed_shape', $value); - } + $self->_load_key_value('bed_shape', $dlg->GetValue) if $dlg->ShowModal == wxID_OK; }); return $sizer; }; - - $self->{extruders_count} = 1; - + { my $page = $self->add_options_page('General', 'printer_empty.png'); { @@ -1249,13 +1284,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}) @@ -1268,7 +1306,7 @@ sub build { $serial_port->side_widget(sub { my ($parent) = @_; - my $btn = Wx::BitmapButton->new($parent, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), + my $btn = Wx::BitmapButton->new($parent, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE); $btn->SetToolTipString("Rescan serial ports") if $btn->can('SetToolTipString'); @@ -1282,7 +1320,7 @@ sub build { my $btn = $self->{serial_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("wrench.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { my $sender = Slic3r::GCode::Sender->new; @@ -1312,7 +1350,7 @@ sub build { my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("zoom.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("zoom.png"), wxBITMAP_TYPE_PNG)); if (!eval "use Net::Bonjour; 1") { $btn->Disable; @@ -1328,13 +1366,8 @@ sub build { } if (@{$entries}) { my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue . ":" . $dlg->GetPort; - $self->{config}->set('octoprint_host', $value); - $self->update_dirty; - $self->_on_value_change('octoprint_host', $value); - $self->reload_config; - } + $self->_load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort) + if $dlg->ShowModal == wxID_OK; } else { Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; } @@ -1348,7 +1381,7 @@ sub build { my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("wrench.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { my $ua = LWP::UserAgent->new; @@ -1434,6 +1467,15 @@ sub build { $option->height(150); $optgroup->append_single_option_line($option); } + { + my $optgroup = $page->new_optgroup('Between objects G-code (for sequential printing)', + label_width => 0, + ); + my $option = $optgroup->get_option('between_objects_gcode'); + $option->full_width(1); + $option->height(150); + $optgroup->append_single_option_line($option); + } } { @@ -1458,43 +1500,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'); { @@ -1544,14 +1566,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; @@ -1560,14 +1574,14 @@ sub _build_extruder_pages { @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], $page_notes ); - $self->update_tree; + $self->rebuild_page_tree; } # Slic3r::GUI::Tab::Printer::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; - + my ($self) = @_; + $self->Freeze; + my $config = $self->{config}; my $serial_speed = $self->get_field('serial_speed'); @@ -1639,42 +1653,27 @@ sub _update { $self->get_field('retract_restart_extra_toolchange', $i)->toggle ($have_multiple_extruders && $toolchange_retraction); } + + $self->Thaw; } # 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); - } -} - -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my $self = shift; - if ($self->SUPER::load_config_file(@_)) { - Slic3r::GUI::warning_catcher($self)->( - "Your configuration was imported. However, Slic3r is currently only able to import settings " - . "for the first defined filament. We recommend you don't use exported configuration files " - . "for multi-extruder setups and rely on the built-in preset management system instead.") - if @{ $self->{config}->nozzle_diameter } > 1; - return 1; - } - return undef; + 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} package Slic3r::GUI::Tab::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; sub new { - my $class = shift; - my ($parent, $title, $iconID) = @_; + my ($class, $parent, $title, $iconID) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{optgroups} = []; $self->{title} = $title; @@ -1718,7 +1717,6 @@ sub reload_config { 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; @@ -1727,9 +1725,7 @@ sub get_field { } sub set_value { - my $self = shift; - my ($opt_key, $value) = @_; - + my ($self, $opt_key, $value) = @_; my $changed = 0; foreach my $optgroup (@{$self->{optgroups}}) { $changed = 1 if $optgroup->set_value($opt_key, $value); @@ -1737,14 +1733,15 @@ sub set_value { return $changed; } +# Dialog to select a new file name for a modified preset to be saved. +# Called from Tab::save_preset(). package Slic3r::GUI::SavePresetWindow; use Wx qw(:combobox :dialog :id :misc :sizer); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); use base 'Wx::Dialog'; sub new { - my $class = shift; - my ($parent, %params) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize); my @values = @{$params{values}}; @@ -1770,8 +1767,7 @@ sub new { sub accept { my ($self, $event) = @_; - - if (($self->{chosen_name} = $self->{combo}->GetValue)) { + if (($self->{chosen_name} = Slic3r::normalize_utf8_nfc($self->{combo}->GetValue))) { if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/) { Slic3r::GUI::show_error($self, "The supplied name is not valid; the following characters are not allowed: <>:/\|?*\""); } elsif ($self->{chosen_name} eq '- default -') { @@ -1783,57 +1779,8 @@ sub accept { } sub get_name { - my $self = shift; + my ($self) = @_; return $self->{chosen_name}; } -package Slic3r::GUI::Tab::Preset; -use Moo; -use List::Util qw(any); - -# The preset represents a "default" set of properties. -has 'default' => (is => 'ro', default => sub { 0 }); -has 'external' => (is => 'ro', default => sub { 0 }); -has 'name' => (is => 'rw', required => 1); -has 'file' => (is => 'rw'); - -# Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. -# In case of a "default" config item, return the default values. -sub config { - my ($self, $keys) = @_; - - if ($self->default) { - # Perl class Slic3r::Config extends the C++ class Slic3r::DynamicPrintConfig - 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 = eval { Slic3r::Config->load($self->file); }; - if ($@) { - Slic3r::GUI::show_error(undef, $@); - return undef; - } - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$keys; - - if (any { $_ eq 'nozzle_diameter' } @$keys) { - # Loaded the Printer settings. Verify, that all extruder dependent values have enough values. - my $nozzle_diameter = $config->nozzle_diameter; - my $num_extruders = scalar(@{$nozzle_diameter}); - foreach my $key (qw(deretract_speed extruder_colour retract_before_wipe)) { - my $vec = $config->get($key); - push @{$vec}, ($vec->[0]) x ($num_extruders - @{$vec}); - $config->set($key, $vec); - } - } - - return $config; - } -} - 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 160f70e06..12ad2f12f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -65,6 +65,9 @@ sub process { } # G-code export process, running at a background thread. +# The export_gcode may die for various reasons (fails to process output_filename_format, +# write error into the G-code, cannot execute post-processing scripts). +# It is up to the caller to show an error message. sub export_gcode { my $self = shift; my %params = @_; @@ -73,11 +76,12 @@ sub export_gcode { $self->process; # output everything to a G-code file + # The following call may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file} // ''); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); - die "G-code export to " . $output_file . " failed\n" - if ! Slic3r::GCode->new->do_export($self, $output_file); + # The following line may die for multiple reasons. + Slic3r::GCode->new->do_export($self, $output_file); # run post-processing scripts if (@{$self->config->post_process}) { @@ -99,6 +103,7 @@ sub export_gcode { } # Export SVG slices for the offline SLA printing. +# The export_svg is expected to be executed inside an eval block. sub export_svg { my $self = shift; my %params = @_; @@ -107,6 +112,7 @@ sub export_svg { my $fh = $params{output_fh}; if (!$fh) { + # The following line may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file}); $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/; Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; @@ -255,13 +261,4 @@ sub make_wipe_tower { $self->set_step_done(STEP_WIPE_TOWER); } -# Wrapper around the C++ Slic3r::Print::validate() -# to produce a Perl exception without a hang-up on some Strawberry perls. -sub validate -{ - my $self = shift; - my $err = $self->_validate; - die $err . "\n" if (defined($err) && $err ne ''); -} - 1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 793654300..e275fdde5 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,9 +11,6 @@ use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; -# If enabled, phases of prepare_infill will be written into SVG files to an "out" directory. -our $SLIC3R_DEBUG_SLICE_PROCESSING = 0; - sub layers { my $self = shift; return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; |