From 5d77c160c66008b5b64c6ee42d8e9189ac4b5df5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 Mar 2017 01:22:54 +0100 Subject: [PATCH 01/68] Changes to the GUI. New preset editor dialog. Option overrides in plater and between configs. More. --- README.md | 1 - lib/Slic3r/Config.pm | 7 +- lib/Slic3r/GUI.pm | 58 +- lib/Slic3r/GUI/Controller.pm | 15 +- lib/Slic3r/GUI/MainFrame.pm | 324 ++-------- lib/Slic3r/GUI/OptionsGroup.pm | 2 +- lib/Slic3r/GUI/Plater.pm | 596 +++++++++++------- .../GUI/Plater/OverrideSettingsPanel.pm | 31 +- lib/Slic3r/GUI/Preferences.pm | 10 - lib/Slic3r/GUI/Preset.pm | 52 ++ lib/Slic3r/GUI/{Tab.pm => PresetEditor.pm} | 470 +++++++++----- lib/Slic3r/GUI/PresetEditorDialog.pm | 65 ++ lib/Slic3r/GUI/Projector.pm | 7 +- lib/Slic3r/GUI/SLAPrintOptions.pm | 7 +- lib/Slic3r/GUI/SimpleTab.pm | 351 ----------- slic3r.pl | 32 +- xs/src/libslic3r/Config.cpp | 22 +- xs/src/libslic3r/Config.hpp | 2 + xs/src/libslic3r/PrintConfig.cpp | 26 +- xs/xsp/Config.xsp | 5 + xs/xsp/Print.xsp | 6 + 21 files changed, 996 insertions(+), 1093 deletions(-) create mode 100644 lib/Slic3r/GUI/Preset.pm rename lib/Slic3r/GUI/{Tab.pm => PresetEditor.pm} (85%) create mode 100644 lib/Slic3r/GUI/PresetEditorDialog.pm delete mode 100644 lib/Slic3r/GUI/SimpleTab.pm diff --git a/README.md b/README.md index dd3c2e29a..5d7d70eb0 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,6 @@ The author of the Silk icon set is Mark James. --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab - --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 5168ddf94..72f28c8bb 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -36,12 +36,11 @@ sub new_from_defaults { my (@opt_keys) = @_; my $self = $class->new; - my $defaults = Slic3r::Config::Full->new; if (@opt_keys) { - $self->set($_, $defaults->get($_)) - for grep $defaults->has($_), @opt_keys; + $self->set($_, $Options->{$_}{default}) + for grep exists $Options->{$_}{default}, @opt_keys; } else { - $self->apply_static($defaults); + $self->apply_static(Slic3r::Config::Full->new); } return $self; } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index c4fe92a04..0624e8883 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -31,9 +31,10 @@ use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::Projector; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup::Field; -use Slic3r::GUI::SimpleTab; +use Slic3r::GUI::Preset; +use Slic3r::GUI::PresetEditor; +use Slic3r::GUI::PresetEditorDialog; use Slic3r::GUI::SLAPrintOptions; -use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_LWP = eval "use LWP::UserAgent; 1"; @@ -57,21 +58,17 @@ use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf our $datadir; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. our $no_controller; -our $no_plater; -our $mode; our $autosave; our @cb; our $Settings = { _ => { - mode => 'simple', version_check => 1, autocenter => 1, invert_zoom => 0, background_processing => 0, # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - # By default, Prusa has the controller hidden. - no_controller => 1, + no_controller => 0, }, }; @@ -96,6 +93,7 @@ sub OnInit { Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; $self->{notifier} = Slic3r::GUI::Notifier->new; + $self->{external_presets} = []; # locate or create data directory # Unix: ~/.Slic3r @@ -123,7 +121,7 @@ sub OnInit { my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") }; $Settings = $ini if $ini; $last_version = $Settings->{_}{version}; - $Settings->{_}{mode} ||= 'expert'; + delete $Settings->{_}{mode}; # handle legacy $Settings->{_}{autocenter} //= 1; $Settings->{_}{invert_zoom} //= 0; $Settings->{_}{background_processing} //= 1; @@ -133,13 +131,17 @@ sub OnInit { $Settings->{_}{version} = $Slic3r::VERSION; $self->save_settings; + if (-f "$enc_datadir/simple.ini") { + # The Simple Mode settings were already automatically duplicated to presets + # named "Simple Mode" in each group, so we already support retrocompatibility. + unlink "$enc_datadir/simple.ini"; + } + # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( - mode => $mode // $Settings->{_}{mode}, # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. no_controller => $no_controller // $Settings->{_}{no_controller}, - no_plater => $no_plater, ); $self->SetTopWindow($frame); @@ -277,20 +279,46 @@ sub save_settings { } sub presets { - my ($self, $section) = @_; + my ($self, $section, $force_default) = @_; + + my @presets = (); - my %presets = (); opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; foreach my $file (grep /\.ini$/i, readdir $dh) { $file = Slic3r::decode_path($file); my $name = basename($file); - $name =~ s/\.ini$//; - $presets{$name} = "$Slic3r::GUI::datadir/$section/$file"; + $name =~ s/\.ini$//i; + push @presets, Slic3r::GUI::Preset->new( + name => $name, + file => "$Slic3r::GUI::datadir/$section/$file", + ); } closedir $dh; - return %presets; + @presets = sort { $a->name cmp $b->name } + @presets, + (grep -e $_->file, @{$self->{external_presets}}); + + if ($force_default || !@presets) { + unshift @presets, Slic3r::GUI::Preset->new( + default => 1, + name => '- default -', + ); + } + + return @presets; +} + +sub add_external_preset { + my ($self, $file) = @_; + + push @{$self->{external_presets}}, my $preset = Slic3r::GUI::Preset->new( + name => basename($file), # keep .ini suffix + file => $file, + external => 1, + ); + return $preset; } sub have_version_check { diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index c6a568f82..7ebf3491d 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -7,6 +7,7 @@ use strict; use warnings; use utf8; +use List::Util qw(any); use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use base qw(Wx::ScrolledWindow Class::Accessor); @@ -39,14 +40,14 @@ sub new { EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; - my %presets = wxTheApp->presets('printer'); + my %presets = map { $_->name => $_ } wxTheApp->presets('printer'); # remove printers that already exist my @panels = $self->print_panels; delete $presets{$_} for map $_->printer_name, @panels; foreach my $preset_name (sort keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); + my $config = $presets{$preset_name}->load_config; next if !$config->serial_port; my $id = &Wx::NewId(); @@ -100,8 +101,8 @@ sub OnActivate { # get all available presets my %presets = (); { - my %all = wxTheApp->presets('printer'); - my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all; + my %all = map { $_->name => $_ } wxTheApp->presets('printer'); + my %configs = map { my $name = $_; $name => $all{$name}->load_config } keys %all; %presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all; } @@ -177,6 +178,12 @@ sub print_panels { map $_->GetWindow, $self->{sizer}->GetChildren; } +sub printing { + my ($self) = @_; + + return any { $_->printing } $self->print_panels; +} + sub update_presets { my $self = shift; my ($group, $presets, $selected, $is_dirty) = @_; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index a3c04cd25..93ca6bfae 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -28,11 +28,8 @@ sub new { } # store input params - $self->{mode} = $params{mode}; - $self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. $self->{no_controller} = $params{no_controller}; - $self->{no_plater} = $params{no_plater}; $self->{loaded} = 0; # initialize tabpanel and menubar @@ -80,9 +77,17 @@ sub new { EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { - $event->Veto; - return; + if ($event->CanVeto) { + my $veto = 0; + if ($self->{controller} && $self->{controller}->printing) { + my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?", + 'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + $veto = 1 if $confirm->ShowModal == wxID_YES; + } + if ($veto) { + $event->Veto; + return; + } } # save window size @@ -107,71 +112,10 @@ sub _init_tabpanel { $panel->OnActivate if $panel->can('OnActivate'); }); - if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); - } + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); } - $self->{options_tabs} = {}; - - my $simple_config; - if ($self->{mode} eq 'simple') { - $simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini") - if -e Slic3r::encode_path("$Slic3r::GUI::datadir/simple.ini"); - } - - my $class_prefix = $self->{mode} eq 'simple' ? "Slic3r::GUI::SimpleTab::" : "Slic3r::GUI::Tab::"; - for my $tab_name (qw(print filament printer)) { - my $tab; - $tab = $self->{options_tabs}{$tab_name} = ($class_prefix . ucfirst $tab_name)->new( - $panel, - no_controller => $self->{no_controller}); - $tab->on_value_change(sub { - my ($opt_key, $value) = @_; - - my $config = $tab->config; - if ($self->{plater}) { - $self->{plater}->on_config_change($config); # propagate config change events to the plater - $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; - } - if ($self->{loaded}) { # don't save while loading for the first time - if ($self->{mode} eq 'simple') { - # save config - $self->config->save("$Slic3r::GUI::datadir/simple.ini"); - - # save a copy into each preset section - # so that user gets the config when switching to expert mode - $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode'); - $Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini'; - wxTheApp->save_settings; - } - $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; - } - }); - $tab->on_presets_changed(sub { - if ($self->{plater}) { - $self->{plater}->update_presets($tab_name, @_); - $self->{plater}->on_config_change($tab->config); - if ($self->{controller}) { - $self->{controller}->update_presets($tab_name, @_); - } - } - }); - $tab->load_presets; - $panel->AddPage($tab, $tab->title); - $tab->load_config($simple_config) if $simple_config; - } - - if ($self->{plater}) { - $self->{plater}->on_select_preset(sub { - my ($group, $i) = @_; - $self->{options_tabs}{$group}->select_preset($i); - }); - - # load initial config - $self->{plater}->on_config_change($self->config); - } } sub _init_menubar { @@ -239,7 +183,7 @@ sub _init_menubar { } # Plater menu - unless ($self->{no_plater}) { + { my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; @@ -252,49 +196,27 @@ sub _init_menubar { $self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }, undef, 'brick_go.png'); - $self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub { - $plater->pause_background_process; - Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal; - $plater->resume_background_process; - }, undef, 'film.png'); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); } - # Window menu - my $windowMenu = Wx::Menu->new; + # Settings menu + my $settingsMenu = Wx::Menu->new; { - my $tab_offset = 0; - if (!$self->{no_plater}) { - $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { - $self->select_tab(0); - }, undef, 'application_view_tile.png'); - $tab_offset += 1; - } - if (!$self->{no_controller}) { - $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { - $self->select_tab(1); - }, undef, 'printer_empty.png'); - $tab_offset += 1; - } - if ($tab_offset > 0) { - $windowMenu->AppendSeparator(); - } - - $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { - $self->select_tab($tab_offset+0); + $self->_append_menu_item($settingsMenu, "P&rint Settings…\tCtrl+1", 'Show the print settings editor', sub { + $self->{plater}->show_preset_editor('print'); }, undef, 'cog.png'); - $self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { - $self->select_tab($tab_offset+1); + $self->_append_menu_item($settingsMenu, "&Filament Settings…\tCtrl+2", 'Show the filament settings editor', sub { + $self->{plater}->show_preset_editor('filament'); }, undef, 'spool.png'); - $self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { - $self->select_tab($tab_offset+2); + $self->_append_menu_item($settingsMenu, "Print&er Settings…\tCtrl+3", 'Show the printer settings editor', sub { + $self->{plater}->show_preset_editor('printer'); }, undef, 'printer_empty.png'); } # View menu - if (!$self->{no_plater}) { + { $self->{viewMenu} = Wx::Menu->new; $self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); }); $self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); }); @@ -305,6 +227,22 @@ sub _init_menubar { $self->_append_menu_item($self->{viewMenu}, "Right" , 'Right View' , sub { $self->select_view('right' ); }); } + # Window menu + my $windowMenu = Wx::Menu->new; + { + $self->_append_menu_item($windowMenu, "&Plater\tCtrl+T", 'Show the plater', sub { + $self->select_tab(0); + }, undef, 'application_view_tile.png'); + $self->_append_menu_item($windowMenu, "&Controller\tCtrl+Y", 'Show the printer controller', sub { + $self->select_tab(1); + }, undef, 'printer_empty.png') if !$self->{no_controller}; + $self->_append_menu_item($windowMenu, "DLP Projector…\tCtrl+P", 'Open projector window for DLP printing', sub { + $self->{plater}->pause_background_process; + Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal; + $self->{plater}->resume_background_process; + }, undef, 'film.png'); + } + # Help menu my $helpMenu = Wx::Menu->new; { @@ -336,8 +274,9 @@ sub _init_menubar { $menubar->Append($fileMenu, "&File"); $menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu}; $menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu}; - $menubar->Append($windowMenu, "&Window"); + $menubar->Append($settingsMenu, "&Settings"); $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; + $menubar->Append($windowMenu, "&Window"); $menubar->Append($helpMenu, "&Help"); $self->SetMenuBar($menubar); } @@ -363,7 +302,7 @@ sub quick_slice { my $progress_dialog; eval { # validate configuration - my $config = $self->config; + my $config = $self->{plater}->config; $config->validate; # select input file @@ -509,7 +448,7 @@ sub repair_stl { sub export_config { my $self = shift; - my $config = $self->config; + my $config = $self->{plater}->config; eval { # validate configuration $config->validate; @@ -535,7 +474,6 @@ sub load_config_file { my ($file) = @_; if (!$file) { - return unless $self->check_unsaved_changes; my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); @@ -546,9 +484,10 @@ sub load_config_file { $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); wxTheApp->save_settings; $last_config = $file; - for my $tab (values %{$self->{options_tabs}}) { - $tab->load_config_file($file); - } + + my $preset = wxTheApp->add_external_preset($file); + $self->{plater}->load_presets; + $self->{plater}->select_preset_by_name($preset->name, $_) for qw(print filament printer); } sub export_configbundle { @@ -556,7 +495,7 @@ sub export_configbundle { eval { # validate current configuration in case it's dirty - $self->config->validate; + $self->{plater}->config->validate; }; Slic3r::GUI::catch_error($self) and return; @@ -571,18 +510,13 @@ sub export_configbundle { # leave default category empty to prevent the bundle from being parsed as a normal config file my $ini = { _ => {} }; - $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter mode); + $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter); $ini->{presets} = $Slic3r::GUI::Settings->{presets}; - if (-e "$Slic3r::GUI::datadir/simple.ini") { - my $config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini"); - $ini->{simple} = $config->as_ini->{_}; - } foreach my $section (qw(print filament printer)) { - my %presets = wxTheApp->presets($section); - foreach my $preset_name (keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); - $ini->{"$section:$preset_name"} = $config->as_ini->{_}; + my @presets = wxTheApp->presets($section); + foreach my $preset (@presets) { + $ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_}; } } @@ -617,15 +551,6 @@ sub load_configbundle { $Slic3r::GUI::Settings->{presets} = $ini->{presets}; wxTheApp->save_settings; } - if ($ini->{simple}) { - my $config = Slic3r::Config->load_ini_hash($ini->{simple}); - $config->save("$Slic3r::GUI::datadir/simple.ini"); - if ($self->{mode} eq 'simple') { - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config) for values %{$self->{options_tabs}}; - } - } - } my $imported = 0; INI_BLOCK: foreach my $ini_category (sort keys %$ini) { next unless $ini_category =~ /^(print|filament|printer):(.+)$/; @@ -634,11 +559,11 @@ sub load_configbundle { next if $skip_no_id && !$config->get($section . "_settings_id"); { - my %current_presets = Slic3r::GUI->presets($section); + my @current_presets = Slic3r::GUI->presets($section); my %current_ids = map { $_ => 1 } grep $_, - map Slic3r::Config->load($_)->get($section . "_settings_id"), - values %current_presets; + map $_->load_config->get($section . "_settings_id"), + @current_presets; next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; } @@ -646,150 +571,34 @@ sub load_configbundle { Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name; $imported++; } - if ($self->{mode} eq 'expert') { - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; - } - } + $self->{plater}->load_presets; return if !$imported; my $message = sprintf "%d presets successfully imported.", $imported; - if ($self->{mode} eq 'simple' && $Slic3r::GUI::Settings->{_}{mode} eq 'expert') { - Slic3r::GUI::show_info($self, "$message You need to restart Slic3r to make the changes effective."); - } else { - Slic3r::GUI::show_info($self, $message); - } + Slic3r::GUI::show_info($self, $message); } sub load_config { - my $self = shift; - my ($config) = @_; + my ($self, $config) = @_; - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config); - } - if ($self->{plater}) { - $self->{plater}->on_config_change($config); - } + $self->{plater}->load_config($config); } sub config_wizard { my $self = shift; - return unless $self->check_unsaved_changes; if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { - if ($self->{mode} eq 'expert') { - for my $tab (values %{$self->{options_tabs}}) { - $tab->select_default_preset; - } - } else { - # TODO: select default settings in simple mode - } - $self->load_config($config); - if ($self->{mode} eq 'expert') { - for my $tab (values %{$self->{options_tabs}}) { - $tab->save_preset('My Settings'); - } + foreach my $group (qw(print filament printer)) { + my $name = 'My Settings'; + $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $group, $name); + $Slic3r::GUI::Settings->{presets}{$group} = "$name.ini"; + $self->{plater}->load_presets; + $self->{plater}->select_preset_by_name($name, $group); } } } -=head2 config - -This method collects all config values from the tabs and merges them into a single config object. - -=cut - -sub config { - my $self = shift; - - return Slic3r::Config->new_from_defaults - if !exists $self->{options_tabs}{print} - || !exists $self->{options_tabs}{filament} - || !exists $self->{options_tabs}{printer}; - - # retrieve filament presets and build a single config object for them - my $filament_config; - if (!$self->{plater} || $self->{plater}->filament_presets == 1 || $self->{mode} eq 'simple') { - $filament_config = $self->{options_tabs}{filament}->config; - } else { - my $i = -1; - foreach my $preset_idx ($self->{plater}->filament_presets) { - $i++; - my $config; - if ($preset_idx == $self->{options_tabs}{filament}->current_preset) { - # the selected preset for this extruder is the one in the tab - # use the tab's config instead of the preset in case it is dirty - # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. - $config = $self->{options_tabs}{filament}->config; - } else { - my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); - $config = $self->{options_tabs}{filament}->get_preset_config($preset); - } - if (!$filament_config) { - $filament_config = $config->clone; - next; - } - foreach my $opt_key (@{$config->get_keys}) { - my $value = $filament_config->get($opt_key); - next unless ref $value eq 'ARRAY'; - $value->[$i] = $config->get($opt_key)->[0]; - $filament_config->set($opt_key, $value); - } - } - } - - my $config = Slic3r::Config->merge( - Slic3r::Config->new_from_defaults, - $self->{options_tabs}{print}->config, - $self->{options_tabs}{printer}->config, - $filament_config, - ); - - if ($self->{mode} eq 'simple') { - # set some sensible defaults - $config->set('first_layer_height', $config->nozzle_diameter->[0]); - $config->set('avoid_crossing_perimeters', 1); - $config->set('infill_every_layers', 10); - } else { - my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; - $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) - for qw(perimeter infill solid_infill support_material support_material_interface); - } - - return $config; -} - -sub filament_preset_names { - my ($self) = @_; - - if ($self->{mode} eq 'simple') { - return ''; - } - - return map $self->{options_tabs}{filament}->get_preset($_)->name, - $self->{plater}->filament_presets; -} - -sub check_unsaved_changes { - my $self = shift; - - my @dirty = (); - foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->is_dirty; - } - - if (@dirty) { - my $titles = join ', ', @dirty; - my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", - 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return ($confirm->ShowModal == wxID_YES); - } - - return 1; -} - sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->SetSelection($tab); @@ -798,9 +607,8 @@ sub select_tab { # Set a camera direction, zoom to all objects. sub select_view { my ($self, $direction) = @_; - if (! $self->{no_plater}) { - $self->{plater}->select_view($direction); - } + + $self->{plater}->select_view($direction); } sub _append_menu_item { diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 2a00b1a0c..776f80ec3 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -1,4 +1,4 @@ -# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc. +# A dialog group object. Used by the PresetEditor, Preferences dialog, ManualControlDialog etc. package Slic3r::GUI::OptionsGroup; use Moo; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3ce193a7c..b93e49038 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -6,16 +6,17 @@ use warnings; use utf8; use File::Basename qw(basename dirname); -use List::Util qw(sum first max); +use List::Util qw(sum first max none any); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg); use LWP::UserAgent; use threads::shared qw(shared_clone); -use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc +use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox); -use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED - EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL +use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); -use base 'Wx::Panel'; +use base qw(Wx::Panel Class::Accessor); + +__PACKAGE__->mk_accessors(qw(presets)); use constant TB_ADD => &Wx::NewId; use constant TB_REMOVE => &Wx::NewId; @@ -42,15 +43,13 @@ our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; use constant FILAMENT_CHOOSERS_SPACING => 0; use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds -my $PreventListEvents = 0; - sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config->new_from_defaults(qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width - serial_port serial_speed octoprint_host octoprint_apikey + serial_port serial_speed octoprint_host octoprint_apikey overridable )); $self->{model} = Slic3r::Model->new; $self->{print} = Slic3r::Print->new; @@ -194,23 +193,6 @@ sub new { } } - $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, - wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); - $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); - $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); - $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); - EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); - EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); - EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); - EVT_KEY_DOWN($self->{list}, sub { - my ($list, $event) = @_; - if ($event->GetKeyCode == WXK_TAB) { - $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); - } else { - $event->Skip; - } - }); - # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self, -1, "Print…", wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -314,7 +296,7 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), - $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; + $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; @@ -365,32 +347,52 @@ sub new { $self->update; { - my $presets; - if ($self->GetFrame->{mode} eq 'expert') { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); - $presets->AddGrowableCol(1, 1); - $presets->SetFlexibleDirection(wxHORIZONTAL); - my %group_labels = ( - print => 'Print settings', - filament => 'Filament', - printer => 'Printer', - ); - $self->{preset_choosers} = {}; - for my $group (qw(print filament printer)) { - my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - $text->SetFont($Slic3r::GUI::small_font); - my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); - $self->{preset_choosers}{$group} = [$choice]; - # setup the listener - EVT_COMBOBOX($choice, $choice, sub { - my ($choice) = @_; - wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice); - }); + my $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 3, 1, 2); + $presets->AddGrowableCol(1, 1); + $presets->SetFlexibleDirection(wxHORIZONTAL); + my %group_labels = ( + print => 'Print settings', + filament => 'Filament', + printer => 'Printer', + ); + $self->{preset_choosers} = {}; + for my $group (qw(print filament printer)) { + # label + my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + $text->SetFont($Slic3r::GUI::small_font); + + # dropdown control + my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); + $self->{preset_choosers}{$group} = [$choice]; + # setup the listener + EVT_COMBOBOX($choice, $choice, sub { + my ($choice) = @_; + wxTheApp->CallAfter(sub { + $self->_on_select_preset($group); }); - $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); - } + }); + + # settings button + my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + EVT_BUTTON($self, $settings_btn, sub { + $self->show_preset_editor($group, 0); + }); + + $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); + $presets->Add($settings_btn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxLEFT, 3); + } + + { + $self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, + on_change => sub { + $self->config_changed; + }); + $self->{settings_override_panel}->set_editable(0); + $self->{settings_override_config} = Slic3r::Config->new; + $self->{settings_override_panel}->set_default_config($self->{settings_override_config}); + $self->{settings_override_panel}->set_config($self->{settings_override_config}); } my $object_info_sizer; @@ -405,6 +407,8 @@ sub new { $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( + name => "Name", + copies => "Copies", size => "Size", volume => "Volume", facets => "Facets", @@ -473,7 +477,7 @@ sub new { my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($self->{list}, 1, wxEXPAND, 5); + $right_sizer->Add($self->{settings_override_panel}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); $right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); $right_sizer->Hide($print_info_sizer); @@ -492,35 +496,83 @@ sub new { $self->SetSizer($sizer); } + $self->load_presets; + $self->_on_select_preset($_) for qw(printer filament print); + return $self; } -# sets the callback -sub on_select_preset { - my ($self, $cb) = @_; - $self->{on_select_preset} = $cb; +sub _on_select_preset { + my ($self, $group) = @_; + + my @presets = map $self->presets->{$group}[scalar $_->GetSelection], + @{$self->{preset_choosers}{$group}}; + + $Slic3r::GUI::Settings->{presets}{$group} = $presets[0]->name; + $Slic3r::GUI::Settings->{presets}{"${group}_${_}"} = $presets[$_]->name + for 1..$#presets; + + wxTheApp->save_settings; + + my $config = $self->config; + + $self->on_extruders_change(scalar @{$config->get('nozzle_diameter')}); + + foreach my $opt_key (@{$self->{config}->diff($config)}) { + # Ignore overrides. No need to set them in our config; we'll use them directly below. + next if $opt_key eq 'overrides'; + + $self->{config}->set($opt_key, $config->get($opt_key)); + + if ($opt_key eq 'bed_shape') { + $self->{canvas}->update_bed_size; + $self->{canvas3D}->update_bed_size if $self->{canvas3D}; + $self->{preview3D}->set_bed_shape($self->{config}->bed_shape) + if $self->{preview3D}; + $self->update; + } elsif ($opt_key eq 'serial_port') { + if ($config->get('serial_port')) { + $self->{btn_print}->Show; + } else { + $self->{btn_print}->Hide; + } + $self->Layout; + } elsif ($opt_key eq 'octoprint_host') { + if ($config->get('octoprint_host')) { + $self->{btn_send_gcode}->Show; + } else { + $self->{btn_send_gcode}->Hide; + } + } + } + + if ($group eq 'print') { + $self->{settings_override_config}->clear; + my $overridable = $config->get('overridable'); + if ($overridable) { + $self->{settings_override_config}->set($_, $config->get($_)) + for @$overridable; + } + $self->{settings_override_panel}->update_optgroup; + } elsif ($group eq 'printer') { + # reload print and filament settings to honor their compatible_printer options + $self->load_presets; + } + + return if !$self->GetFrame->is_loaded; + + $self->config_changed; } -sub _on_select_preset { - my $self = shift; - my ($group, $choice) = @_; - - # if user changed filament preset, don't propagate this to the tabs - if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - my @filament_presets = $self->filament_presets; - $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini"; - $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_]) - for 1 .. $#filament_presets; - wxTheApp->save_settings; - return; - } - - # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, scalar $choice->GetSelection) - if $self->{on_select_preset}; - - # get new config and generate on_config_change() event for updating plater and other things - $self->on_config_change($self->GetFrame->config); +sub load_config { + my ($self, $config) = @_; + + # This method is called with the CLI options. + # We add them to the visible overrides. + $self->{settings_override_config}->apply($config); + $self->{settings_override_panel}->update_optgroup; + + $self->config_changed; } sub GetFrame { @@ -528,60 +580,188 @@ sub GetFrame { return &Wx::GetTopLevelParent($self); } -sub update_presets { - my $self = shift; - my ($group, $presets, $selected, $is_dirty) = @_; +sub load_presets { + my ($self) = @_; - my @choosers = @{ $self->{preset_choosers}{$group} }; - foreach my $choice (@choosers) { - if ($group eq 'filament' && @choosers > 1) { - # if we have more than one filament chooser, keep our selection - # instead of importing the one from the tab - $selected = $choice->GetSelection; - $is_dirty = 0; - } - $choice->Clear; - foreach my $preset (@$presets) { - my $bitmap; - if ($group eq 'filament') { - my $config = $preset->config(['filament_colour']); - my $rgb_hex = $config->filament_colour->[0]; - if ($preset->default) { - $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); - } else { - $rgb_hex =~ s/^#//; - my @rgb = unpack 'C*', pack 'H*', $rgb_hex; - my $image = Wx::Image->new(16,16); - $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); - $bitmap = Wx::Bitmap->new($image); + $self->presets({}); + + my $selected_printer_name; + foreach my $group (qw(printer filament print)) { + my @presets = wxTheApp->presets($group); + if ($group eq 'filament' || $group eq 'print') { + my %printer_names = map { $_->name => 1 } @{ $self->presets->{printer} }; + # Skip presets not compatible with the selected printer, if they + # have other compatible printers configured (and at least one of them exists). + for (my $i = 0; $i <= $#presets; ++$i) { + my $config = $presets[$i]->config; + next if !$config->has('compatible_printers'); + my @compat = @{$config->compatible_printers}; + if (@compat + && (none { $_ eq $selected_printer_name } @compat) + && (any { $printer_names{$_} } @compat)) { + splice @presets, $i, 1; + --$i; } - } elsif ($group eq 'print') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); - } elsif ($group eq 'printer') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); } - $choice->AppendString($preset->name, $bitmap); + } + if (!@presets) { + unshift @presets, Slic3r::GUI::Preset->new( + default => 1, + name => '- default -', + ); } - if ($selected <= $#$presets) { - my $preset_name = $choice->GetString($selected); - if ($is_dirty) { - $choice->SetString($selected, "$preset_name (modified)"); + $self->presets->{$group} = [@presets]; + + # get the wxChoice objects for this group + my @choosers = @{ $self->{preset_choosers}{$group} }; + + # find the currently selected one(s) according to the saved file + my @sel = (); + if (my $current = $Slic3r::GUI::Settings->{presets}{$group}) { + push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets; + } + for my $i (1..(@choosers-1)) { + if (my $current = $Slic3r::GUI::Settings->{presets}{"${group}_$i"}) { + push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets; + } + } + @sel = (0) if !@sel; + + # populate the wxChoice objects + foreach my $choice (@choosers) { + $choice->Clear; + foreach my $preset (@presets) { + # load/generate the proper icon + my $bitmap; + if ($group eq 'filament') { + my $config = $preset->config; + if ($preset->default || !$config->has('filament_colour')) { + $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); + } else { + my $rgb_hex = $config->filament_colour->[0]; + + $rgb_hex =~ s/^#//; + my @rgb = unpack 'C*', pack 'H*', $rgb_hex; + my $image = Wx::Image->new(16,16); + $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); + $bitmap = Wx::Bitmap->new($image); + } + } elsif ($group eq 'print') { + $bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); + } elsif ($group eq 'printer') { + $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); + } + $choice->AppendString($preset->name, $bitmap); + } + + my $selected = shift @sel; + if ($selected <= $#presets) { + # call SetSelection() only after SetString() otherwise the new string + # won't be picked up as the visible string + $choice->SetSelection($selected); + + my $preset_name = $choice->GetString($selected); + $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); + $selected_printer_name = $preset_name if $group eq 'printer'; } - # call SetSelection() only after SetString() otherwise the new string - # won't be picked up as the visible string - $choice->SetSelection($selected); - - $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); } } + + #$self->_on_select_preset($_) for qw(printer filament print); } -sub filament_presets { - my $self = shift; +sub select_preset_by_name { + my ($self, $name, $group, $n) = @_; - # force scalar context for GetSelection() as it's context-aware - return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} }; + # $n is optional + + my $presets = $self->presets->{$group}; + my $i = first { $presets->[$_]->name eq $name } 0..$#$presets; + return if !defined $i; + + my $choosers = $self->{preset_choosers}{$group}; + if (defined $n && $n <= $#$choosers) { + $choosers->[$n]->SetSelection($i); + } else { + $_->SetSelection($i) for @$choosers; + } + $self->_on_select_preset($group); +} + +sub selected_presets { + my ($self, $group) = @_; + + my %presets; + foreach my $group (qw(printer filament print)) { + my @i = map scalar($_->GetSelection), @{ $self->{preset_choosers}{$group} }; + $presets{$group} = [ @{$self->presets->{$group}}[@i] ]; + } + return $group ? @{$presets{$group}} : %presets; +} + +sub show_preset_editor { + my ($self, $group, $i) = @_; + + my $class = "Slic3r::GUI::PresetEditorDialog::" . ucfirst($group); + my $dlg = $class->new($self); + + my @presets = $self->selected_presets($group); + $dlg->preset_editor->select_preset_by_name($presets[$i // 0]->name); + $dlg->ShowModal; + + # Re-load the presets as they might have changed. + $self->load_presets; + + # Select the preset that was last selected in the editor. + $self->select_preset_by_name + ($dlg->preset_editor->get_current_preset->name, $group, $i, 1); +} + +# Returns the current config by merging the selected presets and the overrides. +sub config { + my ($self) = @_; + + # use a DynamicConfig because FullPrintConfig is not enough + my $config = Slic3r::Config->new_from_defaults; + + # get defaults also for the values tracked by the Plater's config + # (for example 'overridable') + $config->apply(Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys})); + + my %classes = map { $_ => "Slic3r::GUI::PresetEditor::".ucfirst($_) } + qw(print filament printer); + + my %presets = $self->selected_presets; + $config->apply($_->config([ $classes{printer}->options ])) + for @{ $presets{printer} }; + if (@{ $presets{filament} }) { + my @opt_keys = ($classes{filament}->options, $classes{filament}->overriding_options); + my $filament_config = shift(@{ $presets{filament} }) + ->config(\@opt_keys); + + my $i = 1; + for my $preset (@{ $presets{filament} }) { + my $config = $preset->config(\@opt_keys); + foreach my $opt_key (@{$config->get_keys}) { + if ($filament_config->has($opt_key)) { + my $value = $filament_config->get($opt_key); + next unless ref $value eq 'ARRAY'; + $value->[$i] = $config->get($opt_key)->[0]; + $filament_config->set($opt_key, $value); + } + } + ++$i; + } + + $config->apply($filament_config); + } + $config->apply($_->config([ $classes{print}->options ])) + for @{ $presets{print} }; + + $config->apply($self->{settings_override_config}); + + return $config; } sub add { @@ -712,18 +892,7 @@ sub load_model_objects { ); } - foreach my $obj_idx (@obj_idx) { - my $object = $self->{objects}[$obj_idx]; - my $model_object = $self->{model}->objects->[$obj_idx]; - $self->{list}->InsertStringItem($obj_idx, $object->name); - $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) - if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() - - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); - - $self->make_thumbnail($obj_idx); - } + $self->make_thumbnail($_) for @obj_idx; $self->arrange if $need_arrange; $self->update; @@ -731,8 +900,6 @@ sub load_model_objects { $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; - $self->{list}->Update; - $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; @@ -766,7 +933,6 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); - $self->{list}->DeleteItem($obj_idx); $self->object_list_changed; $self->select_object(undef); @@ -786,7 +952,6 @@ sub reset { @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; - $self->{list}->DeleteAllItems; $self->object_list_changed; $self->select_object(undef); @@ -808,7 +973,6 @@ sub increase { ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; @@ -833,15 +997,10 @@ sub decrease { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } else { $self->remove; } - if ($self->{objects}[$obj_idx]) { - $self->{list}->Select($obj_idx, 0); - $self->{list}->Select($obj_idx, 1); - } $self->update; $self->schedule_background_process; } @@ -1006,8 +1165,8 @@ sub changescale { $model_instance->scaling_factor*100, $self); return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0; } - - $self->{list}->SetItem($obj_idx, 2, "$scale%"); + return if !$scale || $scale < 0; + $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; @@ -1035,7 +1194,7 @@ sub arrange { $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); + my $success = $self->{model}->arrange_objects($self->config->min_object_distance, $bb); # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed @@ -1086,6 +1245,17 @@ sub split_object { $self->load_model_objects(@model_objects); } +sub config_changed { + my ($self) = @_; + + if ($Slic3r::GUI::autosave) { + $self->config->save($Slic3r::GUI::autosave); + } + + # (re) start timer + $self->schedule_background_process; +} + sub schedule_background_process { my ($self) = @_; @@ -1117,7 +1287,7 @@ sub async_apply_config { $self->pause_background_process; # apply new config - my $invalidated = $self->{print}->apply_config($self->GetFrame->config); + my $invalidated = $self->{print}->apply_config($self->config); return if !$Slic3r::GUI::Settings->{_}{background_processing}; @@ -1151,7 +1321,7 @@ sub start_background_process { # don't start process thread if config is not valid eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + $self->config->validate; $self->{print}->validate; }; if ($@) { @@ -1239,14 +1409,14 @@ sub export_gcode { # (we assume that if it is running, config is valid) eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + $self->config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print - my $config = $self->GetFrame->config; + my $config = $self->config; eval { # this will throw errors if config is not valid $config->validate; @@ -1410,15 +1580,15 @@ sub on_export_completed { sub do_print { my ($self) = @_; - my $printer_tab = $self->GetFrame->{options_tabs}{printer}; - my $printer_name = $printer_tab->get_current_preset->name; + my $controller = $self->GetFrame->{controller} or return; - my $controller = $self->GetFrame->{controller}; - my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config); + my %current_presets = $self->selected_presets; + + my $printer_name = $current_presets{printer}->[0]->name; + my $printer_panel = $controller->add_printer($printer_name, $self->config); my $filament_stats = $self->{print}->filament_stats; - my @filament_names = $self->GetFrame->filament_preset_names; - $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; + $filament_stats = { map { $current_presets{filament}[$_]->name => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); $self->GetFrame->select_tab(1); @@ -1620,18 +1790,27 @@ sub on_extruders_change { # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; - # insert new choice into sizer - $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); - $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + # settings button + my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - # setup the listener + # insert new row into sizer + $self->{presets_sizer}->Insert(6 + ($#$choices-1)*3, 0, 0); + $self->{presets_sizer}->Insert(7 + ($#$choices-1)*3, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); + $self->{presets_sizer}->Insert(8 + ($#$choices-1)*3, $settings_btn, 0, wxEXPAND | wxLEFT, 4); + + # setup the listeners EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice); + $self->_on_select_preset('filament'); }); }); + EVT_BUTTON($self, $settings_btn, sub { + $self->show_preset_editor('filament', $#$choices); + }); + # initialize selection my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; $choice->SetSelection($i || 0); @@ -1639,79 +1818,21 @@ sub on_extruders_change { # remove unused choices if any while (@$choices > $num_extruders) { - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label - $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice + my $i = 6 + ($#$choices-1)*3; + + $self->{presets_sizer}->Remove($i); # label + $self->{presets_sizer}->Remove($i); # wxChoice + + my $settings_btn = $self->{presets_sizer}->GetItem($i)->GetWindow; + $self->{presets_sizer}->Remove($i); # settings btn + $settings_btn->Destroy; + $choices->[-1]->Destroy; pop @$choices; } $self->Layout; } -sub on_config_change { - my $self = shift; - my ($config) = @_; - - foreach my $opt_key (@{$self->{config}->diff($config)}) { - $self->{config}->set($opt_key, $config->get($opt_key)); - if ($opt_key eq 'bed_shape') { - $self->{canvas}->update_bed_size; - $self->{canvas3D}->update_bed_size if $self->{canvas3D}; - $self->{preview3D}->set_bed_shape($self->{config}->bed_shape) - if $self->{preview3D}; - $self->update; - } elsif ($opt_key eq 'serial_port') { - if ($config->get('serial_port')) { - $self->{btn_print}->Show; - } else { - $self->{btn_print}->Hide; - } - $self->Layout; - } elsif ($opt_key eq 'octoprint_host') { - if ($config->get('octoprint_host')) { - $self->{btn_send_gcode}->Show; - } else { - $self->{btn_send_gcode}->Hide; - } - $self->Layout; - } - } - if ($self->{"right_sizer"}) { - $self->{"right_sizer"}->Hide($self->{"sliced_info_box"}); - $self->{"right_sizer"}->Layout; - } - - return if !$self->GetFrame->is_loaded; - - # (re)start timer - $self->schedule_background_process; -} - -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); - $self->refresh_canvases; - } -} - -sub list_item_selected { - my ($self, $event) = @_; - return if $PreventListEvents; - - my $obj_idx = $event->GetIndex; - $self->select_object($obj_idx); - $self->refresh_canvases; -} - -sub list_item_activated { - my ($self, $event, $obj_idx) = @_; - - $obj_idx //= $event->GetIndex; - $self->object_settings_dialog($obj_idx); -} - sub object_cut_dialog { my $self = shift; my ($obj_idx) = @_; @@ -1818,9 +1939,17 @@ sub selection_changed { if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { + $self->{object_info_name}->SetLabel($object->name); my $model_object = $self->{model}->objects->[$obj_idx]; + $self->{object_info_copies}->SetLabel($model_object->instances_count); my $model_instance = $model_object->instances->[0]; - $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); + { + my $size_string = sprintf "%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size}; + if ($model_instance->scaling_factor != 1) { + $size_string .= sprintf " (%s%%)", $model_instance->scaling_factor * 100; + } + $self->{object_info_size}->SetLabel($size_string); + } $self->{object_info_materials}->SetLabel($model_object->materials_count); my $raw_mesh = $model_object->raw_mesh; @@ -1845,7 +1974,7 @@ sub selection_changed { $self->{object_info_facets}->SetLabel($object->facets); } } else { - $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); + $self->{"object_info_$_"}->SetLabel("") for qw(name copies size volume facets materials manifold); $self->{object_info_manifold_warning_icon}->Hide; $self->{object_info_manifold}->SetToolTipString(""); } @@ -1862,15 +1991,6 @@ sub select_object { $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - - # We use this flag to avoid circular event handling - # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, - # whose event handler calls this method again and again and again - $PreventListEvents = 1; - $self->{list}->Select($obj_idx, 1); - $PreventListEvents = 0; - } else { - # TODO: deselect all in list } $self->selection_changed(1); } @@ -1895,7 +2015,7 @@ sub validate_config { my $self = shift; eval { - $self->GetFrame->config->validate; + $self->config->validate; }; return 0 if Slic3r::GUI::catch_error($self); return 1; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index e848b7638..5328cbff5 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -1,5 +1,4 @@ -# Included in ObjectSettingsDialog -> ObjectPartsPanel. -# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh. +# Maintains, displays, adds and removes overrides of slicing parameters. package Slic3r::GUI::Plater::OverrideSettingsPanel; use strict; @@ -19,10 +18,11 @@ use constant ICON_MODIFIERMESH => 2; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $params{size} // wxDefaultSize, wxTAB_TRAVERSAL); $self->{default_config} = Slic3r::Config->new; $self->{config} = Slic3r::Config->new; $self->{on_change} = $params{on_change}; + $self->{editable} = 1; $self->{fixed_options} = {}; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); @@ -64,17 +64,20 @@ sub new { return $self; } +# Sets the config used to get the default values for user-added options. sub set_default_config { my ($self, $config) = @_; $self->{default_config} = $config; } +# Sets the target config, whose options will be displayed in the OptionsGroup. sub set_config { my ($self, $config) = @_; $self->{config} = $config; $self->update_optgroup; } +# Sets the options listed in the Add button. sub set_opt_keys { my ($self, $opt_keys) = @_; @@ -85,6 +88,7 @@ sub set_opt_keys { $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; } +# Sets the options that user can't remove. sub set_fixed_options { my ($self, $opt_keys) = @_; $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; @@ -97,6 +101,8 @@ sub update_optgroup { $self->{options_sizer}->Clear(1); return if !defined $self->{config}; + $self->{btn_add}->Show($self->{editable}); + my %categories = (); foreach my $opt_key (@{$self->{config}->get_keys}) { my $category = $Slic3r::Config::Options->{$opt_key}{category}; @@ -104,7 +110,8 @@ sub update_optgroup { push @{$categories{$category}}, $opt_key; } foreach my $category (sort keys %categories) { - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( + my $optgroup; + $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( parent => $self, title => $category, config => $self->{config}, @@ -116,10 +123,11 @@ sub update_optgroup { extra_column => sub { my ($line) = @_; - my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line + my $opt_id = $line->get_options->[0]->opt_id; # we assume that we have one option per line + my ($opt_key, $opt_index) = @{ $optgroup->_opt_map->{$opt_id} }; # disallow deleting fixed options - return undef if $self->{fixed_options}{$opt_key}; + return undef if $self->{fixed_options}{$opt_key} || !$self->{editable}; my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); @@ -132,7 +140,9 @@ sub update_optgroup { }, ); foreach my $opt_key (sort @{$categories{$category}}) { - $optgroup->append_single_option_line($opt_key); + # For array options we override the first value. + my $opt_index = (ref($self->{config}->get($opt_key)) eq 'ARRAY') ? 0 : -1; + $optgroup->append_single_option_line($opt_key, $opt_index); } $self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0); } @@ -154,4 +164,11 @@ sub disable { $self->Disable; } +# Shows or hides the Add button. +sub set_editable { + my ($self, $editable) = @_; + + $self->{editable} = $editable; +} + 1; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index 91465fa64..c6f2f1d13 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -20,16 +20,6 @@ sub new { }, label_width => 200, ); - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'mode', - type => 'select', - label => 'Mode', - tooltip => 'Choose between a simpler, basic mode and an expert mode with more options and more complicated interface.', - labels => ['Simple','Expert'], - values => ['simple','expert'], - default => $Slic3r::GUI::Settings->{_}{mode}, - width => 100, - )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'version_check', type => 'bool', diff --git a/lib/Slic3r/GUI/Preset.pm b/lib/Slic3r/GUI/Preset.pm new file mode 100644 index 000000000..35208f585 --- /dev/null +++ b/lib/Slic3r/GUI/Preset.pm @@ -0,0 +1,52 @@ +package Slic3r::GUI::Preset; +use Moo; +use Unicode::Normalize; + +has 'default' => (is => 'ro', default => sub { 0 }); +has 'external' => (is => 'ro', default => sub { 0 }); +has 'name' => (is => 'rw', required => 1); +has 'file' => (is => 'rw'); + +sub BUILD { + my ($self) = @_; + + $self->name(Unicode::Normalize::NFC($self->name)); +} + +sub config { + my ($self, $keys, $extra_keys) = @_; + + if ($self->default) { + return Slic3r::Config->new_from_defaults(@$keys); + } else { + if (!-e Slic3r::encode_path($self->file)) { + Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); + return undef; + } + my $external_config = $self->load_config; + if (!$keys) { + return $external_config; + } else { + # apply preset values on top of defaults + my $config = Slic3r::Config->new_from_defaults(@$keys); + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @$keys; + + # For extra_keys we don't populate defaults. + if ($extra_keys) { + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @$extra_keys; + } + + return $config; + } + } +} + +sub load_config { + my ($self) = @_; + + return Slic3r::Config->load($self->file); +} + +1; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/PresetEditor.pm similarity index 85% rename from lib/Slic3r/GUI/Tab.pm rename to lib/Slic3r/GUI/PresetEditor.pm index 1b61e3f38..da70a1840 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -1,7 +1,4 @@ -# The "Expert" tab at the right of the main tabbed window. -# The "Expert" is enabled by File->Preferences dialog. - -package Slic3r::GUI::Tab; +package Slic3r::GUI::PresetEditor; use strict; use warnings; use utf8; @@ -22,7 +19,7 @@ sub new { # horizontal sizer $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); - $self->{sizer}->SetSizeHints($self); + #$self->{sizer}->SetSizeHints($self); $self->SetSizer($self->{sizer}); # left vertical sizer @@ -89,7 +86,6 @@ sub new { EVT_CHOICE($parent, $self->{presets_choice}, sub { $self->on_select_preset; - $self->_on_presets_changed; }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); @@ -106,12 +102,15 @@ sub new { $self->{presets_choice}->Delete($i); $self->current_preset(undef); $self->select_preset(0); - $self->_on_presets_changed; }); # C++ instance DynamicPrintConfig $self->{config} = Slic3r::Config->new; # Initialize the DynamicPrintConfig by default keys/values. + $self->{config}->apply(Slic3r::Config->new_from_defaults($self->options)); + + + # Possible %params keys: no_controller $self->build(%params); $self->update_tree; @@ -128,6 +127,12 @@ sub get_current_preset { return $self->get_preset($self->current_preset); } +sub get_current_preset_config { + my ($self) = @_; + + return $self->get_preset_config($self->get_current_preset); +} + sub get_preset { my ($self, $i) = @_; return $self->{presets}[$i]; @@ -151,14 +156,15 @@ sub save_preset { default => $default_name, values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); - return unless $dlg->ShowModal == wxID_OK; + return 0 unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); $self->load_presets; $self->select_preset_by_name($name); - $self->_on_presets_changed; + + return 1; } sub on_value_change { @@ -166,11 +172,6 @@ sub on_value_change { $self->{on_value_change} = $cb; } -sub on_presets_changed { - my ($self, $cb) = @_; - $self->{on_presets_changed} = $cb; -} - # This method is supposed to be called whenever new values are loaded # or changed by user (so also when a preset is loaded). # propagate event to the parent @@ -183,40 +184,27 @@ sub _on_value_change { sub _update {} -sub _on_presets_changed { - my $self = shift; - - $self->{on_presets_changed}->( - $self->{presets}, - scalar($self->{presets_choice}->GetSelection), - $self->is_dirty, - ) if $self->{on_presets_changed}; -} - sub on_preset_loaded {} sub hidden_options {} sub config { $_[0]->{config}->clone } -sub select_default_preset { - my $self = shift; - $self->select_preset(0); -} - sub select_preset { - my $self = shift; - $self->{presets_choice}->SetSelection($_[0]); - $self->on_select_preset; + my ($self, $i, $force) = @_; + + $self->{presets_choice}->SetSelection($i); + $self->on_select_preset($force); } sub select_preset_by_name { - my ($self, $name) = @_; + my ($self, $name, $force) = @_; - $name = Unicode::Normalize::NFC($name); - $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); + my $i = first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}; + return if !defined $i; + $self->select_preset($i, $force); } -sub on_select_preset { - my $self = shift; +sub prompt_unsaved_changes { + my ($self) = @_; if ($self->is_dirty) { my $old_preset = $self->get_current_preset; @@ -233,27 +221,39 @@ sub on_select_preset { } my $changes = join "\n", map "- $_", @option_names; - my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", - 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if ($confirm->ShowModal == wxID_NO) { - $self->{presets_choice}->SetSelection($self->current_preset); - - # trigger the on_presets_changed event so that we also restore the previous value - # in the plater selector - $self->_on_presets_changed; - return; + my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDo you want to save them?", + 'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION); + $confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel'); + my $res = $confirm->ShowModal; + + if ($res == wxID_CANCEL) { + return 0; + } elsif ($res == wxID_YES) { + return $self->save_preset($old_preset->default ? undef : $old_preset->name); } } - $self->current_preset($self->{presets_choice}->GetSelection); + return 1; +} + +sub on_select_preset { + my ($self, $force) = @_; + + my $i = $self->{presets_choice}->GetSelection; + return if defined $self->current_preset && $i == $self->current_preset; + + if (!$force && defined $self->current_preset && !$self->prompt_unsaved_changes) { + $self->{presets_choice}->SetSelection($self->current_preset); + return; + } + + $self->current_preset($i); my $preset = $self->get_current_preset; my $preset_config = $self->get_preset_config($preset); eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - foreach my $opt_key (@{$self->{config}->get_keys}) { - $self->{config}->set($opt_key, $preset_config->get($opt_key)) - if $preset_config->has($opt_key); - } + $self->{config}->clear; + $self->{config}->apply($preset_config); ($preset->default || $preset->external) ? $self->{btn_delete_preset}->Disable : $self->{btn_delete_preset}->Enable; @@ -261,12 +261,10 @@ sub on_select_preset { $self->_update; $self->on_preset_loaded; $self->reload_config; - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; }; if ($@) { $@ = "I was unable to load the selected config file: $@"; Slic3r::GUI::catch_error($self); - $self->select_default_preset; } # use CallAfter because some field triggers schedule on_change calls using CallAfter, @@ -274,16 +272,8 @@ sub on_select_preset { # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { - $self->_on_presets_changed; $self->update_dirty; }); - - wxTheApp->save_settings; -} - -sub init_config_options { - my ($self, @opt_keys) = @_; - $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); } sub add_options_page { @@ -296,7 +286,7 @@ sub add_options_page { $self->{iconcount}++; } - my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); + my $page = Slic3r::GUI::PresetEditor::Page->new($self, $title, $self->{iconcount}); $page->Hide; $self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; @@ -345,7 +335,6 @@ sub update_dirty { } } $self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769 - $self->_on_presets_changed; } sub is_dirty { @@ -357,36 +346,23 @@ sub dirty_options { my $self = shift; return [] if !defined $self->current_preset; # happens during initialization - return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); + + my $preset_config = $self->get_current_preset_config; + return [ + (grep !$preset_config->has($_), @{$self->{config}->get_keys}), + (grep !$self->{config}->has($_), @{$preset_config->get_keys}), + @{ $self->{config}->diff($self->get_current_preset_config) }, + ]; } sub load_presets { my $self = shift; - $self->{presets} = [ - Slic3r::GUI::Tab::Preset->new( - default => 1, - name => '- default -', - ), - ]; - - my %presets = wxTheApp->presets($self->name); - foreach my $preset_name (sort keys %presets) { - push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( - name => $preset_name, - file => $presets{$preset_name}, - ); - } - + $self->{presets} = [ wxTheApp->presets($self->name, 1) ]; $self->current_preset(undef); $self->{presets_choice}->Clear; $self->{presets_choice}->Append($_->name) for @{$self->{presets}}; - { - # load last used preset - my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; - $self->select_preset($i || 0); - } - $self->_on_presets_changed; + $self->select_preset(0); } sub load_config_file { @@ -397,7 +373,7 @@ sub load_config_file { my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}}; if (!$i) { my $preset_name = basename($file); # keep the .ini suffix - push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( + push @{$self->{presets}}, Slic3r::GUI::Preset->new( file => $file, name => $preset_name, external => 1, @@ -407,7 +383,6 @@ sub load_config_file { } $self->{presets_choice}->SetSelection($i); $self->on_select_preset; - $self->_on_presets_changed; } sub load_config { @@ -424,7 +399,11 @@ sub load_config { sub get_preset_config { my ($self, $preset) = @_; - return $preset->config($self->{config}->get_keys); + + return $preset->config( + [ $self->options ], + [ $self->overriding_options ], + ); } sub get_field { @@ -448,19 +427,64 @@ sub set_value { return $changed; } -package Slic3r::GUI::Tab::Print; -use base 'Slic3r::GUI::Tab'; +sub _compatible_printers_widget { + my ($self) = @_; + + return sub { + my ($parent) = @_; + + my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG)); + } + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($btn); + + EVT_BUTTON($self, $btn, sub { + my @presets = map $_->name, grep !$_->default && !$_->external, + wxTheApp->presets('printer'); + + my $dlg = Wx::MultiChoiceDialog->new($self, + "Select the printers this profile is compatible with.\nIf none are selected, it will be considered compatible with all of them.", + "Compatible printers", \@presets); + + my @selections = (); + foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { + push @selections, first { $presets[$_] eq $preset_name } 0..$#presets; + } + $dlg->SetSelections(@selections); + + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @presets[$dlg->GetSelections] ]; + $self->{config}->set('compatible_printers', $value); + $self->update_dirty; + $self->_on_value_change('compatible_printers', $value); + } + }); + + return $sizer; + }; +} + +sub options { die "Unimplemented options()"; } +sub overridable_options { () } +sub overriding_options { () } + +package Slic3r::GUI::PresetEditor::Print; +use base 'Slic3r::GUI::PresetEditor'; use List::Util qw(first any); -use Wx qw(:icon :dialog :id); +use Wx qw(:icon :dialog :id :misc :button :sizer); +use Wx::Event qw(EVT_BUTTON); sub name { 'print' } sub title { 'Print Settings' } -sub build { - my $self = shift; - - $self->init_config_options(qw( +sub options { + return qw( layer_height first_layer_height perimeters spiral_vase top_solid_layers bottom_solid_layers @@ -497,9 +521,54 @@ sub build { external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width top_infill_extrusion_width support_material_extrusion_width infill_overlap bridge_flow_ratio - xy_size_compensation threads resolution - )); - $self->{config}->set('print_settings_id', ''); + xy_size_compensation threads resolution overridable compatible_printers + print_settings_id + ) +} + +sub build { + my $self = shift; + + my $overridable_widget = sub { + my ($parent) = @_; + + my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + if ($Slic3r::GUI::have_button_icons) { + $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + } + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($btn); + + EVT_BUTTON($self, $btn, sub { + my %options = ( + map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } + map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new + ); + my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options; + + my $dlg = Wx::MultiChoiceDialog->new($self, "Selected options will be displayed in the plater screen for quick changes.", + "Overridable options", + [ map $options{$_}, @opt_keys ]); + + my @selections = (); + foreach my $opt_key (@{ $self->{config}->get('overridable') }) { + push @selections, first { $opt_keys[$_] eq $opt_key } 0..$#opt_keys; + } + $dlg->SetSelections(@selections); + + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @opt_keys[$dlg->GetSelections] ]; + $self->{config}->set('overridable', $value); + $self->update_dirty; + $self->_on_value_change('overridable', $value); + } + }); + + return $sizer; + }; { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); @@ -643,7 +712,7 @@ sub build { } { - my $page = $self->add_options_page('Multiple Extruders', 'funnel.png'); + my $page = $self->add_options_page('Multiple extruders', 'funnel.png'); { my $optgroup = $page->new_optgroup('Extruders'); $optgroup->append_single_option_line('perimeter_extruder'); @@ -728,7 +797,7 @@ sub build { } { - my $page = $self->add_options_page('Notes', 'note.png'); + my $page = $self->add_options_page('Notes and preferences', 'note.png'); { my $optgroup = $page->new_optgroup('Notes', label_width => 0, @@ -738,6 +807,23 @@ sub build { $option->height(250); $optgroup->append_single_option_line($option); } + { + my $optgroup = $page->new_optgroup('Profile preferences'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Overridable settings', + widget => $overridable_widget, + ); + $optgroup->append_line($line); + } + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } } } @@ -890,24 +976,34 @@ sub _update { sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } -package Slic3r::GUI::Tab::Filament; -use base 'Slic3r::GUI::Tab'; +package Slic3r::GUI::PresetEditor::Filament; +use base 'Slic3r::GUI::PresetEditor'; + +use Wx qw(wxTheApp); sub name { 'filament' } sub title { 'Filament Settings' } -sub build { - my $self = shift; - - $self->init_config_options(qw( +sub options { + return qw( filament_colour filament_diameter filament_notes filament_max_volumetric_speed extrusion_multiplier filament_density filament_cost temperature first_layer_temperature bed_temperature first_layer_bed_temperature - fan_always_on cooling + fan_always_on cooling compatible_printers min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers fan_below_layer_time slowdown_below_layer_time min_print_speed start_filament_gcode end_filament_gcode - )); - $self->{config}->set('filament_settings_id', ''); + filament_settings_id + ); +} + +sub overriding_options { + return ( + Slic3r::GUI::PresetEditor::Printer->overridable_options, + ); +} + +sub build { + my $self = shift; { my $page = $self->add_options_page('Filament', 'spool.png'); @@ -987,26 +1083,6 @@ sub build { } } - { - my $page = $self->add_options_page('Advanced', 'wrench.png'); - { - my $optgroup = $page->new_optgroup('Print speed override'); - $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); - } - } - - { - my $page = $self->add_options_page('Notes', 'note.png'); - { - my $optgroup = $page->new_optgroup('Notes', - label_width => 0, - ); - my $option = $optgroup->get_option('filament_notes', 0); - $option->full_width(1); - $option->height(250); - $optgroup->append_single_option_line($option); - } - } { my $page = $self->add_options_page('Custom G-code', 'cog.png'); { @@ -1028,6 +1104,81 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Overrides', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile preferences'); + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + { + my $optgroup = $page->new_optgroup('Overrides'); + $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + + # Populate the overrides config. + my @overridable = $self->overriding_options; + $self->{overrides_config} = Slic3r::Config->new; + + # Populate the defaults with the current preset. + $self->{overrides_default_config} = Slic3r::Config->new; + $self->{overrides_default_config}->apply_only + (wxTheApp->{mainframe}->{plater}->config, \@overridable); + + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => '', + full_width => 1, + widget => sub { + my ($parent) = @_; + + $self->{overrides_panel} = my $panel = Slic3r::GUI::Plater::OverrideSettingsPanel->new($parent, + size => [-1, 300], + on_change => sub { + $self->{config}->erase($_) for @overridable; + $self->{config}->apply($self->{overrides_config}); + $self->update_dirty; + }); + $panel->set_editable(1); + $panel->set_default_config($self->{overrides_default_config}); + $panel->set_config($self->{overrides_config}); + $panel->set_opt_keys([@overridable]); + + return $panel; + }, + ); + $optgroup->append_line($line); + } + } + + { + my $page = $self->add_options_page('Notes', 'note.png'); + { + my $optgroup = $page->new_optgroup('Notes', + label_width => 0, + ); + my $option = $optgroup->get_option('filament_notes', 0); + $option->full_width(1); + $option->height(250); + $optgroup->append_single_option_line($option); + } + } +} + +sub reload_config { + my ($self) = @_; + + $self->{overrides_config}->clear; + foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) { + if ($self->{config}->has($opt_key)) { + $self->{overrides_config}->set($opt_key, $self->{config}->get($opt_key)); + } + } + $self->{overrides_panel}->update_optgroup; + + $self->SUPER::reload_config; } sub _update { @@ -1071,19 +1222,16 @@ sub _update_description { $self->{description_line}->SetText($msg); } -package Slic3r::GUI::Tab::Printer; -use base 'Slic3r::GUI::Tab'; +package Slic3r::GUI::PresetEditor::Printer; +use base 'Slic3r::GUI::PresetEditor'; use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog); use Wx::Event qw(EVT_BUTTON); sub name { 'printer' } sub title { 'Printer Settings' } -sub build { - my $self = shift; - my (%params) = @_; - - $self->init_config_options(qw( +sub options { + return qw( bed_shape z_offset z_steps_per_mm has_heatbed gcode_flavor use_relative_e_distances serial_port serial_speed @@ -1093,9 +1241,22 @@ sub build { start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode nozzle_diameter extruder_offset retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe - retract_length_toolchange retract_restart_extra_toolchange - )); - $self->{config}->set('printer_settings_id', ''); + retract_length_toolchange retract_restart_extra_toolchange retract_lift_above retract_lift_below + printer_settings_id + ); +} + +sub overridable_options { + return qw( + pressure_advance + retract_length retract_lift retract_speed retract_restart_extra + retract_before_travel retract_layer_change wipe + ); +} + +sub build { + my $self = shift; + my (%params) = @_; $self->{extruders_count} = 1; @@ -1484,7 +1645,7 @@ sub load_config_file { if @{ $self->{config}->nozzle_diameter } > 1; } -package Slic3r::GUI::Tab::Page; +package Slic3r::GUI::PresetEditor::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; @@ -1603,33 +1764,4 @@ sub get_name { return $self->{chosen_name}; } -package Slic3r::GUI::Tab::Preset; -use Moo; - -has 'default' => (is => 'ro', default => sub { 0 }); -has 'external' => (is => 'ro', default => sub { 0 }); -has 'name' => (is => 'rw', required => 1); -has 'file' => (is => 'rw'); - -sub config { - my ($self, $keys) = @_; - - if ($self->default) { - return Slic3r::Config->new_from_defaults(@$keys); - } else { - if (!-e Slic3r::encode_path($self->file)) { - Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); - return undef; - } - - # apply preset values on top of defaults - my $config = Slic3r::Config->new_from_defaults(@$keys); - my $external_config = Slic3r::Config->load($self->file); - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$keys; - - return $config; - } -} - 1; diff --git a/lib/Slic3r/GUI/PresetEditorDialog.pm b/lib/Slic3r/GUI/PresetEditorDialog.pm new file mode 100644 index 000000000..349f1e871 --- /dev/null +++ b/lib/Slic3r/GUI/PresetEditorDialog.pm @@ -0,0 +1,65 @@ +package Slic3r::GUI::PresetEditorDialog; +use strict; +use warnings; +use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp); +use Wx::Event qw(EVT_CLOSE); +use base qw(Wx::Dialog Class::Accessor); +use utf8; + +__PACKAGE__->mk_accessors(qw(preset_editor)); + +sub new { + my ($class, $parent) = @_; + my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [800,500], + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxDIALOG_EX_METAL); + + $self->preset_editor($self->preset_editor_class->new($self)); + $self->SetTitle($self->preset_editor->title); + $self->preset_editor->load_presets; + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add($self->preset_editor, 1, wxEXPAND); + + $self->SetSizer($sizer); + #$sizer->SetSizeHints($self); + + if (0) { + # This does not call the EVT_CLOSE below + my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE); + $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + } + + EVT_CLOSE($self, sub { + my (undef, $event) = @_; + + if ($event->CanVeto && !$self->preset_editor->prompt_unsaved_changes) { + $event->Veto; + return; + } + + # propagate event + $event->Skip; + }); + + return $self; +} + + +package Slic3r::GUI::PresetEditorDialog::Printer; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Printer" } + + +package Slic3r::GUI::PresetEditorDialog::Filament; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Filament" } + + +package Slic3r::GUI::PresetEditorDialog::Print; +use base qw(Slic3r::GUI::PresetEditorDialog); + +sub preset_editor_class { "Slic3r::GUI::PresetEditor::Print" } + +1; diff --git a/lib/Slic3r/GUI/Projector.pm b/lib/Slic3r/GUI/Projector.pm index 9023cb0c7..c41b7e8ad 100644 --- a/lib/Slic3r/GUI/Projector.pm +++ b/lib/Slic3r/GUI/Projector.pm @@ -44,7 +44,7 @@ sub new { $self->config(Slic3r::Config->new_from_defaults( qw(serial_port serial_speed bed_shape start_gcode end_gcode z_offset) )); - $self->config->apply(wxTheApp->{mainframe}->config); + $self->config->apply(wxTheApp->{mainframe}->{plater}->config); my @optgroups = (); { @@ -520,9 +520,6 @@ sub _close { } wxTheApp->{mainframe}->Show; - my $printer_tab = wxTheApp->{mainframe}{options_tabs}{printer}; - $printer_tab->load_config($self->config); - $self->EndModal(wxID_OK); } @@ -555,7 +552,7 @@ sub BUILD { # init print { my $print = Slic3r::SLAPrint->new(wxTheApp->{mainframe}->{plater}->{model}); - $print->apply_config(wxTheApp->{mainframe}->config); + $print->apply_config(wxTheApp->{mainframe}->{plater}->config); $self->_print($print); $self->screen->print($print); diff --git a/lib/Slic3r/GUI/SLAPrintOptions.pm b/lib/Slic3r/GUI/SLAPrintOptions.pm index 7b3348823..042ddc288 100644 --- a/lib/Slic3r/GUI/SLAPrintOptions.pm +++ b/lib/Slic3r/GUI/SLAPrintOptions.pm @@ -10,7 +10,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, "SLA/DLP Print", wxDefaultPosition, wxDefaultSize); $self->config(Slic3r::Config::SLAPrint->new); - $self->config->apply_dynamic(wxTheApp->{mainframe}->config); + $self->config->apply_dynamic(wxTheApp->{mainframe}->{plater}->config); my $sizer = Wx::BoxSizer->new(wxVERTICAL); my $new_optgroup = sub { @@ -103,8 +103,6 @@ sub _accept { return; } - wxTheApp->{mainframe}->load_config($self->config->dynamic); - $self->EndModal(wxID_OK); $self->Close; # needed on Linux @@ -113,6 +111,9 @@ sub _accept { # this double invocation is needed for properly hiding the MainFrame $projector->Show; $projector->ShowModal; + + # TODO: diff the new config with the selected presets and prompt the user for + # applying the changes to them. } 1; diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm deleted file mode 100644 index 6e7d00d09..000000000 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ /dev/null @@ -1,351 +0,0 @@ -# The "Simple" Print Settings tab. -# The "Simple" mode is enabled by File->Preferences dialog. - -package Slic3r::GUI::SimpleTab; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename); -use List::Util qw(first); -use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings); -use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN); -use base 'Wx::ScrolledWindow'; - -sub new { - my $class = shift; - my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - - $self->SetScrollbars(1, 1, 1, 1); - - $self->{config} = Slic3r::Config->new; - $self->{optgroups} = []; - - $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); - $self->SetSizer($self->{vsizer}); - $self->build; - $self->_update; - - { - my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize); - $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); - $self->{vsizer}->Add($label, 0, wxEXPAND | wxALL, 10); - } - - return $self; -} - -sub init_config_options { - my ($self, @opt_keys) = @_; - $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); -} - -sub new_optgroup { - my ($self, $title, %params) = @_; - - my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( - parent => $self, - title => $title, - config => $self->{config}, - label_width => $params{label_width} // 200, - on_change => sub { $self->_on_value_change(@_) }, - ); - - push @{$self->{optgroups}}, $optgroup; - $self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); - - return $optgroup; -} - -sub load_config_file { - my $self = shift; - my ($file) = @_; - - my $config = Slic3r::Config->load($file); - $self->load_config($config); -} - -sub load_config { - my $self = shift; - my ($config) = @_; - - foreach my $opt_key (@{$self->{config}->get_keys}) { - next unless $config->has($opt_key); - $self->{config}->set($opt_key, $config->get($opt_key)); - } - $_->reload_config for @{$self->{optgroups}}; - $self->_update; -} - -sub load_presets {} - -sub is_dirty { 0 } -sub config { $_[0]->{config}->clone } -sub _update {} - -sub on_value_change { - my ($self, $cb) = @_; - $self->{on_value_change} = $cb; -} - -sub on_presets_changed {} - -# propagate event to the parent -sub _on_value_change { - my $self = shift; - - $self->{on_value_change}->(@_) if $self->{on_value_change}; - $self->_update; -} - -sub get_field { - my ($self, $opt_key, $opt_index) = @_; - - foreach my $optgroup (@{ $self->{optgroups} }) { - my $field = $optgroup->get_fieldc($opt_key, $opt_index); - return $field if defined $field; - } - return undef; -} - -package Slic3r::GUI::SimpleTab::Print; -use base 'Slic3r::GUI::SimpleTab'; - -use Wx qw(:sizer); - -sub name { 'print' } -sub title { 'Print Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - layer_height perimeters top_solid_layers bottom_solid_layers - fill_density fill_pattern top_infill_pattern bottom_infill_pattern - support_material support_material_spacing raft_layers - support_material_contact_distance dont_support_bridges - perimeter_speed infill_speed travel_speed - brim_width - xy_size_compensation - )); - - { - my $optgroup = $self->new_optgroup('General'); - $optgroup->append_single_option_line('layer_height'); - $optgroup->append_single_option_line('perimeters'); - - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Solid layers', - ); - $line->append_option($optgroup->get_option('top_solid_layers')); - $line->append_option($optgroup->get_option('bottom_solid_layers')); - $optgroup->append_line($line); - } - - { - my $optgroup = $self->new_optgroup('Infill'); - $optgroup->append_single_option_line('fill_density'); - $optgroup->append_single_option_line('fill_pattern'); - $optgroup->append_single_option_line('top_infill_pattern'); - $optgroup->append_single_option_line('bottom_infill_pattern'); - } - - { - my $optgroup = $self->new_optgroup('Support material'); - $optgroup->append_single_option_line('support_material'); - $optgroup->append_single_option_line('support_material_spacing'); - $optgroup->append_single_option_line('support_material_contact_distance'); - $optgroup->append_single_option_line('dont_support_bridges'); - $optgroup->append_single_option_line('raft_layers'); - } - - { - my $optgroup = $self->new_optgroup('Speed'); - $optgroup->append_single_option_line('perimeter_speed'); - $optgroup->append_single_option_line('infill_speed'); - $optgroup->append_single_option_line('travel_speed'); - } - - { - my $optgroup = $self->new_optgroup('Brim'); - $optgroup->append_single_option_line('brim_width'); - } - - { - my $optgroup = $self->new_optgroup('Other'); - $optgroup->append_single_option_line('xy_size_compensation'); - } -} - -sub _update { - my ($self) = @_; - - my $config = $self->{config}; - - my $have_perimeters = $config->perimeters > 0; - $self->get_field($_)->toggle($have_perimeters) - for qw(perimeter_speed); - - my $have_infill = $config->fill_density > 0; - my $have_solid_infill = $config->top_solid_layers > 0 || $config->bottom_solid_layers > 0; - $self->get_field($_)->toggle($have_infill) - for qw(fill_pattern); - $self->get_field($_)->toggle($have_solid_infill) - for qw(top_infill_pattern bottom_infill_pattern); - $self->get_field($_)->toggle($have_infill || $have_solid_infill) - for qw(infill_speed); - - my $have_support_material = $config->support_material || $config->raft_layers > 0; - $self->get_field($_)->toggle($have_support_material) - for qw(support_material_spacing dont_support_bridges - support_material_contact_distance); -} - -package Slic3r::GUI::SimpleTab::Filament; -use base 'Slic3r::GUI::SimpleTab'; - -sub name { 'filament' } -sub title { 'Filament Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - filament_diameter extrusion_multiplier - temperature first_layer_temperature bed_temperature first_layer_bed_temperature - )); - - { - my $optgroup = $self->new_optgroup('Filament'); - $optgroup->append_single_option_line('filament_diameter', 0); - $optgroup->append_single_option_line('extrusion_multiplier', 0); - } - - { - my $optgroup = $self->new_optgroup('Temperature (°C)'); - - { - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Extruder', - ); - $line->append_option($optgroup->get_option('first_layer_temperature', 0)); - $line->append_option($optgroup->get_option('temperature', 0)); - $optgroup->append_line($line); - } - - { - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Bed', - ); - $line->append_option($optgroup->get_option('first_layer_bed_temperature')); - $line->append_option($optgroup->get_option('bed_temperature')); - $optgroup->append_line($line); - } - } -} - -package Slic3r::GUI::SimpleTab::Printer; -use base 'Slic3r::GUI::SimpleTab'; -use Wx qw(:sizer :button :bitmap :misc :id); -use Wx::Event qw(EVT_BUTTON); - -sub name { 'printer' } -sub title { 'Printer Settings' } - -sub build { - my $self = shift; - - $self->init_config_options(qw( - bed_shape - z_offset - gcode_flavor - nozzle_diameter - retract_length retract_lift wipe - start_gcode - end_gcode - )); - - { - my $bed_shape_widget = sub { - my ($parent) = @_; - - my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - $btn->SetFont($Slic3r::GUI::small_font); - if ($Slic3r::GUI::have_button_icons) { - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); - } - - my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($btn); - - EVT_BUTTON($self, $btn, sub { - my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue; - $self->{config}->set('bed_shape', $value); - $self->_on_value_change('bed_shape', $value); - } - }); - - return $sizer; - }; - - my $optgroup = $self->new_optgroup('Size and coordinates'); - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Bed shape', - widget => $bed_shape_widget, - ); - $optgroup->append_line($line); - $optgroup->append_single_option_line('z_offset'); - } - - { - my $optgroup = $self->new_optgroup('Firmware'); - $optgroup->append_single_option_line('gcode_flavor'); - } - - { - my $optgroup = $self->new_optgroup('Extruder'); - $optgroup->append_single_option_line('nozzle_diameter', 0); - } - - { - my $optgroup = $self->new_optgroup('Retraction'); - $optgroup->append_single_option_line('retract_length', 0); - $optgroup->append_single_option_line('retract_lift', 0); - $optgroup->append_single_option_line('wipe', 0); - } - - { - my $optgroup = $self->new_optgroup('Start G-code', - label_width => 0, - ); - my $option = $optgroup->get_option('start_gcode'); - $option->full_width(1); - $option->height(150); - $optgroup->append_single_option_line($option); - } - - { - my $optgroup = $self->new_optgroup('End G-code', - label_width => 0, - ); - my $option = $optgroup->get_option('end_gcode'); - $option->full_width(1); - $option->height(150); - $optgroup->append_single_option_line($option); - } -} - -sub _update { - my ($self) = @_; - - my $config = $self->{config}; - - my $have_retraction = $config->retract_length->[0] > 0; - $self->get_field($_, 0)->toggle($have_retraction) - for qw(retract_lift wipe); -} - -1; diff --git a/slic3r.pl b/slic3r.pl index d4ebd5c48..3aa3292ba 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -35,8 +35,6 @@ my %cli_options = (); 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-controller' => \$opt{no_controller}, - 'no-plater' => \$opt{no_plater}, - 'gui-mode=s' => \$opt{gui_mode}, 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, @@ -77,28 +75,26 @@ if ($opt{load}) { $opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n"; } } + + # expand shortcuts before applying, otherwise destination values would be already filled with defaults + $_->normalize for @external_configs; } # process command line options -my $cli_config = Slic3r::Config->new; -foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) { - $c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults - $cli_config->apply($c); -} +my $cli_config = Slic3r::Config->new_from_cli(%cli_options); +$cli_config->normalize; # expand shortcuts # save configuration if ($opt{save}) { - if (@{$cli_config->get_keys} > 0) { - $cli_config->save($opt{save}); + my $config = $cli_config->clone; + $config->apply($_) for @external_configs; + if (@{$config->get_keys} > 0) { + $config->save($opt{save}); } else { Slic3r::Config->new_from_defaults->save($opt{save}); } } -# apply command line config on top of default config -my $config = Slic3r::Config->new_from_defaults; -$config->apply($cli_config); - # launch GUI my $gui; if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { @@ -106,9 +102,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { no warnings 'once'; $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); $Slic3r::GUI::no_controller = $opt{no_controller}; - $Slic3r::GUI::no_plater = $opt{no_plater}; - $Slic3r::GUI::mode = $opt{gui_mode}; - $Slic3r::GUI::autosave = $opt{autosave}; + $Slic3r::GUI::autosave = Slic3r::decode_path($opt{autosave} // ''); } $gui = Slic3r::GUI->new; setlocale(LC_NUMERIC, 'C'); @@ -117,7 +111,7 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { $gui->{mainframe}->load_config($cli_config); foreach my $input_file (@ARGV) { $input_file = Slic3r::decode_path($input_file); - $gui->{mainframe}{plater}->load_file($input_file) unless $opt{no_plater}; + $gui->{mainframe}{plater}->load_file($input_file); } }); $gui->MainLoop; @@ -126,6 +120,9 @@ if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line + # apply command line config on top of default config + my $config = Slic3r::Config->new_from_defaults; + $config->apply($cli_config); $config->validate; if ($opt{repair}) { @@ -327,7 +324,6 @@ $j --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab - --gui-mode Overrides the configured mode (simple/expert) --autosave Automatically export current configuration to the specified file Output options: diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index ccb357cf2..d5a67702a 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -241,21 +241,24 @@ ConfigBase::has(const t_config_option_key &opt_key) { void ConfigBase::apply(const ConfigBase &other, bool ignore_nonexistent) { - // get list of option keys to apply - t_config_option_keys opt_keys = other.keys(); - + // apply all options + this->apply_only(other, other.keys(), ignore_nonexistent); +} + +void +ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent) { // loop through options and apply them - for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { - ConfigOption* my_opt = this->option(*it, true); + for (const t_config_option_key &opt_key : opt_keys) { + ConfigOption* my_opt = this->option(opt_key, true); if (my_opt == NULL) { if (ignore_nonexistent == false) throw "Attempt to apply non-existent option"; continue; } // not the most efficient way, but easier than casting pointers to subclasses - bool res = my_opt->deserialize( other.option(*it)->serialize() ); + bool res = my_opt->deserialize( other.option(opt_key)->serialize() ); if (!res) { - std::string error = "Unexpected failure when deserializing serialized value for " + *it; + std::string error = "Unexpected failure when deserializing serialized value for " + opt_key; CONFESS(error.c_str()); } } @@ -500,6 +503,11 @@ DynamicConfig::erase(const t_config_option_key &opt_key) { this->options.erase(opt_key); } +void +DynamicConfig::clear() { + this->options.clear(); +} + void DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* extra) { diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 8e375701e..908d761a8 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -665,6 +665,7 @@ class ConfigBase virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; virtual t_config_option_keys keys() const = 0; void apply(const ConfigBase &other, bool ignore_nonexistent = false); + void apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent = false); bool equals(ConfigBase &other); t_config_option_keys diff(ConfigBase &other); std::string serialize(const t_config_option_key &opt_key) const; @@ -691,6 +692,7 @@ class DynamicConfig : public virtual ConfigBase virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); t_config_option_keys keys() const; void erase(const t_config_option_key &opt_key); + void clear(); void read_cli(const int argc, const char **argv, t_config_option_keys* extra); private: diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index caeaaf8da..4f0f071de 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -131,6 +131,10 @@ PrintConfigDef::PrintConfigDef() def->cli = "brim-width=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); + + def = this->add("compatible_printers", coStrings); + def->label = "Compatible printers"; + def->default_value = new ConfigOptionStrings(); def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; @@ -784,7 +788,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication."; def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); - + def = this->add("octoprint_host", coString); def->label = "Host or IP"; def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance."; @@ -816,6 +820,14 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); + + def = this->add("overridable", coStrings); + def->label = "Overridable options"; + { + ConfigOptionStrings* opt = new ConfigOptionStrings(); + opt->values.push_back("support_material"); + def->default_value = opt; + } def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; @@ -876,6 +888,7 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 60; + def->default_value = new ConfigOptionStrings(); def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); @@ -885,6 +898,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("pressure_advance", coFloat); def->label = "Pressure advance"; + def->category = "Extruder"; def->tooltip = "When set to a non-zero value, this experimental option enables pressure regulation. It's the K constant for the advance algorithm that pushes more or less filament upon speed changes. It's useful for Bowden-tube extruders. Reasonable values are in range 0-10."; def->cli = "pressure-advance=f"; def->min = 0; @@ -918,6 +932,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_before_travel", coFloats); def->label = "Minimum travel after retraction"; + def->category = "Retraction"; def->tooltip = "Retraction is not triggered when travel moves are shorter than this length."; def->sidetext = "mm"; def->cli = "retract-before-travel=f@"; @@ -929,6 +944,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_layer_change", coBools); def->label = "Retract on layer change"; + def->category = "Retraction"; def->tooltip = "This flag enforces a retraction whenever a Z move is done."; def->cli = "retract-layer-change!"; { @@ -939,6 +955,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_length", coFloats); def->label = "Length"; + def->category = "Retraction"; def->full_label = "Retraction Length"; def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; def->sidetext = "mm (zero to disable)"; @@ -963,6 +980,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift", coFloats); def->label = "Lift Z"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered."; def->sidetext = "mm"; def->cli = "retract-lift=f@"; @@ -975,6 +993,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift_above", coFloats); def->label = "Above Z"; def->full_label = "Only lift Z above"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-above=f@"; @@ -987,6 +1006,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_lift_below", coFloats); def->label = "Below Z"; def->full_label = "Only lift Z below"; + def->category = "Retraction"; def->tooltip = "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-below=f@"; @@ -998,6 +1018,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_restart_extra", coFloats); def->label = "Extra length on restart"; + def->category = "Retraction"; def->tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed."; def->sidetext = "mm"; def->cli = "retract-restart-extra=f@"; @@ -1020,7 +1041,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_speed", coFloats); def->label = "Speed"; - def->full_label = "Retraction Speed"; + def->category = "Retraction"; def->tooltip = "The speed for retractions (it only applies to the extruder motor). If you use the Firmware Retraction option, please note this value still affects the auto-speed pressure regulator."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; @@ -1479,6 +1500,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe", coBools); def->label = "Wipe while retracting"; + def->category = "Retraction"; def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders."; def->cli = "wipe!"; { diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 96c4830e0..622ed6b01 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -27,14 +27,19 @@ double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; + void apply_only(DynamicPrintConfig* other, std::vector opt_keys) + %code{% THIS->apply_only(*other, opt_keys, true); %}; std::vector diff(DynamicPrintConfig* other) %code{% RETVAL = THIS->diff(*other); %}; + std::vector diff_static(StaticPrintConfig* other) + %code{% RETVAL = THIS->diff(*other); %}; bool equals(DynamicPrintConfig* other) %code{% RETVAL = THIS->equals(*other); %}; void apply_static(StaticPrintConfig* other) %code{% THIS->apply(*other, true); %}; %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); + void clear(); void normalize(); %name{setenv} void setenv_(); double min_object_distance(); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 1bd25edb4..afeb993c1 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -256,6 +256,12 @@ _constant() void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; + bool apply_static_config(StaticPrintConfig* config) + %code%{ + DynamicPrintConfig dpc; + dpc.apply(*config); + RETVAL = THIS->apply_config(dpc); + %}; bool has_infinite_skirt(); bool has_skirt(); std::string _validate() From 4fe51125cfd460f1d443da48ce20416cab5adfd5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 Mar 2017 15:45:24 +0100 Subject: [PATCH 02/68] A couple minor fixes for the new GUI --- lib/Slic3r/GUI/Plater.pm | 2 +- lib/Slic3r/GUI/Preset.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b93e49038..5fb3b29f4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -656,7 +656,7 @@ sub load_presets { } my $selected = shift @sel; - if ($selected <= $#presets) { + if (defined $selected && $selected <= $#presets) { # call SetSelection() only after SetString() otherwise the new string # won't be picked up as the visible string $choice->SetSelection($selected); diff --git a/lib/Slic3r/GUI/Preset.pm b/lib/Slic3r/GUI/Preset.pm index 35208f585..d855c3c60 100644 --- a/lib/Slic3r/GUI/Preset.pm +++ b/lib/Slic3r/GUI/Preset.pm @@ -33,7 +33,7 @@ sub config { for grep $external_config->has($_), @$keys; # For extra_keys we don't populate defaults. - if ($extra_keys) { + if ($extra_keys && !$self->external) { $config->set($_, $external_config->get($_)) for grep $external_config->has($_), @$extra_keys; } From 9e6b869bd453df518f6403a3bf73587075cffa65 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 19:46:50 +0100 Subject: [PATCH 03/68] Enlarge the preset dialog to avoid horizontal scrollbars --- lib/Slic3r/GUI/PresetEditorDialog.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PresetEditorDialog.pm b/lib/Slic3r/GUI/PresetEditorDialog.pm index 349f1e871..d706622a3 100644 --- a/lib/Slic3r/GUI/PresetEditorDialog.pm +++ b/lib/Slic3r/GUI/PresetEditorDialog.pm @@ -10,7 +10,7 @@ __PACKAGE__->mk_accessors(qw(preset_editor)); sub new { my ($class, $parent) = @_; - my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [800,500], + my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [900,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxDIALOG_EX_METAL); $self->preset_editor($self->preset_editor_class->new($self)); From c2fecf7c70bcae9028126787e5b82e390f683966 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 20:29:36 +0100 Subject: [PATCH 04/68] Minor fix to prevent the Cancel button from showing when there are no objects --- lib/Slic3r/GUI/Plater.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 59fa49659..797e7c01f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1343,9 +1343,13 @@ sub start_background_process { my ($self) = @_; return if !$Slic3r::have_threads; - return if !@{$self->{objects}}; return if $self->{process_thread}; + if (!@{$self->{objects}}) { + $self->on_process_completed; + return; + } + # It looks like declaring a local $SIG{__WARN__} prevents the ugly # "Attempt to free unreferenced scalar" warning... local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); From f055b496eafb1330b3072819c93bc9cf03359228 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 20:33:37 +0100 Subject: [PATCH 05/68] Make overridable options easier to find --- lib/Slic3r/GUI/PresetEditor.pm | 8 ++++++-- var/wand.png | Bin 0 -> 570 bytes 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100755 var/wand.png diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index d5fae9311..6b7ae7717 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -733,7 +733,7 @@ sub build { } { - my $page = $self->add_options_page('Advanced', 'wrench.png'); + my $page = $self->add_options_page('Advanced', 'wand.png'); { my $optgroup = $page->new_optgroup('Extrusion width', label_width => 180, @@ -796,7 +796,7 @@ sub build { } { - my $page = $self->add_options_page('Notes and preferences', 'note.png'); + my $page = $self->add_options_page('Notes', 'note.png'); { my $optgroup = $page->new_optgroup('Notes', label_width => 0, @@ -806,6 +806,10 @@ sub build { $option->height(250); $optgroup->append_single_option_line($option); } + } + + { + my $page = $self->add_options_page('Overrides', 'wrench.png'); { my $optgroup = $page->new_optgroup('Profile preferences'); { diff --git a/var/wand.png b/var/wand.png new file mode 100755 index 0000000000000000000000000000000000000000..44ccbf812879c42cb1f9587d865bcfc337ce6361 GIT binary patch literal 570 zcmV-A0>%A_P)hUn2-Navsqo?5A8 zh}}V^=%7@_%C;=H{tGpIj5CMu*>5J=i;m^t2QTySd)}9aAppozC}++wDz`eOViU-dbRRoz=JsVlZk>N%^azhi%=xTCt9`LQjtqNFW~e|R=r9= z`@I3J^#z@aD5yBuq2DLQO#|4uFW6R5kzPZ+h&6Af&5}POarL&lA~3t5R1i7uh*ffDw@qEs=HBW ze?CI~MkvG6H-MF7r{Yv4kw_q&PNP^XqFgTHXlH@RpLO}3aV{T{Ez=8lo;PHV$Ads1 zfOtF(s5%5V>3qE|&{lueV1Y1j%GF zXt&#NI-LlGLPN>_18TJzN~Mxf*f6pY5Dteo^|P&3>(H{!KTr&_wGRQWb^rhX07*qo IM6N<$g7V1&P5=M^ literal 0 HcmV?d00001 From cea9ff53d57531b40923b4109c26e035a7581a97 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 20:41:39 +0100 Subject: [PATCH 06/68] Allow to add overrides in the plater on the fly --- lib/Slic3r/GUI/Plater.pm | 11 +++++++---- lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 797e7c01f..bbfc064e8 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -397,14 +397,15 @@ sub new { } { - $self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, + my $o = $self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->config_changed; }); - $self->{settings_override_panel}->set_editable(0); + $o->set_editable(1); + $o->set_opt_keys([ Slic3r::GUI::PresetEditor::Print->options ]); $self->{settings_override_config} = Slic3r::Config->new; - $self->{settings_override_panel}->set_default_config($self->{settings_override_config}); - $self->{settings_override_panel}->set_config($self->{settings_override_config}); + $o->set_default_config($self->{settings_override_config}); + $o->set_config($self->{settings_override_config}); } my $object_info_sizer; @@ -531,6 +532,8 @@ sub _on_select_preset { $self->{settings_override_config}->clear; my $overridable = $config->get('overridable'); if ($overridable) { + $self->{settings_override_panel}->set_default_config($config); + $self->{settings_override_panel}->set_fixed_options(\@$overridable); $self->{settings_override_config}->set($_, $config->get($_)) for @$overridable; } diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 5328cbff5..1a87ae384 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -35,6 +35,8 @@ sub new { # create the button 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("Override one more option") + if $btn->can('SetToolTipString'); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; foreach my $opt_key (@{$self->{options}}) { From c2d933382a1303701e29bf6b272d0d3193b6d782 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 21:12:44 +0100 Subject: [PATCH 07/68] Add nice icons to the dropdown override menu --- lib/Slic3r/GUI.pm | 9 ++++ lib/Slic3r/GUI/MainFrame.pm | 11 +---- lib/Slic3r/GUI/Plater.pm | 8 ++-- .../GUI/Plater/OverrideSettingsPanel.pm | 42 +++++++++++++++++-- xs/src/libslic3r/PrintConfig.cpp | 25 ++++++++++- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 7fe1e4133..88e8b072a 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -407,4 +407,13 @@ sub scan_serial_ports { return grep !/Bluetooth|FireFly/, @ports; } +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)); + } +} + 1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 3ce77a14d..9a6707b91 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -638,19 +638,10 @@ sub _append_menu_item { $id //= &Wx::NewId(); my $item = $menu->Append($id, $string, $description, $kind); - $self->_set_menu_item_icon($item, $icon); + wxTheApp->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)); - } -} - 1; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index bbfc064e8..21c98bf78 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2146,7 +2146,7 @@ sub object_menu { my $rotateMenu = Wx::Menu->new; my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle'); - $frame->_set_menu_item_icon($rotateMenuItem, 'textfield.png'); + wxTheApp->set_menu_item_icon($rotateMenuItem, 'textfield.png'); $frame->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub { $self->rotate(undef, X); }, undef, 'bullet_red.png'); @@ -2159,7 +2159,7 @@ sub object_menu { my $mirrorMenu = Wx::Menu->new; my $mirrorMenuItem = $menu->AppendSubMenu($mirrorMenu, "Mirror", 'Mirror the selected object'); - $frame->_set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png'); + wxTheApp->set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png'); $frame->_append_menu_item($mirrorMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { $self->mirror(X); }, undef, 'bullet_red.png'); @@ -2172,7 +2172,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'); + wxTheApp->set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); $frame->_append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef); }); @@ -2188,7 +2188,7 @@ sub object_menu { my $scaleToSizeMenu = Wx::Menu->new; my $scaleToSizeMenuItem = $menu->AppendSubMenu($scaleToSizeMenu, "Scale to size", 'Scale the selected object along a single axis'); - $frame->_set_menu_item_icon($scaleToSizeMenuItem, 'arrow_out.png'); + wxTheApp->set_menu_item_icon($scaleToSizeMenuItem, 'arrow_out.png'); $frame->_append_menu_item($scaleToSizeMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef, 1); }); diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 1a87ae384..9549807a8 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -15,6 +15,18 @@ use constant ICON_MATERIAL => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; +my %icons = ( + 'Advanced' => 'wand.png', + 'Extruders' => 'funnel.png', + 'Extrusion Width' => 'funnel.png', + 'Infill' => 'infill.png', + 'Layers and Perimeters' => 'layers.png', + 'Skirt and brim' => 'box.png', + 'Speed' => 'time.png', + 'Speed > Acceleration' => 'time.png', + 'Support material' => 'building.png', +); + sub new { my $class = shift; my ($parent, %params) = @_; @@ -39,9 +51,23 @@ sub new { if $btn->can('SetToolTipString'); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; + my $last_cat = ''; foreach my $opt_key (@{$self->{options}}) { my $id = &Wx::NewId(); - $menu->Append($id, $self->{option_labels}{$opt_key}); + + # add icon, if we have one for this category + my $icon; + if (my $cat = $Slic3r::Config::Options->{$opt_key}{category}) { + if ($last_cat && $cat ne $last_cat) { + $menu->AppendSeparator; + } + $last_cat = $cat; + $icon = $icons{$cat}; + } + + my $menuItem = $menu->Append($id, $self->{option_labels}{$opt_key}); + wxTheApp->set_menu_item_icon($menuItem, $icon) if $icon; + EVT_MENU($menu, $id, sub { $self->{config}->set($opt_key, $self->{default_config}->get($opt_key)); $self->update_optgroup; @@ -84,10 +110,18 @@ sub set_opt_keys { my ($self, $opt_keys) = @_; # sort options by category+label - $self->{option_labels} = { - map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } @$opt_keys + $self->{option_labels} = {}; + foreach my $opt_key (@$opt_keys) { + my $def = $Slic3r::Config::Options->{$opt_key} or next; + if (!$def->{category}) { + #printf "Skipping %s\n", $opt_key; + next; + } + $self->{option_labels}{$opt_key} = sprintf '%s > %s', + $def->{category}, + $def->{full_label} // $def->{label}; }; - $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; + $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } keys %{$self->{option_labels}} ]; } # Sets the options that user can't remove. diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index b1f4e9474..25c2ee7a3 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -25,6 +25,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("avoid_crossing_perimeters", coBool); def->label = "Avoid crossing perimeters"; + def->category = "Layers and Perimeters"; def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation."; def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); @@ -82,6 +83,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("bridge_acceleration", coFloat); def->label = "Bridge"; + def->category = "Speed > Acceleration"; def->tooltip = "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges."; def->sidetext = "mm/s²"; def->cli = "bridge-acceleration=f"; @@ -120,6 +122,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("brim_connections_width", coFloat); def->label = "Brim connections width"; + def->category = "Skirt and brim"; def->tooltip = "If set to a positive value, straight connections will be built on the first layer between adjacent objects."; def->sidetext = "mm"; def->cli = "brim-connections-width=f"; @@ -128,6 +131,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("brim_width", coFloat); def->label = "Exterior brim width"; + def->category = "Skirt and brim"; def->tooltip = "Horizontal width of the brim that will be printed around each object on the first layer."; def->sidetext = "mm"; def->cli = "brim-width=f"; @@ -152,6 +156,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("default_acceleration", coFloat); def->label = "Default"; + def->category = "Speed > Acceleration"; def->tooltip = "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all."; def->sidetext = "mm/s²"; def->cli = "default-acceleration=f"; @@ -505,6 +510,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_acceleration", coFloat); def->label = "First layer"; + def->category = "Speed > Acceleration"; def->tooltip = "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer."; def->sidetext = "mm/s²"; def->cli = "first-layer-acceleration=f"; @@ -543,6 +549,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_speed", coFloatOrPercent); def->label = "First layer speed"; + def->category = "Speed"; def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds."; def->sidetext = "mm/s or %"; def->cli = "first-layer-speed=s"; @@ -614,6 +621,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_acceleration", coFloat); def->label = "Infill"; + def->category = "Speed > Acceleration"; def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill."; def->sidetext = "mm/s²"; def->cli = "infill-acceleration=f"; @@ -652,6 +660,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_first", coBool); def->label = "Infill before perimeters"; + def->category = "Infill"; def->tooltip = "This option will switch the print order of perimeters and infill, making the latter first."; def->cli = "infill-first!"; def->default_value = new ConfigOptionBool(false); @@ -688,6 +697,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("interior_brim_width", coFloat); def->label = "Interior brim width"; + def->category = "Skirt and brim"; def->tooltip = "Horizontal width of the brim that will be printed inside object holes on the first layer."; def->sidetext = "mm"; def->cli = "interior-brim-width=f"; @@ -730,6 +740,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_print_speed", coFloat); def->label = "Max print speed"; + def->category = "Speed"; def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow."; def->sidetext = "mm/s"; def->cli = "max-print-speed=f"; @@ -738,6 +749,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_speed", coFloat); def->label = "Max volumetric speed"; + def->category = "Speed"; def->tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports."; def->sidetext = "mm³/s"; def->cli = "max-volumetric-speed=f"; @@ -763,6 +775,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("min_skirt_length", coFloat); def->label = "Minimum extrusion length"; + def->category = "Skirt and brim"; def->tooltip = "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder."; def->sidetext = "mm"; def->cli = "min-skirt-length=f"; @@ -803,12 +816,14 @@ PrintConfigDef::PrintConfigDef() def = this->add("only_retract_when_crossing_perimeters", coBool); def->label = "Only retract when crossing perimeters"; + def->category = "Layers and Perimeters"; def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)."; def->cli = "only-retract-when-crossing-perimeters!"; def->default_value = new ConfigOptionBool(true); def = this->add("ooze_prevention", coBool); def->label = "Enable"; + def->category = "Extruders"; def->tooltip = "During multi-extruder prints, this option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures."; def->cli = "ooze-prevention!"; def->default_value = new ConfigOptionBool(false); @@ -837,6 +852,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; + def->category = "Speed > Acceleration"; def->tooltip = "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters."; def->sidetext = "mm/s²"; def->cli = "perimeter-acceleration=f"; @@ -1059,7 +1075,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("seam_position", coEnum); def->label = "Seam position"; - def->category = "Layers and perimeters"; + def->category = "Layers and Perimeters"; def->tooltip = "Position of perimeters starting points."; def->cli = "seam-position=s"; def->enum_keys_map = ConfigOptionEnum::get_enum_values(); @@ -1096,6 +1112,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_distance", coFloat); def->label = "Distance from object"; + def->category = "Skirt and brim"; def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; def->sidetext = "mm"; def->cli = "skirt-distance=f"; @@ -1104,6 +1121,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_height", coInt); def->label = "Skirt height"; + def->category = "Skirt and brim"; def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts."; def->sidetext = "layers"; def->cli = "skirt-height=i"; @@ -1111,6 +1129,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirts", coInt); def->label = "Loops (minimum)"; + def->category = "Skirt and brim"; def->full_label = "Skirt Loops"; def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; def->cli = "skirts=i"; @@ -1205,12 +1224,14 @@ PrintConfigDef::PrintConfigDef() def = this->add("spiral_vase", coBool); def->label = "Spiral vase"; + def->category = "Layers and Perimeters"; def->tooltip = "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object."; def->cli = "spiral-vase!"; def->default_value = new ConfigOptionBool(false); def = this->add("standby_temperature_delta", coInt); def->label = "Temperature variation"; + def->category = "Extruders"; def->tooltip = "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; def->sidetext = "∆°C"; def->cli = "standby-temperature-delta=i"; @@ -1329,6 +1350,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_interface_speed", coFloatOrPercent); def->label = "↳ interface"; + def->label = "Interface Speed"; def->category = "Support material interface speed"; def->gui_type = "f_enum_open"; def->category = "Support material"; @@ -1476,6 +1498,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("travel_speed", coFloat); def->label = "Travel"; + def->category = "Speed"; def->tooltip = "Speed for travel moves (jumps between distant extrusion points)."; def->sidetext = "mm/s"; def->cli = "travel-speed=f"; From 745911e0c8d2dd851a95cfc33dcd14bf36c0dad8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 21:28:05 +0100 Subject: [PATCH 08/68] Minor label fix --- xs/src/libslic3r/PrintConfig.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 25c2ee7a3..08f441ffd 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -823,6 +823,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("ooze_prevention", coBool); def->label = "Enable"; + def->full_label = "Ooze Prevention"; def->category = "Extruders"; def->tooltip = "During multi-extruder prints, this option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures."; def->cli = "ooze-prevention!"; @@ -1231,6 +1232,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("standby_temperature_delta", coInt); def->label = "Temperature variation"; + def->full_label = "Standby temperature delta"; def->category = "Extruders"; def->tooltip = "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; def->sidetext = "∆°C"; From 9a9597df83b854df661d61a1c424796c5a00cdcd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 21 Mar 2017 21:43:25 +0100 Subject: [PATCH 09/68] Removed duplicate filament_notes left after merge --- lib/Slic3r/GUI/PresetEditor.pm | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 6b7ae7717..63bc52981 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -1165,19 +1165,6 @@ sub build { $optgroup->append_line($line); } } - - { - my $page = $self->add_options_page('Notes', 'note.png'); - { - my $optgroup = $page->new_optgroup('Notes', - label_width => 0, - ); - my $option = $optgroup->get_option('filament_notes', 0); - $option->full_width(1); - $option->height(250); - $optgroup->append_single_option_line($option); - } - } } sub reload_config { From 4ca5f9ee16afd82146031e69de929445f5c1b51c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 22 Mar 2017 19:47:23 +0100 Subject: [PATCH 10/68] Allow dirty presets, and many more improvements including remembering the preset dialog position and closing it with the Esc key --- lib/Slic3r/GUI.pm | 108 ++++-- lib/Slic3r/GUI/Controller.pm | 4 +- lib/Slic3r/GUI/MainFrame.pm | 46 +-- lib/Slic3r/GUI/Plater.pm | 124 ++++--- .../GUI/Plater/OverrideSettingsPanel.pm | 9 +- lib/Slic3r/GUI/Preset.pm | 210 ++++++++++-- lib/Slic3r/GUI/PresetEditor.pm | 324 ++++++------------ lib/Slic3r/GUI/PresetEditorDialog.pm | 24 +- xs/src/libslic3r/Config.cpp | 5 + xs/src/libslic3r/Config.hpp | 1 + xs/xsp/Config.xsp | 1 + 11 files changed, 497 insertions(+), 359 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 88e8b072a..eb5a7bd01 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -5,7 +5,9 @@ use utf8; use File::Basename qw(basename); use FindBin; -use List::Util qw(first); +use List::Util qw(first any); +use Slic3r::Geometry qw(X Y); + use Slic3r::GUI::2DBed; use Slic3r::GUI::AboutDialog; use Slic3r::GUI::BedShapeDialog; @@ -98,7 +100,7 @@ sub OnInit { Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; $self->{notifier} = Slic3r::GUI::Notifier->new; - $self->{external_presets} = []; + $self->{presets} = { print => [], filament => [], printer => [] }; # locate or create data directory # Unix: ~/.Slic3r @@ -142,6 +144,8 @@ sub OnInit { unlink "$enc_datadir/simple.ini"; } + $self->load_presets; + # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( @@ -283,47 +287,64 @@ sub save_settings { Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings); } -sub presets { - my ($self, $section, $force_default) = @_; +sub presets { return $_[0]->{presets}; } + +sub load_presets { + my ($self) = @_; - my @presets = (); + for my $group (qw(printer filament print)) { + my $presets = $self->{presets}{$group}; + + # keep external or dirty presets + @$presets = grep { ($_->external && $_->file_exists) || $_->dirty } @$presets; + + my $dir = "$Slic3r::GUI::datadir/$group"; + opendir my $dh, Slic3r::encode_path($dir) + or die "Failed to read directory $dir (errno: $!)\n"; + foreach my $file (grep /\.ini$/i, readdir $dh) { + $file = Slic3r::decode_path($file); + my $name = basename($file); + $name =~ s/\.ini$//i; + + # skip if we already have it + next if any { $_->name eq $name } @$presets; + + push @$presets, Slic3r::GUI::Preset->new( + group => $group, + name => $name, + file => "$dir/$file", + ); + } + closedir $dh; - opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") - or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; - foreach my $file (grep /\.ini$/i, readdir $dh) { - $file = Slic3r::decode_path($file); - my $name = basename($file); - $name =~ s/\.ini$//i; - push @presets, Slic3r::GUI::Preset->new( - name => $name, - file => "$Slic3r::GUI::datadir/$section/$file", - ); - } - closedir $dh; + @$presets = sort { $a->name cmp $b->name } @$presets; - @presets = sort { $a->name cmp $b->name } - @presets, - (grep -e $_->file, @{$self->{external_presets}}); - - if ($force_default || !@presets) { - unshift @presets, Slic3r::GUI::Preset->new( + unshift @$presets, Slic3r::GUI::Preset->new( + group => $group, default => 1, name => '- default -', ); } - - return @presets; } sub add_external_preset { my ($self, $file) = @_; - push @{$self->{external_presets}}, my $preset = Slic3r::GUI::Preset->new( - name => basename($file), # keep .ini suffix - file => $file, - external => 1, - ); - return $preset; + my $name = basename($file); # keep .ini suffix + for my $group (qw(printer filament print)) { + my $presets = $self->{presets}{$group}; + + # remove any existing preset with the same name + @$presets = grep { $_->name ne $name } @$presets; + + push @$presets, Slic3r::GUI::Preset->new( + group => $group, + name => $name, + file => $file, + external => 1, + ); + } + return $name; } sub have_version_check { @@ -416,4 +437,29 @@ sub set_menu_item_icon { } } +sub save_window_pos { + my ($self, $window, $name) = @_; + + $Settings->{_}{"${name}_pos"} = join ',', $window->GetScreenPositionXY; + $Settings->{_}{"${name}_size"} = join ',', $window->GetSizeWH; + $Settings->{_}{"${name}_maximized"} = $window->IsMaximized; + $self->save_settings; +} + +sub restore_window_pos { + my ($self, $window, $name) = @_; + + if (defined $Settings->{_}{"${name}_pos"}) { + my $size = [ split ',', $Settings->{_}{"${name}_size"}, 2 ]; + $window->SetSize($size); + + my $display = Wx::Display->new->GetClientArea(); + my $pos = [ split ',', $Settings->{_}{"${name}_pos"}, 2 ]; + if (($pos->[X] + $size->[X]/2) < $display->GetRight && ($pos->[Y] + $size->[Y]/2) < $display->GetBottom) { + $window->Move($pos); + } + $window->Maximize(1) if $Settings->{_}{"${name}_maximized"}; + } +} + 1; diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 7ebf3491d..87b85fde2 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -47,7 +47,7 @@ sub new { delete $presets{$_} for map $_->printer_name, @panels; foreach my $preset_name (sort keys %presets) { - my $config = $presets{$preset_name}->load_config; + my $config = $presets{$preset_name}->dirty_config; next if !$config->serial_port; my $id = &Wx::NewId(); @@ -101,7 +101,7 @@ sub OnActivate { # get all available presets my %presets = (); { - my %all = map { $_->name => $_ } wxTheApp->presets('printer'); + my %all = map { $_->name => $_ } @{wxTheApp->presets->{printer}}; my %configs = map { my $name = $_; $name => $all{$name}->load_config } keys %all; %presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all; } diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 9a6707b91..7b21d64c3 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -56,19 +56,8 @@ sub new { $self->SetSizer($sizer); $self->Fit; $self->SetMinSize([760, 490]); - if (defined $Slic3r::GUI::Settings->{_}{main_frame_size}) { - my $size = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ]; - $self->SetSize($size); - - my $display = Wx::Display->new->GetClientArea(); - my $pos = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ]; - if (($pos->[X] + $size->[X]/2) < $display->GetRight && ($pos->[Y] + $size->[Y]/2) < $display->GetBottom) { - $self->Move($pos); - } - $self->Maximize(1) if $Slic3r::GUI::Settings->{_}{main_frame_maximized}; - } else { - $self->SetSize($self->GetMinSize); - } + $self->SetSize($self->GetMinSize); + wxTheApp->restore_window_pos($self, "main_frame"); $self->Show; $self->Layout; } @@ -78,23 +67,23 @@ sub new { my (undef, $event) = @_; if ($event->CanVeto) { - my $veto = 0; + if (!$self->{plater}->prompt_unsaved_changes) { + $event->Veto; + return; + } + if ($self->{controller} && $self->{controller}->printing) { my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?", 'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - $veto = 1 if $confirm->ShowModal == wxID_YES; - } - if ($veto) { - $event->Veto; - return; + if ($confirm->ShowModal == wxID_NO) { + $event->Veto; + return; + } } } # save window size - $Slic3r::GUI::Settings->{_}{main_frame_pos} = join ',', $self->GetScreenPositionXY; - $Slic3r::GUI::Settings->{_}{main_frame_size} = join ',', $self->GetSizeWH; - $Slic3r::GUI::Settings->{_}{main_frame_maximized} = $self->IsMaximized; - wxTheApp->save_settings; + wxTheApp->save_window_pos($self, "main_frame"); # propagate event $event->Skip; @@ -507,9 +496,9 @@ sub load_config_file { wxTheApp->save_settings; $last_config = $file; - my $preset = wxTheApp->add_external_preset($file); + my $name = wxTheApp->add_external_preset($file); $self->{plater}->load_presets; - $self->{plater}->select_preset_by_name($preset->name, $_) for qw(print filament printer); + $self->{plater}->select_preset_by_name($name, $_) for qw(print filament printer); } sub export_configbundle { @@ -536,8 +525,9 @@ sub export_configbundle { $ini->{presets} = $Slic3r::GUI::Settings->{presets}; foreach my $section (qw(print filament printer)) { - my @presets = wxTheApp->presets($section); + my @presets = @{wxTheApp->presets->{$section}}; foreach my $preset (@presets) { + next if $preset->default || $preset->external; $ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_}; } } @@ -581,10 +571,10 @@ sub load_configbundle { next if $skip_no_id && !$config->get($section . "_settings_id"); { - my @current_presets = Slic3r::GUI->presets($section); + my @current_presets = @{wxTheApp->presets->{$section}}; my %current_ids = map { $_ => 1 } grep $_, - map $_->load_config->get($section . "_settings_id"), + map $_->dirty_config->get($section . "_settings_id"), @current_presets; next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 21c98bf78..f586c7b43 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -368,6 +368,7 @@ sub new { printer => 'Printer', ); $self->{preset_choosers} = {}; + $self->{preset_choosers_names} = {}; # wxChoice* => [] for my $group (qw(print filament printer)) { # label my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); @@ -380,7 +381,7 @@ sub new { EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset($group); + $self->_on_change_combobox($group, $choice); }); }); @@ -512,11 +513,52 @@ sub new { return $self; } +sub prompt_unsaved_changes { + my ($self) = @_; + + foreach my $group (qw(printer filament print)) { + foreach my $choice (@{$self->{preset_choosers}{$group}}) { + my $pp = $self->{preset_choosers_names}{$choice}; + for my $i (0..$#$pp) { + my $preset = first { $_->name eq $pp->[$i] } @{wxTheApp->presets->{$group}}; + if (!$preset->prompt_unsaved_changes($self)) { + # Restore the previous one + $choice->SetSelection($i); + return 0; + } + } + } + } + return 1; +} + +sub _on_change_combobox { + my ($self, $group, $choice) = @_; + + if (0) { + # This code is disabled because wxPerl doesn't provide GetCurrentSelection + my $current_name = $self->{preset_choosers_names}{$choice}[$choice->GetCurrentSelection]; + my $current = first { $_->name eq $current_name } @{wxTheApp->presets->{$group}}; + if (!$current->prompt_unsaved_changes($self)) { + # Restore the previous one + $choice->SetSelection($choice->GetCurrentSelection); + return; + } + } else { + return 0 if !$self->prompt_unsaved_changes; + } + wxTheApp->CallAfter(sub { + $self->_on_select_preset($group); + + # This will remove the "(modified)" mark from any dirty preset handled here. + $self->load_presets; + }); +} + sub _on_select_preset { my ($self, $group) = @_; - my @presets = map $self->presets->{$group}[scalar $_->GetSelection], - @{$self->{preset_choosers}{$group}}; + my @presets = $self->selected_presets($group); $Slic3r::GUI::Settings->{presets}{$group} = $presets[0]->name; $Slic3r::GUI::Settings->{presets}{"${group}_${_}"} = $presets[$_]->name @@ -565,17 +607,16 @@ sub GetFrame { sub load_presets { my ($self) = @_; - $self->presets({}); - my $selected_printer_name; foreach my $group (qw(printer filament print)) { - my @presets = wxTheApp->presets($group); + my @presets = @{wxTheApp->presets->{$group}}; + + # Skip presets not compatible with the selected printer, if they + # have other compatible printers configured (and at least one of them exists). if ($group eq 'filament' || $group eq 'print') { - my %printer_names = map { $_->name => 1 } @{ $self->presets->{printer} }; - # Skip presets not compatible with the selected printer, if they - # have other compatible printers configured (and at least one of them exists). + my %printer_names = map { $_->name => 1 } @{ wxTheApp->presets->{printer} }; for (my $i = 0; $i <= $#presets; ++$i) { - my $config = $presets[$i]->config; + my $config = $presets[$i]->dirty_config; next if !$config->has('compatible_printers'); my @compat = @{$config->compatible_printers}; if (@compat @@ -586,14 +627,11 @@ sub load_presets { } } } - if (!@presets) { - unshift @presets, Slic3r::GUI::Preset->new( - default => 1, - name => '- default -', - ); - } - $self->presets->{$group} = [@presets]; + # Only show the default presets if we have no other presets. + if (@presets > 1) { + @presets = grep { !$_->default } @presets; + } # get the wxChoice objects for this group my @choosers = @{ $self->{preset_choosers}{$group} }; @@ -613,11 +651,12 @@ sub load_presets { # populate the wxChoice objects foreach my $choice (@choosers) { $choice->Clear; + $self->{preset_choosers_names}{$choice} = []; foreach my $preset (@presets) { # load/generate the proper icon my $bitmap; if ($group eq 'filament') { - my $config = $preset->config; + my $config = $preset->dirty_config; if ($preset->default || !$config->has('filament_colour')) { $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); } else { @@ -634,7 +673,8 @@ sub load_presets { } elsif ($group eq 'printer') { $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); } - $choice->AppendString($preset->name, $bitmap); + $choice->AppendString($preset->dropdown_name, $bitmap); + push @{$self->{preset_choosers_names}{$choice}}, $preset->name; } my $selected = shift @sel; @@ -643,14 +683,13 @@ sub load_presets { # won't be picked up as the visible string $choice->SetSelection($selected); - my $preset_name = $choice->GetString($selected); + my $preset_name = $self->{preset_choosers_names}{$choice}[$selected]; $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); + # TODO: populate other filament preset placeholders $selected_printer_name = $preset_name if $group eq 'printer'; } } } - - #$self->_on_select_preset($_) for qw(printer filament print); } sub select_preset_by_name { @@ -658,11 +697,12 @@ sub select_preset_by_name { # $n is optional - my $presets = $self->presets->{$group}; - my $i = first { $presets->[$_]->name eq $name } 0..$#$presets; + my $presets = wxTheApp->presets->{$group}; + my $choosers = $self->{preset_choosers}{$group}; + my $names = $self->{preset_choosers_names}{$choosers->[0]}; + my $i = first { $names->[$_] eq $name } 0..$#$names; return if !defined $i; - my $choosers = $self->{preset_choosers}{$group}; if (defined $n && $n <= $#$choosers) { $choosers->[$n]->SetSelection($i); } else { @@ -674,10 +714,16 @@ sub select_preset_by_name { sub selected_presets { my ($self, $group) = @_; - my %presets; + my %presets = (); foreach my $group (qw(printer filament print)) { - my @i = map scalar($_->GetSelection), @{ $self->{preset_choosers}{$group} }; - $presets{$group} = [ @{$self->presets->{$group}}[@i] ]; + $presets{$group} = []; + foreach my $choice (@{$self->{preset_choosers}{$group}}) { + my $sel = $choice->GetSelection; + $sel = 0 if $sel == -1; + push @{ $presets{$group} }, + grep { $_->name eq $self->{preset_choosers_names}{$choice}[$sel] } + @{wxTheApp->presets->{$group}}; + } } return $group ? @{$presets{$group}} : %presets; } @@ -697,7 +743,7 @@ sub show_preset_editor { # Select the preset that was last selected in the editor. $self->select_preset_by_name - ($dlg->preset_editor->get_current_preset->name, $group, $i, 1); + ($dlg->preset_editor->current_preset->name, $group, $i, 1); } # Returns the current config by merging the selected presets and the overrides. @@ -715,16 +761,13 @@ sub config { qw(print filament printer); my %presets = $self->selected_presets; - $config->apply($_->config([ $classes{printer}->options ])) - for @{ $presets{printer} }; + $config->apply($_->dirty_config) for @{ $presets{printer} }; if (@{ $presets{filament} }) { - my @opt_keys = ($classes{filament}->options, $classes{filament}->overriding_options); - my $filament_config = shift(@{ $presets{filament} }) - ->config(\@opt_keys); + my $filament_config = $presets{filament}[0]->dirty_config; - my $i = 1; - for my $preset (@{ $presets{filament} }) { - my $config = $preset->config(\@opt_keys); + for my $i (1..$#{ $presets{filament} }) { + my $preset = $presets{filament}[$i]; + my $config = $preset->dirty_config; foreach my $opt_key (@{$config->get_keys}) { if ($filament_config->has($opt_key)) { my $value = $filament_config->get($opt_key); @@ -733,14 +776,11 @@ sub config { $filament_config->set($opt_key, $value); } } - ++$i; } $config->apply($filament_config); } - $config->apply($_->config([ $classes{print}->options ])) - for @{ $presets{print} }; - + $config->apply($_->dirty_config) for @{ $presets{print} }; $config->apply($self->{settings_override_config}); return $config; @@ -1896,7 +1936,7 @@ sub on_extruders_change { EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament'); + $self->_on_change_combobox('filament', $choice); }); }); diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 9549807a8..781e0fb6d 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -71,7 +71,7 @@ sub new { EVT_MENU($menu, $id, sub { $self->{config}->set($opt_key, $self->{default_config}->get($opt_key)); $self->update_optgroup; - $self->{on_change}->() if $self->{on_change}; + $self->{on_change}->($opt_key) if $self->{on_change}; }); } $self->PopupMenu($menu, $btn->GetPosition); @@ -155,7 +155,10 @@ sub update_optgroup { label_font => $Slic3r::GUI::small_font, sidetext_font => $Slic3r::GUI::small_font, label_width => 120, - on_change => sub { $self->{on_change}->() if $self->{on_change} }, + on_change => sub { + my ($opt_key) = @_; + $self->{on_change}->($opt_key) if $self->{on_change}; + }, extra_column => sub { my ($line) = @_; @@ -169,7 +172,7 @@ sub update_optgroup { wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_BUTTON($self, $btn, sub { $self->{config}->erase($opt_key); - $self->{on_change}->() if $self->{on_change}; + $self->{on_change}->($opt_key) if $self->{on_change}; wxTheApp->CallAfter(sub { $self->update_optgroup }); }); return $btn; diff --git a/lib/Slic3r/GUI/Preset.pm b/lib/Slic3r/GUI/Preset.pm index d855c3c60..5bdd0f20e 100644 --- a/lib/Slic3r/GUI/Preset.pm +++ b/lib/Slic3r/GUI/Preset.pm @@ -1,11 +1,16 @@ package Slic3r::GUI::Preset; use Moo; -use Unicode::Normalize; +use Unicode::Normalize; +use Wx qw(:dialog :icon :id wxTheApp); + +has 'group' => (is => 'ro', required => 1); has 'default' => (is => 'ro', default => sub { 0 }); has 'external' => (is => 'ro', default => sub { 0 }); has 'name' => (is => 'rw', required => 1); has 'file' => (is => 'rw'); +has '_config' => (is => 'rw', default => sub { Slic3r::Config->new }); +has '_dirty_config' => (is => 'ro', default => sub { Slic3r::Config->new }); sub BUILD { my ($self) = @_; @@ -13,40 +18,193 @@ sub BUILD { $self->name(Unicode::Normalize::NFC($self->name)); } -sub config { - my ($self, $keys, $extra_keys) = @_; +sub _loaded { + my ($self) = @_; - if ($self->default) { - return Slic3r::Config->new_from_defaults(@$keys); - } else { - if (!-e Slic3r::encode_path($self->file)) { - Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); - return undef; - } - my $external_config = $self->load_config; - if (!$keys) { - return $external_config; - } else { - # apply preset values on top of defaults - my $config = Slic3r::Config->new_from_defaults(@$keys); - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$keys; - - # For extra_keys we don't populate defaults. - if ($extra_keys && !$self->external) { - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$extra_keys; - } + return !$self->_config->empty; +} + +sub dirty_options { + my ($self) = @_; + + my @dirty = (); + + # Options present in both configs with different values: + push @dirty, @{$self->_config->diff($self->_dirty_config)}; + + # Overrides added to the dirty config: + my @extra = $self->_group_class->overriding_options; + push @dirty, grep { !$self->_config->has($_) && $self->_dirty_config->has($_) } @extra; + # Overrides removed from the dirty config: + push @dirty, grep { $self->_config->has($_) && !$self->_dirty_config->has($_) } @extra; + + return @dirty; +} + +sub dirty { + my ($self) = @_; + + return !!$self->dirty_options; +} + +sub dropdown_name { + my ($self) = @_; + + my $name = $self->name; + $name .= " (modified)" if $self->dirty; + return $name; +} + +sub file_exists { + my ($self) = @_; + + die "Can't call file_exists() on a non-file preset" if !$self->file; + return -e Slic3r::encode_path($self->file); +} + +sub rename { + my ($self, $name) = @_; + + $self->name($name); + $self->file(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->group, $name); +} + +sub prompt_unsaved_changes { + my ($self, $parent) = @_; + + if ($self->dirty) { + my $name = $self->default ? 'Default preset' : "Preset \"" . $self->name . "\""; - return $config; + my $opts = ''; + 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"; + } + $opts .= "- $name\n"; + } + + my $msg = sprintf "%s has unsaved changes:\n%s\nDo you want to save them?", $name, $opts; + my $confirm = Wx::MessageDialog->new($parent, $msg, + 'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION); + $confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel'); + my $res = $confirm->ShowModal; + + if ($res == wxID_CANCEL) { + return 0; + } elsif ($res == wxID_YES) { + return $self->save($self->default ? undef : $self->name); + } elsif ($res == wxID_NO) { + $self->dismiss_changes; + return 1; } } + + return 1; +} + +sub save { + my ($self, $name, $parent) = @_; + + if (!$name) { + my $default_name = $self->default ? 'Untitled' : $self->name; + $default_name =~ s/\.ini$//i; + + my $dlg = Slic3r::GUI::SavePresetWindow->new($parent, + default => $default_name, + values => [ map $_->name, grep !$_->default && !$_->external, @{wxTheApp->presets->{$self->name}} ], + ); + return 0 unless $dlg->ShowModal == wxID_OK; + $name = $dlg->get_name; + } + + $self->rename($name); + + if (!$self->file) { + die "Calling save() without setting filename"; + } + + $self->_config->clear; + $self->_config->apply($self->_dirty_config); + $self->_config->save($self->file); + wxTheApp->load_presets; + + return 1; +} + +sub dismiss_changes { + my ($self) = @_; + + $self->_dirty_config->clear; + $self->_dirty_config->apply($self->_config); +} + +sub delete { + my ($self) = @_; + + die "Default config can't be deleted" if $self->default; + die "External configs can't be deleted" if $self->external; + + # Otherwise wxTheApp->load_presets() will keep it + $self->dismiss_changes; + + if ($self->file) { + unlink Slic3r::encode_path($self->file) if $self->file_exists; + $self->file(undef); + } +} + +# This returns the loaded config with the dirty options applied. +sub dirty_config { + my ($self) = @_; + + $self->load_config if !$self->_loaded; + + return $self->_dirty_config->clone; } sub load_config { my ($self) = @_; - return Slic3r::Config->load($self->file); + my @keys = $self->_group_class->options; + my @extra_keys = $self->_group_class->overriding_options; + + if ($self->default) { + $self->_config(Slic3r::Config->new_from_defaults(@keys)); + } elsif ($self->file) { + if (!$self->file_exists) { + Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); + return undef; + } + my $external_config = Slic3r::Config->load($self->file); + if (!@keys) { + $self->_config($external_config); + } else { + # apply preset values on top of defaults + my $config = Slic3r::Config->new_from_defaults(@keys); + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @keys; + + # For extra_keys we don't populate defaults. + if (@extra_keys && !$self->external) { + $config->set($_, $external_config->get($_)) + for grep $external_config->has($_), @extra_keys; + } + + $self->_config($config); + } + } + + $self->_dirty_config->apply($self->_config); + + return $self->_config; +} + +sub _group_class { + my ($self) = @_; + + return "Slic3r::GUI::PresetEditor::".ucfirst $self->group; } 1; diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 63bc52981..758810490 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -10,13 +10,15 @@ use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :wi use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED); use base qw(Wx::Panel Class::Accessor); -__PACKAGE__->mk_accessors(qw(current_preset)); +__PACKAGE__->mk_accessors(qw(current_preset config)); sub new { my $class = shift; my ($parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + $self->{presets} = wxTheApp->presets->{$self->name}; + # horizontal sizer $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); #$self->{sizer}->SetSizeHints($self); @@ -91,78 +93,39 @@ sub new { EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); EVT_BUTTON($self, $self->{btn_delete_preset}, sub { - my $i = $self->current_preset; - return if $i == 0; # this shouldn't happen but let's trap it anyway 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; - if (-e $self->{presets}[$i]->file) { - unlink $self->{presets}[$i]->file; - } - splice @{$self->{presets}}, $i, 1; - $self->{presets_choice}->Delete($i); + + $self->current_preset->delete; $self->current_preset(undef); - $self->select_preset(0); + wxTheApp->load_presets; + $self->load_presets; + $self->select_preset(0, 1); }); - # C++ instance DynamicPrintConfig - $self->{config} = Slic3r::Config->new; - # Initialize the DynamicPrintConfig by default keys/values. - $self->{config}->apply(Slic3r::Config->new_from_defaults($self->options)); + $self->config(Slic3r::Config->new_from_defaults($self->options)); - - - # Possible %params keys: no_controller - $self->build(%params); + $self->build; $self->update_tree; + $self->load_presets; $self->_update; - if ($self->hidden_options) { - $self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options)); - } return $self; } -sub get_current_preset { - my $self = shift; - return $self->get_preset($self->current_preset); -} - -sub get_current_preset_config { - my ($self) = @_; - - return $self->get_preset_config($self->get_current_preset); -} - -sub get_preset { - my ($self, $i) = @_; - return $self->{presets}[$i]; -} - +# This is called by the save button. sub save_preset { - my ($self, $name) = @_; + my ($self) = @_; # since buttons (and choices too) don't get focus on Mac, we set focus manually # to the treectrl so that the EVT_* events are fired for the input field having # focus currently. is there anything better than this? $self->{treectrl}->SetFocus; - if (!defined $name) { - my $preset = $self->get_current_preset; - my $default_name = $preset->default ? 'Untitled' : $preset->name; - $default_name =~ s/\.ini$//i; - - my $dlg = Slic3r::GUI::SavePresetWindow->new($self, - title => lc($self->title), - default => $default_name, - values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], - ); - return 0 unless $dlg->ShowModal == wxID_OK; - $name = $dlg->get_name; - } - - $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); + my $preset = $self->current_preset; + $preset->save(undef, $self); $self->load_presets; - $self->select_preset_by_name($name); + $self->select_preset_by_name($preset->name); return 1; } @@ -176,17 +139,17 @@ sub on_value_change { # or changed by user (so also when a preset is loaded). # propagate event to the parent sub _on_value_change { - my $self = shift; + my ($self, $opt_key) = @_; - $self->{on_value_change}->(@_) if $self->{on_value_change}; + $self->current_preset->_dirty_config->apply($self->config); + $self->{on_value_change}->($opt_key) if $self->{on_value_change}; + $self->load_presets; $self->_update; } sub _update {} sub on_preset_loaded {} -sub hidden_options {} -sub config { $_[0]->{config}->clone } sub select_preset { my ($self, $i, $force) = @_; @@ -198,62 +161,54 @@ sub select_preset { sub select_preset_by_name { my ($self, $name, $force) = @_; - my $i = first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}; - return if !defined $i; - $self->select_preset($i, $force); + my $presets = wxTheApp->presets->{$self->name}; + my $i = first { $presets->[$_]->name eq $name } 0..$#$presets; + $self->{presets_choice}->SetSelection($i); + $self->on_select_preset($force); } sub prompt_unsaved_changes { my ($self) = @_; - if ($self->is_dirty) { - 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"; - } - push @option_names, $name; - } - - my $changes = join "\n", map "- $_", @option_names; - my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDo you want to save them?", - 'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION); - $confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel'); - my $res = $confirm->ShowModal; - - if ($res == wxID_CANCEL) { - return 0; - } elsif ($res == wxID_YES) { - return $self->save_preset($old_preset->default ? undef : $old_preset->name); - } - } - - return 1; + return 1 if !$self->current_preset; + return $self->current_preset->prompt_unsaved_changes($self); } sub on_select_preset { my ($self, $force) = @_; - my $i = $self->{presets_choice}->GetSelection; - return if defined $self->current_preset && $i == $self->current_preset; + # This method is called: + # - upon first initialization; + # - whenever user selects a preset from the dropdown; + # - whenever select_preset() or select_preset_by_name() are called. - if (!$force && defined $self->current_preset && !$self->prompt_unsaved_changes) { - $self->{presets_choice}->SetSelection($self->current_preset); + # Get the selected name. + my $preset = wxTheApp->presets->{$self->name}->[$self->{presets_choice}->GetSelection]; + + # If selection didn't change, do nothing. + return if defined $self->current_preset && $preset->name eq $self->current_preset->name; + + # If we have unsaved changes, prompt user. + if (!$force && !$self->prompt_unsaved_changes) { + # User decided not to save the current changes, so we restore the previous selection. + my $presets = wxTheApp->presets->{$self->name}; + my $i = first { $presets->[$_]->name eq $self->current_preset->name } 0..$#$presets; + $self->{presets_choice}->SetSelection($i); return; } - $self->current_preset($i); - my $preset = $self->get_current_preset; - my $preset_config = $self->get_preset_config($preset); + $self->current_preset($preset); + + # We reload presets in order to remove the "(modified)" suffix in case user was + # prompted and chose to discard changes. + $self->load_presets; + + $preset->load_config if !$preset->_loaded; + $self->config->clear; + $self->config->apply($preset->dirty_config); + eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - $self->{config}->clear; - $self->{config}->apply($preset_config); ($preset->default || $preset->external) ? $self->{btn_delete_preset}->Disable : $self->{btn_delete_preset}->Enable; @@ -266,14 +221,6 @@ sub on_select_preset { $@ = "I was unable to load the selected config file: $@"; Slic3r::GUI::catch_error($self); } - - # 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->update_dirty; - }); } sub add_options_page { @@ -323,89 +270,35 @@ sub update_tree { } } -sub update_dirty { - my $self = shift; - - foreach my $i (0..$#{$self->{presets}}) { - my $preset = $self->get_preset($i); - if ($i == $self->current_preset && $self->is_dirty) { - $self->{presets_choice}->SetString($i, $preset->name . " (modified)"); - } else { - $self->{presets_choice}->SetString($i, $preset->name); - } - } - $self->{presets_choice}->SetSelection($self->current_preset); # http://trac.wxwidgets.org/ticket/13769 -} - -sub is_dirty { - my $self = shift; - return @{$self->dirty_options} > 0; -} - -sub dirty_options { - my $self = shift; - - return [] if !defined $self->current_preset; # happens during initialization - - my $preset_config = $self->get_current_preset_config; - return [ - (grep !$preset_config->has($_), @{$self->{config}->get_keys}), - (grep !$self->{config}->has($_), @{$preset_config->get_keys}), - @{ $self->{config}->diff($self->get_current_preset_config) }, - ]; -} - sub load_presets { my $self = shift; - $self->{presets} = [ wxTheApp->presets($self->name, 1) ]; - $self->current_preset(undef); + my $presets = wxTheApp->presets->{$self->name}; $self->{presets_choice}->Clear; - $self->{presets_choice}->Append($_->name) for @{$self->{presets}}; - $self->select_preset(0); -} - -sub load_config_file { - my $self = shift; - my ($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 - push @{$self->{presets}}, Slic3r::GUI::Preset->new( - file => $file, - name => $preset_name, - external => 1, - ); - $self->{presets_choice}->Append($preset_name); - $i = $#{$self->{presets}}; + foreach my $preset (@$presets) { + $self->{presets_choice}->Append($preset->dropdown_name); + + # Preserve selection. + if ($self->current_preset && $self->current_preset->name eq $preset->name) { + $self->{presets_choice}->SetSelection($self->{presets_choice}->GetCount-1); + } } - $self->{presets_choice}->SetSelection($i); - $self->on_select_preset; } -sub load_config { +# This is called internally whenever we make automatic adjustments to configuration +# based on user actions. +sub _load_config { my $self = shift; my ($config) = @_; - foreach my $opt_key (@{$self->{config}->diff($config)}) { - $self->{config}->set($opt_key, $config->get($opt_key)); - $self->update_dirty; - } + my $diff = $self->config->diff($config); + $self->config->set($_, $config->get($_)) for @$diff; + # First apply all changes, then call all the _on_value_change triggers. + $self->_on_value_change($_) for @$diff; $self->reload_config; $self->_update; } -sub get_preset_config { - my ($self, $preset) = @_; - - return $preset->config( - [ $self->options ], - [ $self->overriding_options ], - ); -} - sub get_field { my ($self, $opt_key, $opt_index) = @_; @@ -452,16 +345,15 @@ sub _compatible_printers_widget { "Compatible printers", \@presets); my @selections = (); - foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { + foreach my $preset_name (@{ $self->config->get('compatible_printers') }) { push @selections, first { $presets[$_] eq $preset_name } 0..$#presets; } $dlg->SetSelections(@selections); if ($dlg->ShowModal == wxID_OK) { my $value = [ @presets[$dlg->GetSelections] ]; - $self->{config}->set('compatible_printers', $value); - $self->update_dirty; - $self->_on_value_change('compatible_printers', $value); + $self->config->set('compatible_printers', $value); + $self->_on_value_change('compatible_printers'); } }); @@ -554,16 +446,15 @@ sub build { [ map $options{$_}, @opt_keys ]); my @selections = (); - foreach my $opt_key (@{ $self->{config}->get('overridable') }) { + foreach my $opt_key (@{ $self->config->get('overridable') }) { push @selections, first { $opt_keys[$_] eq $opt_key } 0..$#opt_keys; } $dlg->SetSelections(@selections); if ($dlg->ShowModal == wxID_OK) { my $value = [ @opt_keys[$dlg->GetSelections] ]; - $self->{config}->set('overridable', $value); - $self->update_dirty; - $self->_on_value_change('overridable', $value); + $self->config->set('overridable', $value); + $self->_on_value_change('overridable'); } }); @@ -850,11 +741,11 @@ sub _update { $new_conf->set("top_solid_layers", 0); $new_conf->set("fill_density", 0); $new_conf->set("support_material", 0); - $self->load_config($new_conf); + $self->_load_config($new_conf); } else { my $new_conf = Slic3r::Config->new; $new_conf->set("spiral_vase", 0); - $self->load_config($new_conf); + $self->_load_config($new_conf); } } @@ -880,7 +771,7 @@ sub _update { $new_conf->set("support_material", 0); $self->{support_material_overhangs_queried} = 0; } - $self->load_config($new_conf); + $self->_load_config($new_conf); } } } else { @@ -900,7 +791,7 @@ sub _update { } else { $new_conf->set("fill_density", 40); } - $self->load_config($new_conf); + $self->_load_config($new_conf); } my $have_perimeters = $config->perimeters > 0; @@ -975,8 +866,6 @@ sub _update { for qw(standby_temperature_delta); } -sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } - package Slic3r::GUI::PresetEditor::Filament; use base 'Slic3r::GUI::PresetEditor'; @@ -1150,9 +1039,11 @@ sub build { $self->{overrides_panel} = my $panel = Slic3r::GUI::Plater::OverrideSettingsPanel->new($parent, size => [-1, 300], on_change => sub { - $self->{config}->erase($_) for @overridable; - $self->{config}->apply($self->{overrides_config}); - $self->update_dirty; + my ($opt_key) = @_; + $self->config->erase($_) for @overridable; + $self->current_preset->_dirty_config->erase($_) for @overridable; + $self->config->apply($self->{overrides_config}); + $self->_on_value_change($opt_key); }); $panel->set_editable(1); $panel->set_default_config($self->{overrides_default_config}); @@ -1172,8 +1063,8 @@ sub reload_config { $self->{overrides_config}->clear; foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) { - if ($self->{config}->has($opt_key)) { - $self->{overrides_config}->set($opt_key, $self->{config}->get($opt_key)); + if ($self->config->has($opt_key)) { + $self->{overrides_config}->set($opt_key, $self->config->get($opt_key)); } } $self->{overrides_panel}->update_optgroup; @@ -1186,10 +1077,10 @@ sub _update { $self->_update_description; - my $cooling = $self->{config}->cooling; + my $cooling = $self->config->cooling; $self->get_field($_)->toggle($cooling) for qw(max_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed); - $self->get_field($_)->toggle($cooling || $self->{config}->fan_always_on) + $self->get_field($_)->toggle($cooling || $self->config->fan_always_on) for qw(min_fan_speed disable_fan_first_layers); } @@ -1269,12 +1160,11 @@ sub build { label => 'Bed shape', ); $line->append_button("Set…", "cog.png", sub { - my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); + 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->config->set('bed_shape', $value); + $self->_on_value_change('bed_shape'); } }); $optgroup->append_line($line); @@ -1301,7 +1191,6 @@ sub build { wxTheApp->CallAfter(sub { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); }); - $self->update_dirty; } }); } @@ -1328,8 +1217,8 @@ sub build { $line->append_button("Test", "wrench.png", sub { my $sender = Slic3r::GCode::Sender->new; my $res = $sender->connect( - $self->{config}->serial_port, - $self->{config}->serial_speed, + $self->config->serial_port, + $self->config->serial_speed, ); if ($res && $sender->wait_connected) { Slic3r::GUI::show_info($self, "Connection to printer works correctly.", "Success!"); @@ -1355,10 +1244,8 @@ sub build { 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->config->set('octoprint_host', $value); + $self->_on_value_change('octoprint_host'); } } else { Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; @@ -1369,8 +1256,8 @@ sub build { $ua->timeout(10); my $res = $ua->get( - "http://" . $self->{config}->octoprint_host . "/api/version", - 'X-Api-Key' => $self->{config}->octoprint_apikey, + "http://" . $self->config->octoprint_host . "/api/version", + 'X-Api-Key' => $self->config->octoprint_apikey, ); if ($res->is_success) { Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!"); @@ -1462,7 +1349,7 @@ sub _extruders_count_changed { $self->{extruders_count} = $extruders_count; $self->_build_extruder_pages; - $self->_on_value_change('extruders_count', $extruders_count); + $self->_on_value_change('extruders_count'); $self->_update; } @@ -1477,7 +1364,7 @@ sub _build_extruder_pages { 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); + my $values = $self->config->get($opt_key); if (!defined $values) { $values = [ $default_config->get_at($opt_key, 0) ]; } else { @@ -1485,7 +1372,7 @@ sub _build_extruder_pages { my $last_value = $values->[-1]; $values->[$extruder_idx] //= $last_value; } - $self->{config}->set($opt_key, $values) + $self->config->set($opt_key, $values) or die "Unable to extend $opt_key"; } @@ -1531,9 +1418,9 @@ sub _build_extruder_pages { # remove extra config values foreach my $opt_key ($self->_extruder_options) { - my $values = $self->{config}->get($opt_key); + my $values = $self->config->get($opt_key); splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values; - $self->{config}->set($opt_key, $values) + $self->config->set($opt_key, $values) or die "Unable to truncate $opt_key"; } @@ -1610,7 +1497,7 @@ sub _update { } else { $new_conf->set("use_firmware_retraction", 0); } - $self->load_config($new_conf); + $self->_load_config($new_conf); } $self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders); @@ -1628,7 +1515,7 @@ sub on_preset_loaded { # 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 }; + my $extruders_count = scalar @{ $self->config->nozzle_diameter }; $self->set_value('extruders_count', $extruders_count); $self->_extruders_count_changed($extruders_count); } @@ -1642,7 +1529,7 @@ sub load_config_file { "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; + if @{ $self->config->nozzle_diameter } > 1; } package Slic3r::GUI::PresetEditor::Page; @@ -1676,8 +1563,7 @@ sub new_optgroup { on_change => sub { my ($opt_key, $value) = @_; wxTheApp->CallAfter(sub { - $self->GetParent->update_dirty; - $self->GetParent->_on_value_change($opt_key, $value); + $self->GetParent->_on_value_change($opt_key); }); }, ); @@ -1726,7 +1612,7 @@ sub new { my @values = @{$params{values}}; - my $text = Wx::StaticText->new($self, -1, "Save " . lc($params{title}) . " as:", wxDefaultPosition, wxDefaultSize); + my $text = Wx::StaticText->new($self, -1, "Save profile as:", wxDefaultPosition, wxDefaultSize); $self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, \@values, wxTE_PROCESS_ENTER); my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL); diff --git a/lib/Slic3r/GUI/PresetEditorDialog.pm b/lib/Slic3r/GUI/PresetEditorDialog.pm index d706622a3..6385844c5 100644 --- a/lib/Slic3r/GUI/PresetEditorDialog.pm +++ b/lib/Slic3r/GUI/PresetEditorDialog.pm @@ -1,8 +1,8 @@ package Slic3r::GUI::PresetEditorDialog; use strict; use warnings; -use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp); -use Wx::Event qw(EVT_CLOSE); +use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp WXK_ESCAPE); +use Wx::Event qw(EVT_CLOSE EVT_CHAR_HOOK); use base qw(Wx::Dialog Class::Accessor); use utf8; @@ -15,7 +15,6 @@ sub new { $self->preset_editor($self->preset_editor_class->new($self)); $self->SetTitle($self->preset_editor->title); - $self->preset_editor->load_presets; my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->preset_editor, 1, wxEXPAND); @@ -24,23 +23,32 @@ sub new { #$sizer->SetSizeHints($self); if (0) { - # This does not call the EVT_CLOSE below my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE); $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); } + wxTheApp->restore_window_pos($self, "preset_editor"); + EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->preset_editor->prompt_unsaved_changes) { - $event->Veto; - return; - } + # save window size + wxTheApp->save_window_pos($self, "preset_editor"); # propagate event $event->Skip; }); + EVT_CHAR_HOOK($self, sub { + my (undef, $event) = @_; + + if ($event->GetKeyCode == WXK_ESCAPE) { + $self->Close; + } else { + $event->Skip; + } + }); + return $self; } diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index b5565ea76..f18e4e025 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -532,6 +532,11 @@ DynamicConfig::clear() { this->options.clear(); } +bool +DynamicConfig::empty() const { + return this->options.empty(); +} + void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) { diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 45ba8a3ba..41d3cc668 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -678,6 +678,7 @@ class DynamicConfig : public virtual ConfigBase t_config_option_keys keys() const; void erase(const t_config_option_key &opt_key); void clear(); + bool empty() const; void read_cli(const std::vector &tokens, t_config_option_keys* extra); void read_cli(const int argc, const char **argv, t_config_option_keys* extra); diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 25da96477..6744a1e10 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -40,6 +40,7 @@ %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); void clear(); + bool empty(); void normalize(); %name{setenv} void setenv_(); double min_object_distance(); From 0b180c62732924dbb32ecda78e09b238382a7314 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 22 Mar 2017 20:48:58 +0100 Subject: [PATCH 11/68] Move the overridable settings list in the preset editor without opening one more dialog --- lib/Slic3r/GUI/PresetEditor.pm | 79 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 758810490..41eb4973c 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -338,7 +338,7 @@ sub _compatible_printers_widget { EVT_BUTTON($self, $btn, sub { my @presets = map $_->name, grep !$_->default && !$_->external, - wxTheApp->presets('printer'); + @{wxTheApp->presets->{printer}}; my $dlg = Wx::MultiChoiceDialog->new($self, "Select the printers this profile is compatible with.\nIf none are selected, it will be considered compatible with all of them.", @@ -370,7 +370,7 @@ use base 'Slic3r::GUI::PresetEditor'; use List::Util qw(first any); use Wx qw(:icon :dialog :id :misc :button :sizer); -use Wx::Event qw(EVT_BUTTON); +use Wx::Event qw(EVT_BUTTON EVT_CHECKLISTBOX); sub name { 'print' } sub title { 'Print Settings' } @@ -424,40 +424,25 @@ sub build { my $overridable_widget = sub { my ($parent) = @_; - my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, - wxBU_LEFT | wxBU_EXACTFIT); - $btn->SetFont($Slic3r::GUI::small_font); - if ($Slic3r::GUI::have_button_icons) { - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); - } + my %options = ( + map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } + map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new + ); + my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options; + $self->{overridable_opt_keys} = [ @opt_keys ]; - my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($btn); + my $listbox = $self->{overridable_list} = Wx::CheckListBox->new($parent, -1, + wxDefaultPosition, [-1, 320], [ map $options{$_}, @opt_keys ]); - EVT_BUTTON($self, $btn, sub { - my %options = ( - map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } - map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new - ); - my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options; - - my $dlg = Wx::MultiChoiceDialog->new($self, "Selected options will be displayed in the plater screen for quick changes.", - "Overridable options", - [ map $options{$_}, @opt_keys ]); - - my @selections = (); - foreach my $opt_key (@{ $self->config->get('overridable') }) { - push @selections, first { $opt_keys[$_] eq $opt_key } 0..$#opt_keys; - } - $dlg->SetSelections(@selections); - - if ($dlg->ShowModal == wxID_OK) { - my $value = [ @opt_keys[$dlg->GetSelections] ]; - $self->config->set('overridable', $value); - $self->_on_value_change('overridable'); - } + EVT_CHECKLISTBOX($self, $listbox, sub { + my $value = [ map $opt_keys[$_], grep $listbox->IsChecked($_), 0..$#opt_keys ]; + $self->config->set('overridable', $value); + $self->_on_value_change('overridable'); }); + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add($listbox, 0, wxEXPAND); + return $sizer; }; @@ -703,13 +688,6 @@ sub build { my $page = $self->add_options_page('Overrides', 'wrench.png'); { my $optgroup = $page->new_optgroup('Profile preferences'); - { - my $line = Slic3r::GUI::OptionsGroup::Line->new( - label => 'Overridable settings', - widget => $overridable_widget, - ); - $optgroup->append_line($line); - } { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Compatible printers', @@ -718,9 +696,32 @@ sub build { $optgroup->append_line($line); } } + { + my $optgroup = $page->new_optgroup('Overridable settings (they will be displayed in the plater for quick changes)'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + widget => $overridable_widget, + full_width => 1, + ); + $optgroup->append_line($line); + } + } } } +sub reload_config { + my ($self) = @_; + + { + my %overridable = map { $_ => 1 } @{ $self->config->get('overridable') }; + for my $i (0..$#{$self->{overridable_opt_keys}}) { + $self->{overridable_list}->Check($i, $overridable{ $self->{overridable_opt_keys}[$i] }); + } + } + + $self->SUPER::reload_config; +} + sub _update { my ($self) = @_; From ce64cb05a1c7dd9b89c4ba6347e1b48e125d9a3d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 22 Mar 2017 21:05:22 +0100 Subject: [PATCH 12/68] Use a nicer "All" checkbox for printer compatibility --- lib/Slic3r/GUI/PresetEditor.pm | 52 ++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 41eb4973c..1251532a3 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -7,7 +7,7 @@ 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_TREE_SEL_CHANGED); +use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_TREE_SEL_CHANGED EVT_CHECKBOX); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(current_preset config)); @@ -242,6 +242,7 @@ sub add_options_page { sub reload_config { my $self = shift; + $_->reload_config for @{$self->{pages}}; } @@ -326,7 +327,9 @@ sub _compatible_printers_widget { return sub { my ($parent) = @_; - my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, + 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); if ($Slic3r::GUI::have_button_icons) { @@ -334,14 +337,23 @@ sub _compatible_printers_widget { } my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $sizer->Add($btn); + $sizer->Add($checkbox, 0, wxALIGN_CENTER_VERTICAL); + $sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL); + + EVT_CHECKBOX($self, $checkbox, sub { + if ($checkbox->GetValue) { + $btn->Disable; + } else { + $btn->Enable; + } + }); EVT_BUTTON($self, $btn, sub { my @presets = map $_->name, grep !$_->default && !$_->external, @{wxTheApp->presets->{printer}}; my $dlg = Wx::MultiChoiceDialog->new($self, - "Select the printers this profile is compatible with.\nIf none are selected, it will be considered compatible with all of them.", + "Select the printers this profile is compatible with.", "Compatible printers", \@presets); my @selections = (); @@ -352,6 +364,10 @@ sub _compatible_printers_widget { if ($dlg->ShowModal == wxID_OK) { my $value = [ @presets[$dlg->GetSelections] ]; + if (!@$value) { + $checkbox->SetValue(1); + $btn->Disable; + } $self->config->set('compatible_printers', $value); $self->_on_value_change('compatible_printers'); } @@ -361,6 +377,18 @@ sub _compatible_printers_widget { }; } +sub _reload_compatible_printers_widget { + my ($self) = @_; + + if (@{ $self->config->get('compatible_printers') }) { + $self->{compatible_printers_checkbox}->SetValue(0); + $self->{compatible_printers_btn}->Enable; + } else { + $self->{compatible_printers_checkbox}->SetValue(1); + $self->{compatible_printers_btn}->Disable; + } +} + sub options { die "Unimplemented options()"; } sub overridable_options { () } sub overriding_options { () } @@ -712,6 +740,8 @@ sub build { sub reload_config { my ($self) = @_; + $self->_reload_compatible_printers_widget; + { my %overridable = map { $_ => 1 } @{ $self->config->get('overridable') }; for my $i (0..$#{$self->{overridable_opt_keys}}) { @@ -1062,13 +1092,17 @@ sub build { sub reload_config { my ($self) = @_; - $self->{overrides_config}->clear; - foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) { - if ($self->config->has($opt_key)) { - $self->{overrides_config}->set($opt_key, $self->config->get($opt_key)); + $self->_reload_compatible_printers_widget; + + { + $self->{overrides_config}->clear; + foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) { + if ($self->config->has($opt_key)) { + $self->{overrides_config}->set($opt_key, $self->config->get($opt_key)); + } } + $self->{overrides_panel}->update_optgroup; } - $self->{overrides_panel}->update_optgroup; $self->SUPER::reload_config; } From 82db702801d34d88a78928ad53acd63744c482f2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 22 Mar 2017 22:19:12 +0100 Subject: [PATCH 13/68] List objects in a submenu and allow to select them from it --- lib/Slic3r/GUI/MainFrame.pm | 6 ++++++ lib/Slic3r/GUI/Plater.pm | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 7b21d64c3..2b988cb64 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -176,6 +176,12 @@ sub _init_menubar { my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; + { + my $selectMenu = $self->{plater_select_menu} = Wx::Menu->new; + my $selectMenuItem = $self->{plater_menu}->AppendSubMenu($selectMenu, "Select", 'Select an object in the plater'); + wxTheApp->set_menu_item_icon($selectMenuItem, 'brick.png'); + } + $self->{plater_menu}->AppendSeparator(); $self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $plater->export_gcode; }, undef, 'cog_go.png'); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f586c7b43..6ad698e7a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1873,6 +1873,23 @@ sub on_thumbnail_made { sub on_model_change { my ($self, $force_autocenter) = @_; + # reload the select submenu (if already initialized) + if (my $menu = $self->GetFrame->{plater_select_menu}) { + $menu->DeleteItem($_) for $menu->GetMenuItems; + for my $i (0..$#{$self->{objects}}) { + my $name = $self->{objects}->[$i]->name; + my $count = $self->{model}->get_object($i)->instances_count; + if ($count > 1) { + $name .= " (${count}x)"; + } + my $item = $self->GetFrame->_append_menu_item($menu, $name, 'Select object', sub { + $self->select_object($i); + $self->refresh_canvases; + }, undef, undef, wxITEM_CHECK); + $item->Check(1) if $self->{objects}->[$i]->selected; + } + } + my $running = $self->pause_background_process; if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { @@ -2061,6 +2078,13 @@ sub selection_changed { my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; + if (my $menu = $self->GetFrame->{plater_select_menu}) { + $_->Check(0) for $menu->GetMenuItems; + if ($have_sel) { + $menu->FindItemByPosition($obj_idx)->Check(1); + } + } + my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); From a34c80b70b5f9875baba99dc7caa69046e3d5d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xoan=20Sampai=C3=B1o?= Date: Thu, 23 Mar 2017 14:43:35 +0100 Subject: [PATCH 14/68] Add accelerators (keyboard shortcuts) to View menu * Follow the OpenSCAD convention: * Reorder the items * Rename Rear -> Back and Iso -> Diagonal (Iso stands for a projection, not for a view -- upcoming versions could have isometric/prespective views...) * Assign accels from 4 to 0 --- lib/Slic3r/GUI/3DScene.pm | 24 ++++++++++++------------ lib/Slic3r/GUI/MainFrame.pm | 14 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 7c9ae2dee..09801fe6d 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -54,13 +54,13 @@ use constant HAS_VBO => eval { glGenBuffersARB_p(0); 1 }; # phi / theta angles to orient the camera. -use constant VIEW_ISO => [45.0,45.0]; -use constant VIEW_LEFT => [90.0,90.0]; -use constant VIEW_RIGHT => [-90.0,90.0]; use constant VIEW_TOP => [0.0,0.0]; use constant VIEW_BOTTOM => [0.0,180.0]; +use constant VIEW_LEFT => [90.0,90.0]; +use constant VIEW_RIGHT => [-90.0,90.0]; use constant VIEW_FRONT => [0.0,90.0]; -use constant VIEW_REAR => [180.0,90.0]; +use constant VIEW_BACK => [180.0,90.0]; +use constant VIEW_DIAGONAL => [45.0,45.0]; use constant GIMBAL_LOCK_THETA_MAX => 170; @@ -338,20 +338,20 @@ sub select_view { if (ref($direction)) { $dirvec = $direction; } else { - if ($direction eq 'iso') { - $dirvec = VIEW_ISO; + if ($direction eq 'top') { + $dirvec = VIEW_TOP; + } elsif ($direction eq 'bottom') { + $dirvec = VIEW_BOTTOM; } elsif ($direction eq 'left') { $dirvec = VIEW_LEFT; } elsif ($direction eq 'right') { $dirvec = VIEW_RIGHT; - } elsif ($direction eq 'top') { - $dirvec = VIEW_TOP; - } elsif ($direction eq 'bottom') { - $dirvec = VIEW_BOTTOM; } elsif ($direction eq 'front') { $dirvec = VIEW_FRONT; - } elsif ($direction eq 'rear') { - $dirvec = VIEW_REAR; + } elsif ($direction eq 'back') { + $dirvec = VIEW_BACK; + } elsif ($direction eq 'diagonal') { + $dirvec = VIEW_DIAGONAL; } } $self->_sphi($dirvec->[0]); diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 2b988cb64..f779de305 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -213,13 +213,13 @@ sub _init_menubar { # View menu { $self->{viewMenu} = Wx::Menu->new; - $self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); }); - $self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); }); - $self->_append_menu_item($self->{viewMenu}, "Bottom" , 'Bottom View' , sub { $self->select_view('bottom' ); }); - $self->_append_menu_item($self->{viewMenu}, "Front" , 'Front View' , sub { $self->select_view('front' ); }); - $self->_append_menu_item($self->{viewMenu}, "Rear" , 'Rear View' , sub { $self->select_view('rear' ); }); - $self->_append_menu_item($self->{viewMenu}, "Left" , 'Left View' , sub { $self->select_view('left' ); }); - $self->_append_menu_item($self->{viewMenu}, "Right" , 'Right View' , sub { $self->select_view('right' ); }); + $self->_append_menu_item($self->{viewMenu}, "Top\tCtrl+4" , 'Top View' , sub { $self->select_view('top' ); }); + $self->_append_menu_item($self->{viewMenu}, "Bottom\tCtrl+5" , 'Bottom View' , sub { $self->select_view('bottom' ); }); + $self->_append_menu_item($self->{viewMenu}, "Left\tCtrl+6" , 'Left View' , sub { $self->select_view('left' ); }); + $self->_append_menu_item($self->{viewMenu}, "Right\tCtrl+7" , 'Right View' , sub { $self->select_view('right' ); }); + $self->_append_menu_item($self->{viewMenu}, "Front\tCtrl+8" , 'Front View' , sub { $self->select_view('front' ); }); + $self->_append_menu_item($self->{viewMenu}, "Back\tCtrl+9" , 'Back View' , sub { $self->select_view('back' ); }); + $self->_append_menu_item($self->{viewMenu}, "Diagonal\tCtrl+0", 'Diagonal View', sub { $self->select_view('diagonal'); }); $self->{viewMenu}->AppendSeparator(); $self->{color_toolpaths_by_role} = $self->_append_menu_item($self->{viewMenu}, "Color Toolpaths by Role", From 48910a79708e9001e73529787634a289ac304d4e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Mar 2017 15:08:00 +0100 Subject: [PATCH 15/68] Remove the stable branch from Travis and add IRC notifications --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d84fd28d..5e6930fbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ perl: branches: only: - master - - stable sudo: false cache: apt: true @@ -26,3 +25,10 @@ addons: - liblocal-lib-perl - g++-4.9 env: CC=g++-4.9 +notifications: + irc: + channels: + - "chat.freenode.net#slic3r" + on_success: change + on_failure: always + use_notice: true From 1843e377d3279a31ff0dfd1c312e295680489ced Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Mar 2017 17:07:11 +0100 Subject: [PATCH 16/68] Bugfix: missing infill segments in some rare circumstances --- t/fill.t | 19 ++++++++++++++++++- xs/src/libslic3r/Fill/FillRectilinear.cpp | 11 ++++++++--- xs/src/libslic3r/LayerRegionFill.cpp | 6 ++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/t/fill.t b/t/fill.t index c9cb21391..710e60536 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 93; +plan tests => 95; BEGIN { use FindBin; @@ -86,6 +86,23 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } 'paths don\'t cross hole') or done_testing, exit; } } + + # rotated square + $filler->set_angle(PI/4); + $filler->set_dont_adjust(0); + $filler->set_min_spacing(0.654498); + $filler->set_endpoints_overlap(unscale(359974)); + $filler->set_density(1); + $filler->set_layer_id(66); + $filler->set_z(20.15); + { + my $e = Slic3r::ExPolygon->new( + Slic3r::Polygon->new([25771516,14142125],[14142138,25771515],[2512749,14142131],[14142125,2512749]), + ); + my $paths = $test->($e); + is(scalar @$paths, 1, 'one continuous path') or done_testing, exit; + ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length'; + } } { diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index c74da2877..e02c3a298 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -37,9 +37,14 @@ FillRectilinear::_fill_single_direction(ExPolygon expolygon, if (bounding_box.size().x < min_spacing) return; // Due to integer rounding, rotated polygons might not preserve verticality - // (i.e. when rotating by PI/2 two points having the same x coordinate - // they might get different y coordinates), thus the first line will be skipped. - bounding_box.offset(-1); + // (i.e. when rotating by PI/2 two points having the same y coordinate + // they might get different x coordinates), thus the first line will be skipped. + // Reducing by 1 is not enough, as we observed normal squares being off by about 30 + // units along x between points supposed to be vertically aligned (coming from an + // axis-aligned polygon edge). We need to be very tolerant here, especially when + // making solid infill where lack of lines is visible. + bounding_box.min.x += SCALED_EPSILON; + bounding_box.max.x -= SCALED_EPSILON; // define flow spacing according to requested density if (this->density > 0.9999f && !this->dont_adjust) { diff --git a/xs/src/libslic3r/LayerRegionFill.cpp b/xs/src/libslic3r/LayerRegionFill.cpp index 954f1d135..d6b567ceb 100644 --- a/xs/src/libslic3r/LayerRegionFill.cpp +++ b/xs/src/libslic3r/LayerRegionFill.cpp @@ -246,6 +246,12 @@ LayerRegion::make_fill() // apply half spacing using this flow's own spacing and generate infill f->density = density/100; f->dont_adjust = false; + /* + std::cout << surface.expolygon.dump_perl() << std::endl + << " layer_id: " << f->layer_id << " z: " << f->z + << " angle: " << f->angle << " min-spacing: " << f->min_spacing + << " endpoints_overlap: " << f->endpoints_overlap << std::endl << std::endl; + */ Polylines polylines = f->fill_surface(surface); if (polylines.empty()) continue; From aa02dfe21434d22455440582bbaf3491a49be1fe Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Thu, 23 Mar 2017 15:48:53 -0500 Subject: [PATCH 17/68] working on rc file to define manifest and filetype (#3797) * working on rc file to define manifest and filetype * use number 24 instead of name manifest --- package/win/slic3r.rc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/win/slic3r.rc b/package/win/slic3r.rc index 20cfa265e..290d51939 100644 --- a/package/win/slic3r.rc +++ b/package/win/slic3r.rc @@ -2,6 +2,8 @@ id ICON "../../var/Slic3r.ico" 1 VERSIONINFO FILEVERSION 1,3,0,0 PRODUCTVERSION 1,3,0,0 +FILEOS 0x4 +FILETYPE 0x1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -10,7 +12,7 @@ BEGIN VALUE "CompanyName", "Slic3r.org" VALUE "FileDescription", "3D Printer Slicer application" VALUE "FileVersion", "1.3.0" - VALUE "InternalName", "slic3r" + VALUE "InternalName", "slic3r.exe" VALUE "LegalCopyright", "Alessandro Ranellucci" VALUE "OriginalFilename", "slic3r.exe" VALUE "ProductName", "Slic3r" @@ -22,4 +24,4 @@ BEGIN VALUE "Translation", 0x409, 1252 END END -1 Manifest slic3r.exe.manifest +1 24 "slic3r.exe.manifest" From 8f27e8f1c0a064ead20d53dc7f9051e8adaf3b2c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 24 Mar 2017 00:55:24 +0100 Subject: [PATCH 18/68] Some updates to the README --- README.md | 142 ++++++++++++++++++++++++++---------------------------- 1 file changed, 67 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 57010dd4c..23b74dc6f 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,50 @@ -_Q: Oh cool, a new RepRap slicer?_ - -A: Yes. - -Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r) +![](var/Slic3r_128px.png) Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) [![Build Status](http://osx-build.slic3r.org:8080/buildStatus/icon?job=Slic3r)](http://osx-build.slic3r.org:8080/job/Slic3r) ====== -Prebuilt Windows (64-bit) and OSX (>10.7) builds: -* http://dl.slic3r.org/dev/ - +We have automated builds for Windows (64-bit) and OSX (>= 10.7). [Get a fresh build now](http://dl.slic3r.org/dev/) and stay up-to-date with the development! -Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for -3D printers. It's compatible with any modern printer based on the RepRap toolchain, -including all those based on the Marlin, Sprinter and Repetier firmware. It also works -with Mach3, LinuxCNC and Machinekit controllers. +The MacOS X build server is kindly sponsored by: -See the [project homepage](http://slic3r.org/) at slic3r.org and the -[manual](http://manual.slic3r.org/) for more information. +### So, what's this Slic3r? + +Slic3r is mainly a **toolpath generator** for 3D printers: it reads 3D models (STL, OBJ, AMF) and it converts them into **G-code** instructions for 3D printers. But it does much more than that, see the [features list](#feature) below. + +Slic3r was born in **2011** within the RepRap community and thanks to its high configurability became the swiss-army knife for 3D printing. It served as a platform for experimenting several **new ideas that later became technology standards**, such as multiple extruders, brim, variable-height layers, per-object settings, modifiers, post-processing scripts, G-code macros and more. Despite being based on volunteer efforts, Slic3r is still pushing the boundaries of 3D printing. + +Slic3r is: + +* **Open:** it is totally **open source** and it's **independent from any commercial company** or printer manufacturer. We want to keep 3D printing open and free. +* **Compatible:** it supports all the known G-code dialects (Marlin, Repetier, Mach3, LinuxCNC, Machinekit, Smoothie, Makerware, Sailfish). +* **Advanced:** many configuration options allow for fine-tuning and full control. While novice users often need just few options, Slic3r is mostly used by advanced users. +* **Community-driven:** new features or issues are discussed in the [GitHub repository](https://github.com/alexrj/Slic3r/issues). Join our collaborative effort and help improve it! +* **Robust:** the codebase includes more than 1,000 unit and regression tests, collected in 6 years of development. +* **Modular:** the core of Slic3r is libslic3r, a C++ library that provides a granular API and reusable components. +* **Embeddable:** a complete and powerful command line interface allows to use Slic3r from the shell or to integrate it in server-side applications. +* **Powerful:** see the list below! + +See the [project homepage](http://slic3r.org/) at slic3r.org for more information. + +### Features + +(Most of these are also available in the command line interface.) + +* **G-code generation** for FFF/FDM printers; +* **conversion** between STL, OBJ, AMF and POV formats; +* **auto-repair** of non-manifold meshes (and ability to re-export them); +* **SVG export** of slices; +* built-in **USB/serial** host controller, supporting **multiple simultaneous printers** each one with a spool queue; +* **OctoPrint integration** (send to printer); +* built-in **projector and host for DLP printers**; +* tool for **cutting meshes** in multiple solid parts with visual preview (also in batch using a grid); +* tool for **extruding 2.5D TIN meshes**. ### What language is it written in? -The core geometric algorithms and data structures are written in C++, -and Perl is used for high-level flow abstraction, GUI and testing. -If you're wondering why Perl, see https://xkcd.com/224/ - -The C++ API is public and its use in other projects is encouraged. -The goal is to make Slic3r fully modular so that any part of its logic -can be used separately. - -### What are Slic3r's main features? - -Key features are: - -* **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required -* complete **command-line interface** to use it with no GUI -* multi-material **(multiple extruders)** object printing -* multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.) -* ability to plate **multiple objects having distinct print settings** -* **multithread** processing -* **STL auto-repair** (tolerance for broken models) -* wide automated unit testing - -Other major features are: - -* combine infill every 'n' perimeters layer to speed up printing -* **3D preview** (including multi-material files) -* **multiple layer heights** in a single print -* **spiral vase** mode for bumpless vases -* fine-grained configuration of speed, acceleration, extrusion width -* several infill patterns including honeycomb, spirals, Hilbert curves -* support material, raft, brim, skirt -* **standby temperature** and automatic wiping for multi-extruder printing -* customizable **G-code macros** and output filename with variable placeholders -* support for **post-processing scripts** -* **cooling logic** controlling fan speed and dynamic print speed +The core parts of Slic3r are written in C++11, with multithreading. The graphical interface is still mostly written in Perl, but we're gradually porting it to C++ (want to help?). The goal is to port everything to C++. ### How to install? -You can download a precompiled package from [slic3r.org](http://slic3r.org/); -it will run without the need for any dependency. +You can download a precompiled package from [slic3r.org](http://slic3r.org/) (releases) or from [dl.slicr3r.org](http://dl.slic3r.org/dev/) (automated builds). If you want to compile the source yourself follow the instructions on one of these wiki pages: * [Linux](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-GNU-Linux) @@ -67,28 +54,40 @@ If you want to compile the source yourself follow the instructions on one of the ### Can I help? Sure! You can do the following to find things that are available to help with: -* [Pull Request Milestone](https://github.com/alexrj/Slic3r/milestone/31) - * Please comment in the related github issue that you are working on it so that other people know. -* Items in the [TODO](https://github.com/alexrj/Slic3r/wiki/TODO) wiki page. - * Please comment in the related github issue that you are working on it so that other people know. -* Drop me a line at aar@cpan.org. -* You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](https://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels. -* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the github tracker if it isn't already present. -Before sending patches and pull requests contact me (preferably through opening a github issue or commenting on an existing, related, issue) to discuss your proposed -changes: this way we'll ensure nobody wastes their time and no conflicts arise -in development. +* Development + * [Low Effort tasks](https://github.com/alexrj/Slic3r/labels/Low%20Effort): pick one of them! + * [More available tasks](https://github.com/alexrj/Slic3r/milestone/31): let's discuss together before you start working on them + * Please comment in the related github issue that you are working on it so that other people know. + * Please comment in the related GitHub issue that you are working on it so that other people know. +* Contribute to the [Manual](http://manual.slic3r.org/)! (see its [GitHub repository](https://github.com/alexrj/Slic3r-Manual)) +* You can also find us in #slic3r on [FreeNode](https://webchat.freenode.net): talk to _Sound_, _LoH_ or the other members of the Slic3r community. +* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the GitHub tracker if it isn't already present. +* Drop Alessandro a line at aar@cpan.org. -### What's Slic3r license? +### Directory structure -Slic3r is licensed under the _GNU Affero General Public License, version 3_. -The author is Alessandro Ranellucci. +* `Build.PL`: this script installs dependencies into `local-lib/`, compiles the C++ part and runs tests +* `lib/`: the Perl code +* `package/`: the scripts used for packaging the executables +* `slic3r.pl`: the main executable script, launching the GUI and providing the CLI +* `src/`: the C++ source of the `slic3r` executable the and CMake definition file for compiling it (note that this C++ `slic3r` executable can do many things but can't generate G-code yet because the porting isn't finished yet - the main executable is `slic3r.pl`) +* `t/`: the test suite +* `utils/`: various useful scripts +* `xs/src/libslic3r/`: C++ sources for libslic3r +* `xs/src/slic3r/`: C++ sources for the Slic3r GUI application +* `xs/t/`: test suite for libslic3r +* `xs/xsp/`: bindings for calling libslic3r from Perl (XS) -The [Silk icon set](http://www.famfamfam.com/lab/icons/silk/) used in Slic3r is -licensed under the _Creative Commons Attribution 3.0 License_. -The author of the Silk icon set is Mark James. +### Acknowledgements -### How can I invoke slic3r.pl using the command line? +The main author of Slic3r is Alessandro Ranellucci (@alexrj, *Sound* in IRC, [@alranel](http://twitter.com/alranel) on Twitter), who started the project in 2011 and still leads development. + +Joseph Lenox (@lordofhyphens, *Loh* in IRC) is the current co-maintainer. + +Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. Original manual by Gary Hodgson. Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. + +### How can I invoke Slic3r using the command line? Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... @@ -384,11 +383,4 @@ The author of the Silk icon set is Mark James. Temperature difference to be applied when an extruder is not active and --ooze-prevention is enabled (default: -5) - -If you want to change a preset file, just do - - slic3r.pl --load config.ini --layer-height 0.25 --save config.ini - -If you want to slice a file overriding an option contained in your preset file: - - slic3r.pl --load config.ini --layer-height 0.25 file.stl +For more information about command line usage see the relevant [manual page](http://manual.slic3r.org/advanced/command-line). From 5d2626fd3a35ba2093f0773d460dcb8dcf662ccb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 24 Mar 2017 00:57:07 +0100 Subject: [PATCH 19/68] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23b74dc6f..926126bde 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The MacOS X build server is kindly sponsored by: Date: Fri, 24 Mar 2017 17:54:02 +0100 Subject: [PATCH 20/68] New GCodeTimeEstimator class, with basic estimation code by @lordofhyphens. #3747 --- src/CMakeLists.txt | 1 + utils/estimate-gcode-time.pl | 21 +++++ xs/MANIFEST | 3 + xs/src/libslic3r/GCode/SpiralVase.cpp | 2 +- xs/src/libslic3r/GCodeReader.cpp | 117 +++++++++++++----------- xs/src/libslic3r/GCodeReader.hpp | 5 +- xs/src/libslic3r/GCodeTimeEstimator.cpp | 78 ++++++++++++++++ xs/src/libslic3r/GCodeTimeEstimator.hpp | 24 +++++ xs/src/perlglue.cpp | 1 + xs/xsp/GCodeTimeEstimator.xsp | 15 +++ xs/xsp/my.map | 4 + 11 files changed, 218 insertions(+), 53 deletions(-) create mode 100755 utils/estimate-gcode-time.pl create mode 100644 xs/src/libslic3r/GCodeTimeEstimator.cpp create mode 100644 xs/src/libslic3r/GCodeTimeEstimator.hpp create mode 100644 xs/xsp/GCodeTimeEstimator.xsp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85e98471e..14f4a657a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/GCode/SpiralVase.cpp ${LIBDIR}/libslic3r/GCodeReader.cpp ${LIBDIR}/libslic3r/GCodeSender.cpp + ${LIBDIR}/libslic3r/GCodeTimeEstimator.cpp ${LIBDIR}/libslic3r/GCodeWriter.cpp ${LIBDIR}/libslic3r/Geometry.cpp ${LIBDIR}/libslic3r/IO.cpp diff --git a/utils/estimate-gcode-time.pl b/utils/estimate-gcode-time.pl new file mode 100755 index 000000000..43c558b68 --- /dev/null +++ b/utils/estimate-gcode-time.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; +} + +use Slic3r; + +die "Usage: estimate-gcode-time.pl FILE\n" + if @ARGV != 1; + +my $estimator = Slic3r::GCode::TimeEstimator->new; +$estimator->parse_file($ARGV[0]); +printf "Time: %d minutes and %d seconds\n", int($estimator->time / 60), $estimator->time % 60; + +__END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 61a97afed..5b2925f2c 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -72,6 +72,8 @@ src/libslic3r/GCodeReader.cpp src/libslic3r/GCodeReader.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp +src/libslic3r/GCodeTimeEstimator.cpp +src/libslic3r/GCodeTimeEstimator.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp src/libslic3r/Geometry.cpp @@ -180,6 +182,7 @@ xsp/Filler.xsp xsp/Flow.xsp xsp/GCode.xsp xsp/GCodeSender.xsp +xsp/GCodeTimeEstimator.xsp xsp/GCodeWriter.xsp xsp/Geometry.xsp xsp/GUI.xsp diff --git a/xs/src/libslic3r/GCode/SpiralVase.cpp b/xs/src/libslic3r/GCode/SpiralVase.cpp index b56c9e58b..9299da86a 100644 --- a/xs/src/libslic3r/GCode/SpiralVase.cpp +++ b/xs/src/libslic3r/GCode/SpiralVase.cpp @@ -39,7 +39,7 @@ SpiralVase::process_layer(const std::string &gcode) { GCodeReader r = this->_reader; // clone r.parse(gcode, [&total_layer_length, &layer_height, &z, &set_z] - (GCodeReader &, GCodeReader::GCodeLine &line) { + (GCodeReader &, const GCodeReader::GCodeLine &line) { if (line.cmd == "G1") { if (line.extruding()) { total_layer_length += line.dist_XY(); diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index 1a0742864..6ad937bbd 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -1,6 +1,7 @@ #include "GCodeReader.hpp" #include #include +#include #include namespace Slic3r { @@ -17,59 +18,73 @@ GCodeReader::parse(const std::string &gcode, callback_t callback) { std::istringstream ss(gcode); std::string line; - while (std::getline(ss, line)) { - GCodeLine gline(this); - gline.raw = line; - if (this->verbose) - std::cout << line << std::endl; - - // strip comment - { - size_t pos = line.find(';'); - if (pos != std::string::npos) { - gline.comment = line.substr(pos+1); - line.erase(pos); - } - } - - // command and args - { - std::vector args; - boost::split(args, line, boost::is_any_of(" ")); - - // first one is cmd - gline.cmd = args.front(); - args.erase(args.begin()); - - for (std::string &arg : args) { - if (arg.size() < 2) continue; - gline.args.insert(std::make_pair(arg[0], arg.substr(1))); - } - } - - // convert extrusion axis - if (this->_extrusion_axis != 'E') { - const auto it = gline.args.find(this->_extrusion_axis); - if (it != gline.args.end()) { - std::swap(gline.args['E'], it->second); - gline.args.erase(it); - } - } - - if (gline.has('E') && this->_config.use_relative_e_distances) - this->E = 0; - - if (callback) callback(*this, gline); - - // update coordinates - if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { - this->X = gline.new_X(); - this->Y = gline.new_Y(); - this->Z = gline.new_Z(); - this->E = gline.new_E(); - this->F = gline.new_F(); + while (std::getline(ss, line)) + this->parse_line(line, callback); +} + +void +GCodeReader::parse_line(std::string line, callback_t callback) +{ + GCodeLine gline(this); + gline.raw = line; + if (this->verbose) + std::cout << line << std::endl; + + // strip comment + { + size_t pos = line.find(';'); + if (pos != std::string::npos) { + gline.comment = line.substr(pos+1); + line.erase(pos); } } + + // command and args + { + std::vector args; + boost::split(args, line, boost::is_any_of(" ")); + + // first one is cmd + gline.cmd = args.front(); + args.erase(args.begin()); + + for (std::string &arg : args) { + if (arg.size() < 2) continue; + gline.args.insert(std::make_pair(arg[0], arg.substr(1))); + } + } + + // convert extrusion axis + if (this->_extrusion_axis != 'E') { + const auto it = gline.args.find(this->_extrusion_axis); + if (it != gline.args.end()) { + std::swap(gline.args['E'], it->second); + gline.args.erase(it); + } + } + + if (gline.has('E') && this->_config.use_relative_e_distances) + this->E = 0; + + if (callback) callback(*this, gline); + + // update coordinates + if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { + this->X = gline.new_X(); + this->Y = gline.new_Y(); + this->Z = gline.new_Z(); + this->E = gline.new_E(); + this->F = gline.new_F(); + } +} + +void +GCodeReader::parse_file(const std::string &file, callback_t callback) +{ + std::ifstream f(file); + std::string line; + while (std::getline(f, line)) + this->parse_line(line, callback); } void diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 267ac0e18..c627fc851 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -25,6 +25,7 @@ class GCodeReader { GCodeLine(GCodeReader* _reader) : reader(_reader) {}; bool has(char arg) const { return this->args.count(arg) > 0; }; + float get_float(char arg) const { return atof(this->args.at(arg).c_str()); }; float new_X() const { return this->has('X') ? atof(this->args.at('X').c_str()) : this->reader->X; }; float new_Y() const { return this->has('Y') ? atof(this->args.at('Y').c_str()) : this->reader->Y; }; float new_Z() const { return this->has('Z') ? atof(this->args.at('Z').c_str()) : this->reader->Z; }; @@ -44,7 +45,7 @@ class GCodeReader { bool travel() const { return this->cmd == "G1" && !this->has('E'); }; void set(char arg, std::string value); }; - typedef std::function callback_t; + typedef std::function callback_t; float X, Y, Z, E, F; bool verbose; @@ -53,6 +54,8 @@ class GCodeReader { GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {}; void apply_config(const PrintConfigBase &config); void parse(const std::string &gcode, callback_t callback); + void parse_line(std::string line, callback_t callback); + void parse_file(const std::string &file, callback_t callback); private: GCodeConfig _config; diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp new file mode 100644 index 000000000..c6fa353b4 --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -0,0 +1,78 @@ +#include "GCodeTimeEstimator.hpp" +#include +#include + +namespace Slic3r { + +void +GCodeTimeEstimator::parse(const std::string &gcode) +{ + GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +void +GCodeTimeEstimator::parse_file(const std::string &file) +{ + GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +void +GCodeTimeEstimator::_parser(GCodeReader&, const GCodeReader::GCodeLine &line) +{ + // std::cout << "[" << this->time << "] " << line.raw << std::endl; + if (line.cmd == "G1") { + const float dist_XY = line.dist_XY(); + const float new_F = line.new_F(); + + if (dist_XY > 0) { + //this->time += dist_XY / new_F * 60; + this->time += _accelerated_move(dist_XY, new_F/60, this->acceleration); + } else { + //this->time += std::abs(line.dist_E()) / new_F * 60; + this->time += _accelerated_move(std::abs(line.dist_E()), new_F/60, this->acceleration); + } + //this->time += std::abs(line.dist_Z()) / new_F * 60; + this->time += _accelerated_move(std::abs(line.dist_Z()), new_F/60, this->acceleration); + } else if (line.cmd == "M204" && line.has('S')) { + this->acceleration = line.get_float('S'); + } else if (line.cmd == "G4") { // swell + if (line.has('S')) { + this->time += line.get_float('S'); + } else if (line.has('P')) { + this->time += line.get_float('P')/1000; + } + } +} + +// Wildly optimistic acceleration "bell" curve modeling. +// Returns an estimate of how long the move with a given accel +// takes in seconds. +// It is assumed that the movement is smooth and uniform. +float +GCodeTimeEstimator::_accelerated_move(double length, double v, double acceleration) +{ + // for half of the move, there are 2 zones, where the speed is increasing/decreasing and + // where the speed is constant. + // Since the slowdown is assumed to be uniform, calculate the average velocity for half of the + // expected displacement. + // final velocity v = a*t => a * (dx / 0.5v) => v^2 = 2*a*dx + // v_avg = 0.5v => 2*v_avg = v + // d_x = v_avg*t => t = d_x / v_avg + acceleration = (acceleration == 0.0 ? 4000.0 : acceleration); // Set a default accel to use for print time in case it's 0 somehow. + auto half_length = length / 2.0; + auto t_init = v / acceleration; // time to final velocity + auto dx_init = (0.5*v*t_init); // Initial displacement for the time to get to final velocity + auto t = 0.0; + if (half_length >= dx_init) { + half_length -= (0.5*v*t_init); + t += t_init; + t += (half_length / v); // rest of time is at constant speed. + } else { + // If too much displacement for the expected final velocity, we don't hit the max, so reduce + // the average velocity to fit the displacement we actually are looking for. + t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration; + } + return 2.0*t; // cut in half before, so double to get full time spent. +} + +} diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp new file mode 100644 index 000000000..dd301c929 --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_GCodeTimeEstimator_hpp_ +#define slic3r_GCodeTimeEstimator_hpp_ + +#include "libslic3r.h" +#include "GCodeReader.hpp" + +namespace Slic3r { + +class GCodeTimeEstimator : public GCodeReader { + public: + float time = 0; // in seconds + + void parse(const std::string &gcode); + void parse_file(const std::string &file); + + protected: + float acceleration = 9000; + void _parser(GCodeReader&, const GCodeReader::GCodeLine &line); + static float _accelerated_move(double length, double v, double acceleration); +}; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeTimeEstimator_hpp_ */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 9ce012a36..a60de7252 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -19,6 +19,7 @@ REGISTER_CLASS(SpiralVase, "GCode::SpiralVase"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); +REGISTER_CLASS(GCodeTimeEstimator, "GCode::TimeEstimator"); REGISTER_CLASS(GCodeWriter, "GCode::Writer"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); diff --git a/xs/xsp/GCodeTimeEstimator.xsp b/xs/xsp/GCodeTimeEstimator.xsp new file mode 100644 index 000000000..9489ff917 --- /dev/null +++ b/xs/xsp/GCodeTimeEstimator.xsp @@ -0,0 +1,15 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/GCodeTimeEstimator.hpp" +%} + +%name{Slic3r::GCode::TimeEstimator} class GCodeTimeEstimator { + GCodeTimeEstimator(); + ~GCodeTimeEstimator(); + + float time %get{time}; + void parse(std::string gcode); + void parse_file(std::string file); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index c47d79e00..b1f026eaa 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -214,6 +214,10 @@ GCodeSender* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +GCodeTimeEstimator* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCodeWriter* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T From 116cfefb086b07e0383f82b3dda52bb054ecde04 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 00:31:29 +0100 Subject: [PATCH 21/68] Fix serial connection on Linux --- xs/src/libslic3r/GCodeSender.cpp | 87 +++++++++++++++++++------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index c1568790e..951662765 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -8,16 +8,36 @@ #include #include -#if defined(__APPLE__) || defined(__linux) || defined(__OpenBSD__) +#if defined(__APPLE__) || defined(__OpenBSD__) #include #endif -#if __APPLE__ +#ifdef __APPLE__ #include #include #endif -#ifdef __linux +#ifdef __linux__ #include -#include +#include +#include "/usr/include/asm-generic/ioctls.h" + +/* The following definitions are kindly borrowed from: + /usr/include/asm-generic/termbits.h + Unfortunately we cannot just include that one because + it would redefine the "struct termios" already defined + the already included by Boost.ASIO. */ +#define K_NCCS 19 +struct termios2 { + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_cflag; + tcflag_t c_lflag; + cc_t c_line; + cc_t c_cc[K_NCCS]; + speed_t c_ispeed; + speed_t c_ospeed; +}; +#define BOTHER CBAUDEX + #endif //#define DEBUG_SERIAL @@ -65,7 +85,6 @@ GCodeSender::connect(std::string devname, unsigned int baud_rate) this->open = true; this->reset(); } catch (boost::system::system_error &e) { - printf("Caught error\n"); this->set_error_status(true); return false; } @@ -107,27 +126,15 @@ GCodeSender::set_baud_rate(unsigned int baud_rate) ioctl(handle, IOSSIOSPEED, &newSpeed); ::tcsetattr(handle, TCSANOW, &ios); #elif __linux - termios ios; - ::tcgetattr(handle, &ios); - ::cfsetispeed(&ios, B38400); - ::cfsetospeed(&ios, B38400); - ::tcflush(handle, TCIFLUSH); - ::tcsetattr(handle, TCSANOW, &ios); - - struct serial_struct ss; - ioctl(handle, TIOCGSERIAL, &ss); - ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST; - ss.custom_divisor = (ss.baud_base + (baud_rate / 2)) / baud_rate; - //cout << "bbase " << ss.baud_base << " div " << ss.custom_divisor; - long closestSpeed = ss.baud_base / ss.custom_divisor; - //cout << " Closest speed " << closestSpeed << endl; - ss.reserved_char[0] = 0; - if (closestSpeed < baud_rate * 98 / 100 || closestSpeed > baud_rate * 102 / 100) { - printf("Failed to set baud rate\n"); - } - - ioctl(handle, TIOCSSERIAL, &ss); - printf("< set_baud_rate: %u\n", baud_rate); + termios2 ios; + if (ioctl(handle, TCGETS2, &ios)) + printf("Error in TCGETS2: %s\n", strerror(errno)); + ios.c_ispeed = ios.c_ospeed = baud_rate; + ios.c_cflag &= ~CBAUD; + ios.c_cflag |= BOTHER; + if (ioctl(handle, TCSETS2, &ios)) + printf("Error in TCSETS2: %s\n", strerror(errno)); + #elif __OpenBSD__ struct termios ios; ::tcgetattr(handle, &ios); @@ -296,17 +303,27 @@ GCodeSender::on_read(const boost::system::error_code& error, { this->set_error_status(false); if (error) { - if (error.value() == 45) { + #ifdef __linux__ + if (error.value() == 2) { + this->do_read(); + return; + } + #endif + + #ifdef __APPLE__ + if (error.value() == 45 || ) { // OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html this->do_read(); - } else { - // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str()); - // error can be true even because the serial port was closed. - // In this case it is not a real error, so ignore. - if (this->open) { - this->do_close(); - this->set_error_status(true); - } + return; + } + #endif + + // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str()); + // error can be true even because the serial port was closed. + // In this case it is not a real error, so ignore. + if (this->open) { + this->do_close(); + this->set_error_status(true); } return; } From 5f9adf8922cedb0e6a4aecd9d75426cdd2b53881 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 13:23:33 +0100 Subject: [PATCH 22/68] Workaround for eof errors on Linux --- xs/src/libslic3r/GCodeSender.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index 951662765..646fdb4bd 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -131,7 +131,9 @@ GCodeSender::set_baud_rate(unsigned int baud_rate) printf("Error in TCGETS2: %s\n", strerror(errno)); ios.c_ispeed = ios.c_ospeed = baud_rate; ios.c_cflag &= ~CBAUD; - ios.c_cflag |= BOTHER; + ios.c_cflag |= BOTHER | CLOCAL | CREAD; + ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read + ios.c_cc[VTIME] = 1; if (ioctl(handle, TCSETS2, &ios)) printf("Error in TCSETS2: %s\n", strerror(errno)); @@ -303,13 +305,6 @@ GCodeSender::on_read(const boost::system::error_code& error, { this->set_error_status(false); if (error) { - #ifdef __linux__ - if (error.value() == 2) { - this->do_read(); - return; - } - #endif - #ifdef __APPLE__ if (error.value() == 45 || ) { // OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html From 67f0fe334406d159de5ff3c2094b22be676c8fdc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 13:57:43 +0100 Subject: [PATCH 23/68] Removed duplicated console log in manual control dialog; process Enter key in manual commands; append outgoing commands to log --- .../GUI/Controller/ManualControlDialog.pm | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm index 37dec1634..9d2fb5cc6 100644 --- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm +++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm @@ -1,5 +1,3 @@ -# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected? - package Slic3r::GUI::Controller::ManualControlDialog; use strict; use warnings; @@ -9,7 +7,7 @@ use Scalar::Util qw(looks_like_number); use Slic3r::Geometry qw(PI X Y unscale); use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap :textctrl wxBORDER_NONE wxTAB_TRAVERSAL); -use Wx::Event qw(EVT_CLOSE EVT_BUTTON); +use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_TEXT_ENTER); use base qw(Wx::Dialog Class::Accessor); __PACKAGE__->mk_accessors(qw(sender writer config2 x_homed y_homed)); @@ -163,6 +161,7 @@ sub new { return; } my $cmd = $self->writer->set_temperature($self->config2->{temperature}); + $self->GetParent->append_to_log(">> $cmd\n"); $self->sender->send($cmd, 1); }); $optgroup->append_line($line); @@ -182,6 +181,7 @@ sub new { return; } my $cmd = $self->writer->set_bed_temperature($self->config2->{bed_temperature}); + $self->GetParent->append_to_log(">> $cmd\n"); $self->sender->send($cmd, 1); }); $optgroup->append_line($line); @@ -189,19 +189,12 @@ sub new { } { - my $box = Wx::StaticBox->new($self, -1, "Console"); + my $box = Wx::StaticBox->new($self, -1, "Send manual command"); my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - $right_sizer->Add($sbsizer, 1, wxEXPAND, 0); - - my $log = $self->{log_textctrl} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize, - wxTE_MULTILINE | wxBORDER_NONE); - $log->SetBackgroundColour($self->GetBackgroundColour); - $log->SetFont($Slic3r::GUI::small_font); - $log->SetEditable(0); - $sbsizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0); + $right_sizer->Add($sbsizer, 1, wxEXPAND | wxALL, 10); my $cmd_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - my $cmd_textctrl = Wx::TextCtrl->new($self, -1, ''); + my $cmd_textctrl = Wx::TextCtrl->new($self, -1, '', wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); $cmd_sizer->Add($cmd_textctrl, 1, wxEXPAND, 0); my $btn = Wx::Button->new($self, -1, @@ -212,13 +205,17 @@ sub new { } $cmd_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 5); - EVT_BUTTON($self, $btn, sub { - return if $cmd_textctrl->GetValue eq ''; - $self->sender->send($cmd_textctrl->GetValue, 1); + my $do_send = sub { + my $cmd = $cmd_textctro->GetValue; + return if cmd eq ''; + $self->GetParent->append_to_log(">> $cmd\n"); + $self->sender->send($cmd, 1); $cmd_textctrl->SetValue(''); - }); + }; + EVT_BUTTON($self, $btn, $do_send); + EVT_TEXT_ENTER($self, $cmd_textctrl, $do_send); - $sbsizer->Add($cmd_sizer, 0, wxEXPAND | wxTOP, 2); + $sbsizer->Add($cmd_sizer, 0, wxEXPAND | wxTOP, 5); } my $main_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -239,12 +236,6 @@ sub new { return $self; } -sub update_log { - my ($self, $log) = @_; - - $self->{log_textctrl}->SetValue($log); -} - sub abs_xy_move { my ($self, $pos) = @_; From 42e9ec90bef32c5443b6fbb4d00bcb1cffd8677e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 13:59:29 +0100 Subject: [PATCH 24/68] Updated log method calls --- lib/Slic3r/GUI/Controller/PrinterPanel.pm | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm index 53850a69e..6a28e2ace 100644 --- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm +++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm @@ -43,9 +43,7 @@ sub new { $self->print_completed; } } - $self->{log_textctrl}->AppendText("$_\n") for @{$self->sender->purge_log}; - $self->{manual_control_dialog}->update_log($self->{log_textctrl}->GetValue) - if $self->{manual_control_dialog}; + $self->append_to_log("$_\n") for @{$self->sender->purge_log}; { my $temp = $self->sender->getT; if ($temp eq '') { @@ -272,6 +270,12 @@ sub new { return $self; } +sub append_to_log { + my ($self, $text) = @_; + + $self->{log_textctrl}->AppendText($text); +} + sub is_connected { my ($self) = @_; return $self->sender && $self->sender->is_connected; @@ -409,9 +413,9 @@ sub print_job { $self->Layout; $self->set_status('Printing...'); - $self->{log_textctrl}->AppendText(sprintf "=====\n"); - $self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name); - $self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp); + $self->append_to_log(sprintf "=====\n"); + $self->append_to_log(sprintf "Printing %s\n", $job->name); + $self->append_to_log(sprintf "Print started at %s\n", $self->_timestamp); } sub print_completed { @@ -426,7 +430,7 @@ sub print_completed { $self->Layout; $self->set_status('Print completed.'); - $self->{log_textctrl}->AppendText(sprintf "Print completed at %s\n", $self->_timestamp); + $self->append_to_log(sprintf "Print completed at %s\n", $self->_timestamp); $self->reload_jobs; } @@ -479,7 +483,7 @@ sub reload_jobs { $self->{gauge}->Disable; $self->{gauge}->Hide; $self->set_status('Print was aborted.'); - $self->{log_textctrl}->AppendText(sprintf "Print aborted at %s\n", $self->_timestamp); + $self->append_to_log(sprintf "Print aborted at %s\n", $self->_timestamp); }); $panel->on_resume_print(sub { my ($job) = @_; From d77469c6582d694624f4d1a949509a61ff0fb4ba Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 14:00:21 +0100 Subject: [PATCH 25/68] Update Slic3r::GCode::Sender API --- utils/send-gcode.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/send-gcode.pl b/utils/send-gcode.pl index 0b803baa6..d87e6826a 100644 --- a/utils/send-gcode.pl +++ b/utils/send-gcode.pl @@ -14,7 +14,8 @@ use Slic3r; die "Usage: send-gcode.pl SERIALPORT BAUDRATE GCODE_FILE\n" if @ARGV != 3; -my $serial = Slic3r::GCode::Sender->new($ARGV[0], $ARGV[1]); +my $serial = Slic3r::GCode::Sender->new; +$serial->connect($ARGV[0], $ARGV[1]); 1 until $serial->is_connected; print "Connected to printer\n"; From 9a1dc1a35e0aab8ece8e5ca90e1b0830102157b0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 14:03:00 +0100 Subject: [PATCH 26/68] Typos --- lib/Slic3r/GUI/Controller/ManualControlDialog.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm index 9d2fb5cc6..424ca3d73 100644 --- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm +++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm @@ -206,8 +206,8 @@ sub new { $cmd_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 5); my $do_send = sub { - my $cmd = $cmd_textctro->GetValue; - return if cmd eq ''; + my $cmd = $cmd_textctrl->GetValue; + return if $cmd eq ''; $self->GetParent->append_to_log(">> $cmd\n"); $self->sender->send($cmd, 1); $cmd_textctrl->SetValue(''); From 5fc5bf06db6f318593736838b842a19868d13b27 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 14:09:05 +0100 Subject: [PATCH 27/68] Smarter BOOST_DIR --- xs/Build.PL | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xs/Build.PL b/xs/Build.PL index 619cc879f..2fc5b33c1 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -70,6 +70,9 @@ if (defined $ENV{BOOST_INCLUDEDIR}) { my $subdir = $ENV{BOOST_DIR} . (($mswin == 1) ? '\include' : '/include'); if (-d $subdir) { push @boost_include, $subdir; + } elsif (-d "../$subdir") { + # User might have provided a path relative to the Build.PL in the main directory + push @boost_include, "../$subdir"; } else { push @boost_include, $ENV{BOOST_DIR}; } @@ -99,6 +102,9 @@ if (defined $ENV{BOOST_LIBRARYPATH}) { my $subdir = $ENV{BOOST_DIR} . ($mswin ? '\stage\lib' : '/stage/lib'); if (-d $subdir) { push @boost_libs, $subdir; + } elsif (-d "../$subdir") { + # User might have provided a path relative to the Build.PL in the main directory + push @boost_libs, "../$subdir"; } else { push @boost_libs, $ENV{BOOST_DIR}; } From 684b8de2364fbc880551b2c26406c4ce353209b7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 15:24:06 +0100 Subject: [PATCH 28/68] Use exec for running the OSX binary --- package/osx/startup_script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/osx/startup_script.sh b/package/osx/startup_script.sh index 4f665ac64..c7fd92d2b 100644 --- a/package/osx/startup_script.sh +++ b/package/osx/startup_script.sh @@ -1,4 +1,4 @@ #!/bin/bash DIR=$(dirname "$0") -"$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@ +exec "$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@ From b2bf08697ae9977fa20c1afc92eb9a0c51279a06 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 15:31:21 +0100 Subject: [PATCH 29/68] Typo --- xs/src/libslic3r/GCodeSender.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index 646fdb4bd..bb91a84c1 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -306,7 +306,7 @@ GCodeSender::on_read(const boost::system::error_code& error, this->set_error_status(false); if (error) { #ifdef __APPLE__ - if (error.value() == 45 || ) { + if (error.value() == 45) { // OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html this->do_read(); return; From a267af2e1aecacce5907d6480e045ce9126216d2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 19:09:18 +0100 Subject: [PATCH 30/68] Keep overrides added on the fly when opening and closing the preset editor dialog. #3800 --- lib/Slic3r/GUI/Plater.pm | 44 ++++++++++++++----- .../GUI/Plater/OverrideSettingsPanel.pm | 6 +++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 355c075c6..4d886893f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -526,10 +526,11 @@ sub _on_select_preset { my ($self, $group) = @_; my @presets = $self->selected_presets($group); - - $Slic3r::GUI::Settings->{presets}{$group} = $presets[0]->name; - $Slic3r::GUI::Settings->{presets}{"${group}_${_}"} = $presets[$_]->name - for 1..$#presets; + + my $s_presets = $Slic3r::GUI::Settings->{presets}; + my $changed = !$s_presets->{$group} || $s_presets->{$group} ne $presets[0]->name; + $s_presets->{$group} = $presets[0]->name; + $s_presets->{"${group}_${_}"} = $presets[$_]->name for 1..$#presets; wxTheApp->save_settings; @@ -538,15 +539,34 @@ sub _on_select_preset { $self->on_extruders_change(scalar @{$config->get('nozzle_diameter')}); if ($group eq 'print') { - $self->{settings_override_config}->clear; - my $overridable = $config->get('overridable'); - if ($overridable) { - $self->{settings_override_panel}->set_default_config($config); - $self->{settings_override_panel}->set_fixed_options(\@$overridable); - $self->{settings_override_config}->set($_, $config->get($_)) - for @$overridable; + my $o_config = $self->{settings_override_config}; + my $o_panel = $self->{settings_override_panel}; + + if ($changed) { + # Preserve current options if re-selecting the same preset + $o_config->clear; } - $self->{settings_override_panel}->update_optgroup; + + my $overridable = $config->get('overridable'); + + # Add/remove options (we do it this way for preserving current options) + foreach my $opt_key (@$overridable) { + if (!$o_config->has($opt_key)) { + # Populate option with the default value taken from configuration + $o_config->set($opt_key, $config->get($opt_key)); + } + } + foreach my $opt_key (@{$o_config->get_keys}) { + # Keep options listed among overridable and options added on the fly + if ((none { $_ eq $opt_key } @$overridable) + && (any { $_ eq $opt_key } $o_panel->fixed_options)) { + $o_config->erase($opt_key); + } + } + + $o_panel->set_default_config($config); + $o_panel->set_fixed_options(\@$overridable); + $o_panel->update_optgroup; } elsif ($group eq 'printer') { # reload print and filament settings to honor their compatible_printer options $self->load_presets; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 781e0fb6d..8e5e6b52b 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -131,6 +131,12 @@ sub set_fixed_options { $self->update_optgroup; } +sub fixed_options { + my ($self) = @_; + + return keys %{$self->{fixed_options}}; +} + sub update_optgroup { my $self = shift; From 93c298f7ac77aca673e7a6a4826a271214335be4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 19:18:41 +0100 Subject: [PATCH 31/68] Include all Print options in the overridable options list. #3770 --- lib/Slic3r/GUI/PresetEditor.pm | 5 +++-- xs/src/libslic3r/PrintConfig.cpp | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 1251532a3..66cc4df23 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -452,9 +452,10 @@ sub build { my $overridable_widget = sub { my ($parent) = @_; + my $Options = $Slic3r::Config::Options; my %options = ( - map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } - map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new + map { $_ => sprintf('%s > %s', $Options->{$_}{category}, $Options->{$_}{full_label} // $Options->{$_}{label}) } + grep { exists $Options->{$_} && $Options->{$_}{category} } $self->options ); my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options; $self->{overridable_opt_keys} = [ @opt_keys ]; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 08f441ffd..4bd2bc8b6 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -144,6 +144,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; + def->category = "Advanced"; def->tooltip = "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware."; def->cli = "complete-objects!"; def->default_value = new ConfigOptionBool(false); From 2fc8152e24075dae96e09283fb548c2d82924fe2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Mar 2017 19:25:32 +0100 Subject: [PATCH 32/68] Add 57600 baud. #3782 --- lib/Slic3r/GUI/Controller/PrinterPanel.pm | 2 +- xs/src/libslic3r/PrintConfig.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm index 6a28e2ace..9e2dc211d 100644 --- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm +++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm @@ -126,7 +126,7 @@ sub new { my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL); { $self->{serial_speed_combobox} = Wx::ComboBox->new($self, -1, $config->serial_speed, wxDefaultPosition, wxDefaultSize, - ["115200", "250000"]); + ["57600", "115200", "250000"]); $self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font); $serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0); } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index c1b5870f9..2ab6ad89e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1069,6 +1069,7 @@ PrintConfigDef::PrintConfigDef() def->cli = "serial-speed=i"; def->min = 1; def->max = 300000; + def->enum_values.push_back("57600"); def->enum_values.push_back("115200"); def->enum_values.push_back("250000"); def->default_value = new ConfigOptionInt(250000); From 5f30ab2f0d80b528a25bd987d77dc9b34e0f03e9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 04:18:06 +0200 Subject: [PATCH 33/68] Make N 0-based --- xs/src/libslic3r/GCodeSender.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index bb91a84c1..c880ee408 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -355,6 +355,7 @@ GCodeSender::on_read(const boost::system::error_code& error, // extract the first number from line boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit()); size_t toresend = boost::lexical_cast(line.substr(0, line.find_first_not_of("0123456789"))); + toresend++; // N is 0-based if (toresend >= this->sent - this->last_sent.size()) { { boost::lock_guard l(this->queue_mutex); @@ -473,8 +474,8 @@ GCodeSender::do_send() if (line.empty()) return; // compute full line - this->sent++; std::string full_line = "N" + boost::lexical_cast(this->sent) + " " + line; + this->sent++; // calculate checksum int cs = 0; From 426e79c65411cdb789da3d2f8190354dee1a9a64 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 15:00:39 +0200 Subject: [PATCH 34/68] Use shift key instead of alt for the OctoPrint shortcut. #3789 --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d43f1cf52..55e331f38 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -256,7 +256,7 @@ sub new { EVT_LEFT_UP($self->{btn_send_gcode}, sub { my (undef, $e) = @_; - my $alt = $e->AltDown; + my $alt = $e->ShiftDown; wxTheApp->CallAfter(sub { $self->prepare_send($alt); }); From 5473cc53a9ab0bb4389efe6ab710f7dbba265f23 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 16:03:27 +0200 Subject: [PATCH 35/68] Use a wxChoice to list/select objects in the plater. #3770 --- lib/Slic3r/GUI/Plater.pm | 43 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d6589da13..4f4c3b204 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -381,6 +381,27 @@ sub new { my $box = Wx::StaticBox->new($self, -1, "Info"); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([350,-1]); + + { + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $object_info_sizer->Add($sizer, 0, wxEXPAND | wxBOTTOM, 5); + my $text = Wx::StaticText->new($self, -1, "Object:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $text->SetFont($Slic3r::GUI::small_font); + $sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL); + + # We supply a bogus width to wxChoice (sizer will override it and stretch + # the control anyway), because if we leave the default (-1) it will stretch + # too much according to the contents, and this is bad with long file names. + $self->{object_info_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [100,-1], []); + $self->{object_info_choice}->SetFont($Slic3r::GUI::small_font); + $sizer->Add($self->{object_info_choice}, 1, wxALIGN_CENTER_VERTICAL); + + EVT_CHOICE($self, $self->{object_info_choice}, sub { + $self->select_object($self->{object_info_choice}->GetSelection); + $self->refresh_canvases; + }); + } + my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); @@ -388,7 +409,6 @@ sub new { $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( - name => "Name", copies => "Copies", size => "Size", volume => "Volume", @@ -1927,6 +1947,21 @@ sub on_model_change { } } + # reload the objects info choice + if (my $choice = $self->{object_info_choice}) { + $choice->Clear; + for my $i (0..$#{$self->{objects}}) { + my $name = $self->{objects}->[$i]->name; + my $count = $self->{model}->get_object($i)->instances_count; + if ($count > 1) { + $name .= " (${count}x)"; + } + $choice->Append($name); + } + my ($obj_idx, $object) = $self->selected_object; + $choice->SetSelection($obj_idx // -1); + } + my $running = $self->pause_background_process; if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { @@ -2132,9 +2167,10 @@ sub selection_changed { } if ($self->{object_info_size}) { # have we already loaded the info pane? + if ($have_sel) { - $self->{object_info_name}->SetLabel($object->name); my $model_object = $self->{model}->objects->[$obj_idx]; + $self->{object_info_choice}->SetSelection($obj_idx); $self->{object_info_copies}->SetLabel($model_object->instances_count); my $model_instance = $model_object->instances->[0]; { @@ -2168,7 +2204,8 @@ sub selection_changed { $self->{object_info_facets}->SetLabel($object->facets); } } else { - $self->{"object_info_$_"}->SetLabel("") for qw(name copies size volume facets materials manifold); + $self->{object_info_choice}->SetSelection(-1); + $self->{"object_info_$_"}->SetLabel("") for qw(copies size volume facets materials manifold); $self->{object_info_manifold_warning_icon}->Hide; $self->{object_info_manifold}->SetToolTipString(""); } From 35758819f25830343c171f557eed7e0570c81a94 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 20:50:25 +0200 Subject: [PATCH 36/68] New Select Next and Select Prev menu items. #3770 --- lib/Slic3r/GUI/MainFrame.pm | 6 ++++++ lib/Slic3r/GUI/Plater.pm | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index f779de305..b1de58b8b 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -181,6 +181,12 @@ sub _init_menubar { my $selectMenuItem = $self->{plater_menu}->AppendSubMenu($selectMenu, "Select", 'Select an object in the plater'); wxTheApp->set_menu_item_icon($selectMenuItem, 'brick.png'); } + $self->_append_menu_item($self->{plater_menu}, "Select Next Object\tCtrl+Right", 'Select Next Object in the plater', sub { + $plater->select_next; + }, undef, 'arrow_right.png'); + $self->_append_menu_item($self->{plater_menu}, "Select Prev Object\tCtrl+Left", 'Select Previous Object in the plater', sub { + $plater->select_prev; + }, undef, 'arrow_left.png'); $self->{plater_menu}->AppendSeparator(); $self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $plater->export_gcode; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4f4c3b204..d71ba7c57 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2226,6 +2226,34 @@ sub select_object { $self->selection_changed(1); } +sub select_next { + my ($self) = @_; + + return if !@{$self->{objects}}; + my ($obj_idx, $object) = $self->selected_object; + if (!defined $obj_idx || $obj_idx == $#{$self->{objects}}) { + $obj_idx = 0; + } else { + $obj_idx++; + } + $self->select_object($obj_idx); + $self->refresh_canvases; +} + +sub select_prev { + my ($self) = @_; + + return if !@{$self->{objects}}; + my ($obj_idx, $object) = $self->selected_object; + if (!defined $obj_idx || $obj_idx == 0) { + $obj_idx = $#{$self->{objects}}; + } else { + $obj_idx--; + } + $self->select_object($obj_idx); + $self->refresh_canvases; +} + sub selected_object { my $self = shift; From cc1ddc5770f9af52f04da7a01f0243065ff9e6fa Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 4 Oct 2016 13:54:10 +0200 Subject: [PATCH 37/68] A new feature "support_material_buildplate_only" implemented. Also a bug has been fixed for zero interface layers. Before slic3r would put infinite number of interface layers over top surfaces, if the number of interface layers was set to zero. --- lib/Slic3r/GUI/PresetEditor.pm | 3 +- lib/Slic3r/Print/SupportMaterial.pm | 64 ++++++++++++++++++++++++----- slic3r.pl | 2 + xs/src/libslic3r/PrintConfig.cpp | 7 ++++ xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/libslic3r/PrintObject.cpp | 1 + 6 files changed, 67 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 66cc4df23..c4c722f10 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -428,7 +428,7 @@ sub options { raft_layers support_material_pattern support_material_spacing support_material_angle support_material_interface_layers support_material_interface_spacing - support_material_contact_distance dont_support_bridges + support_material_contact_distance support_material_buildplate_only dont_support_bridges notes complete_objects extruder_clearance_radius extruder_clearance_height gcode_comments output_filename_format @@ -578,6 +578,7 @@ sub build { $optgroup->append_single_option_line('support_material_angle'); $optgroup->append_single_option_line('support_material_interface_layers'); $optgroup->append_single_option_line('support_material_interface_spacing'); + $optgroup->append_single_option_line('support_material_buildplate_only'); $optgroup->append_single_option_line('dont_support_bridges'); } } diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 02b2e5375..b830c3599 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -1,3 +1,5 @@ +# Instantiated by Slic3r::Print::Object->_support_material() +# only generate() and contact_distance() are called from the outside of this module. package Slic3r::Print::SupportMaterial; use Moo; @@ -25,6 +27,7 @@ use constant PILLAR_SIZE => 2.5; use constant PILLAR_SPACING => 10; sub generate { + # $object is Slic3r::Print::Object my ($self, $object) = @_; # Determine the top surfaces of the support, defined as: @@ -38,7 +41,7 @@ sub generate { # the layer heights of support material and to clip support to the object # silhouette. my ($top) = $self->object_top($object, $contact); - + # We now know the upper and lower boundaries for our support material object # (@$contact_z and @$top_z), so we can generate intermediate layers. my $support_z = $self->support_layers_z( @@ -88,6 +91,7 @@ sub generate { } sub contact_area { + # $object is Slic3r::Print::Object my ($self, $object) = @_; # if user specified a custom angle threshold, convert it to radians @@ -97,6 +101,12 @@ sub contact_area { Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } + # Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces + # and subtract $buildplate_only_top_surfaces from the contact surfaces, so + # there is no contact surface supported by a top surface. + my $buildplate_only = $self->object_config->support_material && $self->object_config->support_material_buildplate_only; + my $buildplate_only_top_surfaces = []; + # determine contact areas my %contact = (); # contact_z => [ polygons ] my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer @@ -113,7 +123,22 @@ sub contact_area { last if $layer_id > 0; } my $layer = $object->get_layer($layer_id); - + + if ($buildplate_only) { + # Collect the top surfaces up to this layer and merge them. + my $projection_new = []; + push @$projection_new, ( map $_->p, map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions} ); + if (@$projection_new) { + # Merge the new top surfaces with the preceding top surfaces. + # Apply the safety offset to the newly added polygons, so they will connect + # with the polygons collected before, + # but don't apply the safety offset during the union operation as it would + # inflate the polygons over and over. + push @$buildplate_only_top_surfaces, @{ offset($projection_new, scale(0.01)) }; + $buildplate_only_top_surfaces = union($buildplate_only_top_surfaces, 0); + } + } + # detect overhangs and contact areas needed to support them my (@overhang, @contact) = (); if ($layer_id == 0) { @@ -239,8 +264,14 @@ sub contact_area { 1, ); } + } # if ($self->object_config->dont_support_bridges) + + if ($buildplate_only) { + # Don't support overhangs above the top surfaces. + # This step is done before the contact surface is calcuated by growing the overhang region. + $diff = diff($diff, $buildplate_only_top_surfaces); } - + next if !@$diff; push @overhang, @$diff; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width! @@ -249,11 +280,16 @@ sub contact_area { # We increment the area in steps because we don't want our support to overflow # on the other side of the object (if it's very thin). { - my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], +$fw/2)}; + my $slices_margin = offset([ map @$_, @{$lower_layer->slices} ], +$fw/2); + if ($buildplate_only) { + # Trim the inflated contact surfaces by the top surfaces as well. + push @$slices_margin, map $_->clone, @{$buildplate_only_top_surfaces}; + $slices_margin = union($slices_margin); + } for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) { $diff = diff( offset($diff, $_), - \@slices_margin, + $slices_margin, ); } } @@ -280,14 +316,15 @@ sub contact_area { if (0) { require "Slic3r/SVG.pm"; - Slic3r::SVG::output("contact_" . $contact_z . ".svg", - expolygons => union_ex(\@contact), - red_expolygons => union_ex(\@overhang), + Slic3r::SVG::output("out\\contact_" . $contact_z . ".svg", + green_expolygons => union_ex($buildplate_only_top_surfaces), + blue_expolygons => union_ex(\@contact), + red_expolygons => union_ex(\@overhang), ); } } } - + return (\%contact, \%overhang); } @@ -297,6 +334,8 @@ sub object_top { # find object top surfaces # we'll use them to clip our support and detect where does it stick my %top = (); # print_z => [ expolygons ] + return \%top if ($self->object_config->support_material_buildplate_only); + my $projection = []; foreach my $layer (reverse @{$object->layers}) { if (my @top = map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions}) { @@ -429,6 +468,9 @@ sub generate_interface_layers { sub generate_bottom_interface_layers { my ($self, $support_z, $base, $top, $interface) = @_; + + # If no interface layers are allowed, don't generate bottom interface layers. + return if $self->object_config->support_material_interface_layers == 0; my $area_threshold = $self->interface_flow->scaled_spacing ** 2; @@ -721,8 +763,8 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, - ), @$p; - } + ), @p; + } $layer->support_interface_fills->append(@paths); } diff --git a/slic3r.pl b/slic3r.pl index 70f42aac0..1349df3e4 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -486,6 +486,8 @@ $j --support-material-enforce-layers Enforce support material on the specified number of layers from bottom, regardless of --support-material and threshold (0+, default: $config->{support_material_enforce_layers}) + --support-material-buildplate-only + Only create support if it lies on a build plate. Don't create support on a print. (default: no) --dont-support-bridges Experimental option for preventing support material from being generated under bridged areas (default: yes) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index c9fb48318..c76a3fa24 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1282,6 +1282,13 @@ PrintConfigDef::PrintConfigDef() def->max = 359; def->default_value = new ConfigOptionInt(0); + def = this->add("support_material_buildplate_only", coBool); + def->label = "Support on build plate only"; + def->category = "Support material"; + def->tooltip = "Only create support if it lies on a build plate. Don't create support on a print."; + def->cli = "support-material-buildplate-only!"; + def->default_value = new ConfigOptionBool(false); + def = this->add("support_material_contact_distance", coFloat); def->gui_type = "f_enum_open"; def->label = "Contact Z distance"; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 5cf7054bf..88890667b 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -153,6 +153,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionEnum seam_position; ConfigOptionBool support_material; ConfigOptionInt support_material_angle; + ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; @@ -183,6 +184,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig OPT_PTR(seam_position); OPT_PTR(support_material); OPT_PTR(support_material_angle); + OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); OPT_PTR(support_material_enforce_layers); OPT_PTR(support_material_extruder); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 1aebd2c37..f052e315a 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -240,6 +240,7 @@ PrintObject::invalidate_state_by_config_options(const std::vector Date: Sun, 26 Mar 2017 13:49:34 -0500 Subject: [PATCH 38/68] Use correct variable type. --- lib/Slic3r/Print/SupportMaterial.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index b830c3599..4314e4fa6 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -763,7 +763,7 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, - ), @p; + ), @$p; } $layer->support_interface_fills->append(@paths); From fc5fec13150278516cac371eb3db0d6dd2303e5e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 21:07:46 +0200 Subject: [PATCH 39/68] Always send M105 on connection in case firmware is silent. #3767 --- xs/src/libslic3r/GCodeSender.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index c880ee408..237009392 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -106,6 +106,9 @@ GCodeSender::connect(std::string devname, unsigned int baud_rate) boost::thread t(boost::bind(&asio::io_service::run, &this->io)); this->background_thread.swap(t); + // always send a M105 to check for connection because firmware might be silent on connect + this->send("M105", true); + return true; } From f9f8be46e37c8adac372dfc17abc20557dfe9313 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 26 Mar 2017 21:27:58 +0200 Subject: [PATCH 40/68] Populate the filament_preset placeholder with all the presets. #3577 --- lib/Slic3r/GUI/Plater.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d71ba7c57..536f8be64 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -656,6 +656,7 @@ sub load_presets { @sel = (0) if !@sel; # populate the wxChoice objects + my @preset_names = (); foreach my $choice (@choosers) { $choice->Clear; $self->{preset_choosers_names}{$choice} = []; @@ -691,11 +692,13 @@ sub load_presets { $choice->SetSelection($selected); my $preset_name = $self->{preset_choosers_names}{$choice}[$selected]; - $self->{print}->placeholder_parser->set("${group}_preset", $preset_name); + push @preset_names, $preset_name; # TODO: populate other filament preset placeholders $selected_printer_name = $preset_name if $group eq 'printer'; } } + + $self->{print}->placeholder_parser->set("${group}_preset", [ @preset_names ]); } } From 66df176447d36c546b3436f8d708048ac0e36575 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Sun, 26 Mar 2017 15:20:24 -0500 Subject: [PATCH 41/68] Abort loading if the file size is larger than the reported # of facets in the file header. --- xs/src/admesh/stlinit.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index a191e3d76..f83056621 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -131,6 +131,12 @@ stl_count_facets(stl_file *stl, const char *file) { if((!fread(&header_num_facets, sizeof(int), 1, stl->fp)) || (num_facets != header_num_facets)) { fprintf(stderr, "Warning: File size doesn't match number of facets in the header\n"); + + if(num_facets > header_num_facets) { + // this file is garbage. + stl->error = 1; + return; + } } } /* Otherwise, if the .STL file is ASCII, then do the following */ From 7ffb7605567ca58e47d8889dacfb6380a1e7c35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xoan=20Sampai=C3=B1o?= Date: Mon, 27 Mar 2017 15:44:29 +0200 Subject: [PATCH 42/68] Remove unused --no-plater option --- README.md | 1 - slic3r.pl | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index aebe1df8d..2a96554fc 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,6 @@ Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Le GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) - --no-plater Disable the plater tab --autosave Automatically export current configuration to the specified file Output options: diff --git a/slic3r.pl b/slic3r.pl index 1349df3e4..ed03c838a 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -326,7 +326,6 @@ $j GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) - --no-plater Disable the plater tab --autosave Automatically export current configuration to the specified file Output options: From 840cd2eda0db0393fa321a7694d1f5af83ae036d Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Mon, 27 Mar 2017 20:16:24 -0500 Subject: [PATCH 43/68] put the exception test back to the way it was (and it PASSES :D) --- xs/t/22_exception.t | 3 --- 1 file changed, 3 deletions(-) diff --git a/xs/t/22_exception.t b/xs/t/22_exception.t index b57255fa5..ca2ffea89 100644 --- a/xs/t/22_exception.t +++ b/xs/t/22_exception.t @@ -8,10 +8,7 @@ use Test::More tests => 1; { eval { - local $SIG{ALRM} = sub { die "Timed out waiting for exception\n" }; # NB: \n required - alarm 30; Slic3r::xspp_test_croak_hangs_on_strawberry(); - alarm 0; }; is $@, "xspp_test_croak_hangs_on_strawberry: exception catched\n", 'croak from inside a C++ exception delivered'; } From 63eadcef976d7bbee4b91d6d082af9ca3e4b8435 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Fri, 24 Mar 2017 13:10:06 -0500 Subject: [PATCH 44/68] force --gui for shell during compilation if -DFORCE_GUI is set --- package/win/shell.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/package/win/shell.cpp b/package/win/shell.cpp index b0904e9e6..3863befe2 100644 --- a/package/win/shell.cpp +++ b/package/win/shell.cpp @@ -23,7 +23,12 @@ int main(int argc, char **argv, char **env) char exe_path[MAX_PATH] = {0}; char script_path[MAX_PATH]; + char gui_flag[6] = {" --gui"}; +#ifdef FORCE_GUI + char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1+6)); +#else char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1)); +#endif { // Unicode path. This will not be used directly, but to test, whether // there are any non-ISO characters, in which case the path is converted to a @@ -67,8 +72,14 @@ int main(int argc, char **argv, char **env) _makepath(script_path, drive, dir, "slic3r", "pl"); command_line[0] = exe_path; command_line[1] = script_path; +#ifdef FORCE_GUI + command_line[2] = gui_flag; + memcpy(command_line + 3, argv + 1, sizeof(char*) * (argc - 2)); + command_line[argc+6] = NULL; +#else memcpy(command_line + 2, argv + 1, sizeof(char*) * (argc - 2)); command_line[argc] = NULL; +#endif // Unset the PERL5LIB and PERLLIB environment variables. SetEnvironmentVariable("PERL5LIB", NULL); SetEnvironmentVariable("PERLLIB", NULL); From 59f801d2ed29a8702a21391df7b5f7acb68a6e1d Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Fri, 24 Mar 2017 13:11:21 -0500 Subject: [PATCH 45/68] added compile flag for FORCE_GUI --- package/win/compile_wrapper.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/win/compile_wrapper.ps1 b/package/win/compile_wrapper.ps1 index fc6f15b97..5a2fc5819 100644 --- a/package/win/compile_wrapper.ps1 +++ b/package/win/compile_wrapper.ps1 @@ -11,5 +11,7 @@ $perllib = "-lperl$perlver" windres slic3r.rc -O coff -o slic3r.res g++ -c -I'C:\strawberry\perl\lib\CORE\' shell.cpp -o slic3r.o +g++ -c -I'C:\strawberry\perl\lib\CORE\' -DFORCE_GUI shell.cpp -o slic3r-gui.o g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r.o slic3r.res -o slic3r.exe | Write-Host +g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r-gui.o slic3r.res -o slic3r-gui.exe | Write-Host From 3534dc8084ce13e70de06b1067e72ba9fa771faf Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Fri, 24 Mar 2017 13:11:53 -0500 Subject: [PATCH 46/68] added slic3r-gui.exe --- package/win/package_win32.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1 index 70127d76b..ccffef3ae 100644 --- a/package/win/package_win32.ps1 +++ b/package/win/package_win32.ps1 @@ -44,6 +44,7 @@ cpanm "PAR::Packer" pp ` -a "slic3r.exe;slic3r.exe" ` +-a "slic3r.exe;slic3r-gui.exe" ` -a "../../lib;lib" ` -a "../../local-lib;local-lib" ` -a "../../slic3r.pl;slic3r.pl" ` From 8719dea4e53d1d2820b11d789bab23d553d7f41f Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Fri, 24 Mar 2017 13:16:46 -0500 Subject: [PATCH 47/68] change execs to slic3r-console and slic3r --- package/win/package_win32.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1 index ccffef3ae..f2e9ecbbd 100644 --- a/package/win/package_win32.ps1 +++ b/package/win/package_win32.ps1 @@ -44,7 +44,7 @@ cpanm "PAR::Packer" pp ` -a "slic3r.exe;slic3r.exe" ` --a "slic3r.exe;slic3r-gui.exe" ` +-a "slic3r.exe;slic3r-console.exe" ` -a "../../lib;lib" ` -a "../../local-lib;local-lib" ` -a "../../slic3r.pl;slic3r.pl" ` From 68ab876bec02477f147187fb8f7f3ad503571f85 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Fri, 24 Mar 2017 13:17:35 -0500 Subject: [PATCH 48/68] slic3r-console now doesn't have gui turned on by default, slic3r does --- package/win/compile_wrapper.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/win/compile_wrapper.ps1 b/package/win/compile_wrapper.ps1 index 5a2fc5819..9b6e0f2cb 100644 --- a/package/win/compile_wrapper.ps1 +++ b/package/win/compile_wrapper.ps1 @@ -12,6 +12,6 @@ $perllib = "-lperl$perlver" windres slic3r.rc -O coff -o slic3r.res g++ -c -I'C:\strawberry\perl\lib\CORE\' shell.cpp -o slic3r.o g++ -c -I'C:\strawberry\perl\lib\CORE\' -DFORCE_GUI shell.cpp -o slic3r-gui.o -g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r.o slic3r.res -o slic3r.exe | Write-Host -g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r-gui.o slic3r.res -o slic3r-gui.exe | Write-Host +g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r.o slic3r.res -o slic3r-console.exe | Write-Host +g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r-gui.o slic3r.res -o slic3r.exe | Write-Host From 8adeb2b5d0983f768167ba22d93ee425cdea897a Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Sat, 25 Mar 2017 23:37:16 -0500 Subject: [PATCH 49/68] Forgot to account for null character --- package/win/compile_wrapper.ps1 | 4 ++-- package/win/shell.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package/win/compile_wrapper.ps1 b/package/win/compile_wrapper.ps1 index 9b6e0f2cb..5704788ff 100644 --- a/package/win/compile_wrapper.ps1 +++ b/package/win/compile_wrapper.ps1 @@ -12,6 +12,6 @@ $perllib = "-lperl$perlver" windres slic3r.rc -O coff -o slic3r.res g++ -c -I'C:\strawberry\perl\lib\CORE\' shell.cpp -o slic3r.o g++ -c -I'C:\strawberry\perl\lib\CORE\' -DFORCE_GUI shell.cpp -o slic3r-gui.o -g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r.o slic3r.res -o slic3r-console.exe | Write-Host -g++ -v -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r-gui.o slic3r.res -o slic3r.exe | Write-Host +g++ -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r.o slic3r.res -o slic3r-console.exe | Write-Host +g++ -static-libgcc -static-libstdc++ -L'C:\strawberry\c\lib' -L'C:\strawberry\perl\bin' -L'C:\strawberry\perl\lib\CORE\' $perllib slic3r-gui.o slic3r.res -o slic3r.exe | Write-Host diff --git a/package/win/shell.cpp b/package/win/shell.cpp index 3863befe2..2808a2772 100644 --- a/package/win/shell.cpp +++ b/package/win/shell.cpp @@ -23,7 +23,7 @@ int main(int argc, char **argv, char **env) char exe_path[MAX_PATH] = {0}; char script_path[MAX_PATH]; - char gui_flag[6] = {" --gui"}; + char gui_flag[7] = {" --gui"}; #ifdef FORCE_GUI char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1+6)); #else From 00744c259cb835af7b3b992a93cd99aad5734f35 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Mon, 27 Mar 2017 21:46:06 -0500 Subject: [PATCH 50/68] Append the --gui flag if compiled with -DFORCE_GUI. --- package/win/shell.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/package/win/shell.cpp b/package/win/shell.cpp index 2808a2772..e4fe45d20 100644 --- a/package/win/shell.cpp +++ b/package/win/shell.cpp @@ -23,9 +23,9 @@ int main(int argc, char **argv, char **env) char exe_path[MAX_PATH] = {0}; char script_path[MAX_PATH]; - char gui_flag[7] = {" --gui"}; + char gui_flag[6] = {"--gui"}; #ifdef FORCE_GUI - char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1+6)); + char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 2)); #else char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1)); #endif @@ -72,12 +72,11 @@ int main(int argc, char **argv, char **env) _makepath(script_path, drive, dir, "slic3r", "pl"); command_line[0] = exe_path; command_line[1] = script_path; -#ifdef FORCE_GUI - command_line[2] = gui_flag; - memcpy(command_line + 3, argv + 1, sizeof(char*) * (argc - 2)); - command_line[argc+6] = NULL; -#else memcpy(command_line + 2, argv + 1, sizeof(char*) * (argc - 2)); +#ifdef FORCE_GUI + command_line[argc] = gui_flag; + command_line[argc+1] = NULL; +#else command_line[argc] = NULL; #endif // Unset the PERL5LIB and PERLLIB environment variables. @@ -89,7 +88,11 @@ int main(int argc, char **argv, char **env) printf(" %d: %s\r\n", i, command_line[i]); #endif } +#ifdef FORCE_GUI + RunPerl(argc+1, command_line, NULL); +#else RunPerl(argc, command_line, NULL); +#endif free(command_line); } From 41407f360d4389899187379fa5e0300774192e43 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Mon, 27 Mar 2017 22:51:57 -0500 Subject: [PATCH 51/68] Remove Sub::Name from package --- package/win/package_win32.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1 index 70127d76b..9c9bc3ed0 100644 --- a/package/win/package_win32.ps1 +++ b/package/win/package_win32.ps1 @@ -110,10 +110,8 @@ pp ` -M Socket ` -M Socket6 ` -M Storable ` --M Sub::Defer ` -M Sub::Exporter ` -M Sub::Exporter::Progressive ` --M Sub::Name ` -M Symbol ` -M Term::Cap ` -M Text::ParseWords ` From 9b4fe076a652864d54cd277ca06f40ce5cf94cec Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 28 Mar 2017 15:58:01 +0200 Subject: [PATCH 52/68] Workaround for detect_surfaces_type() not being idempotent and causing artifacts after multiple runs. #3764 --- lib/Slic3r/Print/Object.pm | 30 +++++++++++++++++++++++------- xs/src/libslic3r/Layer.cpp | 12 +++++++----- xs/src/libslic3r/Print.hpp | 4 ++-- xs/src/libslic3r/PrintObject.cpp | 18 +++++++++++++++++- xs/xsp/Print.xsp | 3 ++- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 937eb2e92..3e9f38d22 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -125,28 +125,44 @@ sub slice { sub make_perimeters { my ($self) = @_; + return if $self->step_done(STEP_PERIMETERS); + + # Temporary workaround for detect_surfaces_type() not being idempotent (see #3764). + # We can remove this when idempotence is restored. This make_perimeters() method + # will just call merge_slices() to undo the typed slices and invalidate posDetectSurfaces. + if ($self->typed_slices) { + $self->invalidate_step(STEP_SLICE); + } + # prerequisites $self->slice; $self->_make_perimeters; } +# This will assign a type (top/bottom/internal) to $layerm->slices +# and transform $layerm->fill_surfaces from expolygon +# to typed top/bottom/internal surfaces; +sub detect_surfaces_type { + my ($self) = @_; + + # prerequisites + $self->slice; + + $self->_detect_surfaces_type; +} + sub prepare_infill { my ($self) = @_; # prerequisites - $self->make_perimeters; + $self->make_perimeters; # do we need them? TODO: check + $self->detect_surfaces_type; return if $self->step_done(STEP_PREPARE_INFILL); $self->set_step_started(STEP_PREPARE_INFILL); $self->print->status_cb->(30, "Preparing infill"); - # this will assign a type (top/bottom/internal) to $layerm->slices - # and transform $layerm->fill_surfaces from expolygon - # to typed top/bottom/internal surfaces; - $self->detect_surfaces_type; - $self->set_typed_slices(1); - # decide what surfaces are to be filled $_->prepare_fill_surfaces for map @{$_->regions}, @{$self->layers}; diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 722b539e5..4837af9c0 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -197,11 +197,11 @@ Layer::make_perimeters() // merge the surfaces assigned to each group SurfaceCollection new_slices; - for (std::map::const_iterator it = slices.begin(); it != slices.end(); ++it) { - ExPolygons expp = union_ex(it->second, true); - for (ExPolygons::iterator ex = expp.begin(); ex != expp.end(); ++ex) { - Surface s = it->second.front(); // clone type and extra_perimeters - s.expolygon = *ex; + for (const auto &it : slices) { + ExPolygons expp = union_ex(it.second, true); + for (ExPolygon &ex : expp) { + Surface s = it.second.front(); // clone type and extra_perimeters + s.expolygon = ex; new_slices.surfaces.push_back(s); } } @@ -274,6 +274,8 @@ Layer::detect_surfaces_type() Layer* const &lower_layer = this->lower_layer; // collapse very narrow parts (using the safety offset in the diff is not enough) + // TODO: this offset2 makes this method not idempotent (see #3764), so we should + // move it to where we generate fill_surfaces instead and leave slices unaltered const float offs = layerm.flow(frExternalPerimeter).scaled_width() / 10.f; const Polygons layerm_slices_surfaces = layerm.slices; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index a76adde6a..18fc760ef 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -26,8 +26,8 @@ enum PrintStep { psSkirt, psBrim, }; enum PrintObjectStep { - posSlice, posPerimeters, posPrepareInfill, - posInfill, posSupportMaterial, + posSlice, posPerimeters, posDetectSurfaces, + posPrepareInfill, posInfill, posSupportMaterial, }; // To be instantiated over PrintStep or PrintObjectStep enums. diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 61e0cc7cd..0e752849f 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -311,6 +311,8 @@ PrintObject::invalidate_step(PrintObjectStep step) this->invalidate_step(posPrepareInfill); this->_print->invalidate_step(psSkirt); this->_print->invalidate_step(psBrim); + } else if (step == posDetectSurfaces) { + this->invalidate_step(posPrepareInfill); } else if (step == posPrepareInfill) { this->invalidate_step(posInfill); } else if (step == posInfill) { @@ -318,6 +320,7 @@ PrintObject::invalidate_step(PrintObjectStep step) this->_print->invalidate_step(psBrim); } else if (step == posSlice) { this->invalidate_step(posPerimeters); + this->invalidate_step(posDetectSurfaces); this->invalidate_step(posSupportMaterial); } else if (step == posSupportMaterial) { this->_print->invalidate_step(psSkirt); @@ -351,11 +354,20 @@ PrintObject::has_support_material() const void PrintObject::detect_surfaces_type() { + // prerequisites + // this->slice(); + + if (this->state.is_done(posDetectSurfaces)) return; + this->state.set_started(posDetectSurfaces); + parallelize( std::queue(std::deque(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue boost::bind(&Slic3r::Layer::detect_surfaces_type, _1), this->_print->config.threads.value ); + + this->typed_slices = true; + this->state.set_done(posDetectSurfaces); } void @@ -835,11 +847,15 @@ PrintObject::_make_perimeters() this->state.set_started(posPerimeters); // merge slices if they were split into types + // This is not currently taking place because since merge_slices + detect_surfaces_type + // are not truly idempotent we are invalidating posSlice here (see the Perl part of + // this method). if (this->typed_slices) { + // merge_slices() undoes detect_surfaces_type() FOREACH_LAYER(this, layer_it) (*layer_it)->merge_slices(); this->typed_slices = false; - this->state.invalidate(posPrepareInfill); + this->state.invalidate(posDetectSurfaces); } // compare each layer to the one below, and mark those slices needing diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index c35da378f..e9a4c1a6d 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -14,6 +14,7 @@ _constant() ALIAS: STEP_SLICE = posSlice STEP_PERIMETERS = posPerimeters + STEP_DETECT_SURFACES = posDetectSurfaces STEP_PREPARE_INFILL = posPrepareInfill STEP_INFILL = posInfill STEP_SUPPORTMATERIAL = posSupportMaterial @@ -123,7 +124,7 @@ _constant() void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; - void detect_surfaces_type(); + %name{_detect_surfaces_type} void detect_surfaces_type(); void process_external_surfaces(); void bridge_over_infill(); void _slice(); From 52a702d0f515054c2ae38d46f2c51ca78d146b51 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Tue, 28 Mar 2017 09:07:39 -0500 Subject: [PATCH 53/68] Package libgcc_seh instead of libgcc_sjlj --- package/win/package_win32.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1 index 1bc17e0f4..f2d7755b9 100644 --- a/package/win/package_win32.ps1 +++ b/package/win/package_win32.ps1 @@ -53,7 +53,7 @@ pp ` -a "../../FreeGLUT/freeglut.dll;freeglut.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\perl${perlversion}.dll;perl${perlversion}.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libstdc++-6.dll;libstdc++-6.dll" ` --a "${STRAWBERRY_PATH}\perl\bin\libgcc_s_sjlj-1.dll;libgcc_s_sjlj-1.dll" ` +-a "${STRAWBERRY_PATH}\perl\bin\libgcc_s_seh-1.dll;libgcc_s_seh-1.dll" ` -a "${STRAWBERRY_PATH}\c\bin\pthreadGC2-w64.dll;pthreadGC2-w64.dll" ` -a "${STRAWBERRY_PATH}\c\bin\libglut-0__.dll;libglut-0__.dll" ` -M AutoLoader ` From d3a91129bb36e9bc0f3d549da2389722f05b75f9 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Tue, 28 Mar 2017 10:11:35 -0500 Subject: [PATCH 54/68] Add libwinpthread-1.dll to package --- package/win/package_win32.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/package/win/package_win32.ps1 b/package/win/package_win32.ps1 index f2d7755b9..302eded3d 100644 --- a/package/win/package_win32.ps1 +++ b/package/win/package_win32.ps1 @@ -54,6 +54,7 @@ pp ` -a "${STRAWBERRY_PATH}\perl\bin\perl${perlversion}.dll;perl${perlversion}.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libstdc++-6.dll;libstdc++-6.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libgcc_s_seh-1.dll;libgcc_s_seh-1.dll" ` +-a "${STRAWBERRY_PATH}\perl\bin\libwinpthread-1.dll;libwinpthread-1.dll" ` -a "${STRAWBERRY_PATH}\c\bin\pthreadGC2-w64.dll;pthreadGC2-w64.dll" ` -a "${STRAWBERRY_PATH}\c\bin\libglut-0__.dll;libglut-0__.dll" ` -M AutoLoader ` From 85f84aa7dc052626b11d48f538847c6775c7a217 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 28 Mar 2017 17:36:58 +0200 Subject: [PATCH 55/68] Refactored the config/step invalidation code. Now we can take values into account in order to reduce unneeded recalculation --- xs/src/libslic3r/Config.cpp | 46 ++++--- xs/src/libslic3r/Config.hpp | 13 +- xs/src/libslic3r/Print.cpp | 217 +++++++++++++++---------------- xs/src/libslic3r/Print.hpp | 5 +- xs/src/libslic3r/PrintObject.cpp | 111 ++++++---------- xs/src/libslic3r/PrintRegion.cpp | 87 +++++++++++++ xs/xsp/Print.xsp | 2 - 7 files changed, 274 insertions(+), 207 deletions(-) diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index f18e4e025..68f7f7e2d 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -242,8 +242,8 @@ ConfigDef::merge(const ConfigDef &other) } bool -ConfigBase::has(const t_config_option_key &opt_key) { - return (this->option(opt_key, false) != NULL); +ConfigBase::has(const t_config_option_key &opt_key) const { + return this->option(opt_key) != NULL; } void @@ -272,21 +272,18 @@ ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &opt_ } bool -ConfigBase::equals(ConfigBase &other) { +ConfigBase::equals(const ConfigBase &other) const { return this->diff(other).empty(); } // this will *ignore* options not present in both configs t_config_option_keys -ConfigBase::diff(ConfigBase &other) { +ConfigBase::diff(const ConfigBase &other) const { t_config_option_keys diff; - t_config_option_keys my_keys = this->keys(); - for (t_config_option_keys::const_iterator opt_key = my_keys.begin(); opt_key != my_keys.end(); ++opt_key) { - if (other.has(*opt_key) && other.serialize(*opt_key) != this->serialize(*opt_key)) { - diff.push_back(*opt_key); - } - } + for (const t_config_option_key &opt_key : this->keys()) + if (other.has(opt_key) && other.serialize(opt_key) != this->serialize(opt_key)) + diff.push_back(opt_key); return diff; } @@ -391,6 +388,25 @@ ConfigBase::option(const t_config_option_key &opt_key, bool create) { return this->optptr(opt_key, create); } +/* +template +T* +ConfigBase::opt(const t_config_option_key &opt_key, bool create) { + return dynamic_cast(this->option(opt_key, create)); +} +template ConfigOptionInt* ConfigBase::opt(const t_config_option_key &opt_key, bool create); +template ConfigOptionBool* ConfigBase::opt(const t_config_option_key &opt_key, bool create); +template ConfigOptionBools* ConfigBase::opt(const t_config_option_key &opt_key, bool create); +template ConfigOptionPercent* ConfigBase::opt(const t_config_option_key &opt_key, bool create); + + +template +const T* +ConfigBase::opt(const t_config_option_key &opt_key) const { + return dynamic_cast(this->option(opt_key)); +} +*/ + void ConfigBase::load(const std::string &file) { @@ -504,16 +520,6 @@ DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { return this->options[opt_key]; } -template -T* -DynamicConfig::opt(const t_config_option_key &opt_key, bool create) { - return dynamic_cast(this->option(opt_key, create)); -} -template ConfigOptionInt* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBool* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBools* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionPercent* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); - t_config_option_keys DynamicConfig::keys() const { t_config_option_keys keys; diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 41d3cc668..39e8f2f68 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -644,15 +644,21 @@ class ConfigBase ConfigBase() : def(NULL) {}; ConfigBase(const ConfigDef* def) : def(def) {}; virtual ~ConfigBase() {}; - bool has(const t_config_option_key &opt_key); + bool has(const t_config_option_key &opt_key) const; const ConfigOption* option(const t_config_option_key &opt_key) const; ConfigOption* option(const t_config_option_key &opt_key, bool create = false); + template T* opt(const t_config_option_key &opt_key, bool create = false) { + return dynamic_cast(this->option(opt_key, create)); + }; + template const T* opt(const t_config_option_key &opt_key) const { + return dynamic_cast(this->option(opt_key)); + }; virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; virtual t_config_option_keys keys() const = 0; void apply(const ConfigBase &other, bool ignore_nonexistent = false); void apply_only(const ConfigBase &other, const t_config_option_keys &opt_keys, bool ignore_nonexistent = false); - bool equals(ConfigBase &other); - t_config_option_keys diff(ConfigBase &other); + bool equals(const ConfigBase &other) const; + t_config_option_keys diff(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; virtual bool set_deserialize(t_config_option_key opt_key, std::string str, bool append = false); double get_abs_value(const t_config_option_key &opt_key) const; @@ -673,7 +679,6 @@ class DynamicConfig : public virtual ConfigBase DynamicConfig& operator= (DynamicConfig other); void swap(DynamicConfig &other); virtual ~DynamicConfig(); - template T* opt(const t_config_option_key &opt_key, bool create = false); virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); t_config_option_keys keys() const; void erase(const t_config_option_key &opt_key); diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 807d58b45..35f6e13e4 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -147,92 +147,95 @@ Print::delete_region(size_t idx) } bool -Print::invalidate_state_by_config_options(const std::vector &opt_keys) +Print::invalidate_state_by_config(const PrintConfigBase &config) { + const t_config_option_keys diff = this->config.diff(config); + std::set steps; std::set osteps; + bool all = false; // this method only accepts PrintConfig option keys - for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { - if (*opt_key == "skirts" - || *opt_key == "skirt_height" - || *opt_key == "skirt_distance" - || *opt_key == "min_skirt_length" - || *opt_key == "ooze_prevention") { + for (const t_config_option_key &opt_key : diff) { + if (opt_key == "skirts" + || opt_key == "skirt_height" + || opt_key == "skirt_distance" + || opt_key == "min_skirt_length" + || opt_key == "ooze_prevention") { steps.insert(psSkirt); - } else if (*opt_key == "brim_width" - || *opt_key == "interior_brim_width" - || *opt_key == "brim_connections_width") { + } else if (opt_key == "brim_width" + || opt_key == "interior_brim_width" + || opt_key == "brim_connections_width") { steps.insert(psBrim); steps.insert(psSkirt); - } else if (*opt_key == "nozzle_diameter" - || *opt_key == "resolution" - || *opt_key == "z_steps_per_mm") { + } else if (opt_key == "nozzle_diameter" + || opt_key == "resolution" + || opt_key == "z_steps_per_mm") { osteps.insert(posSlice); - } else if (*opt_key == "avoid_crossing_perimeters" - || *opt_key == "bed_shape" - || *opt_key == "bed_temperature" - || *opt_key == "bridge_acceleration" - || *opt_key == "bridge_fan_speed" - || *opt_key == "complete_objects" - || *opt_key == "cooling" - || *opt_key == "default_acceleration" - || *opt_key == "disable_fan_first_layers" - || *opt_key == "duplicate_distance" - || *opt_key == "end_gcode" - || *opt_key == "extruder_clearance_height" - || *opt_key == "extruder_clearance_radius" - || *opt_key == "extruder_offset" - || *opt_key == "extrusion_axis" - || *opt_key == "extrusion_multiplier" - || *opt_key == "fan_always_on" - || *opt_key == "fan_below_layer_time" - || *opt_key == "filament_colour" - || *opt_key == "filament_diameter" - || *opt_key == "first_layer_acceleration" - || *opt_key == "first_layer_bed_temperature" - || *opt_key == "first_layer_speed" - || *opt_key == "first_layer_temperature" - || *opt_key == "gcode_arcs" - || *opt_key == "gcode_comments" - || *opt_key == "gcode_flavor" - || *opt_key == "infill_acceleration" - || *opt_key == "infill_first" - || *opt_key == "layer_gcode" - || *opt_key == "min_fan_speed" - || *opt_key == "max_fan_speed" - || *opt_key == "min_print_speed" - || *opt_key == "notes" - || *opt_key == "only_retract_when_crossing_perimeters" - || *opt_key == "output_filename_format" - || *opt_key == "perimeter_acceleration" - || *opt_key == "post_process" - || *opt_key == "pressure_advance" - || *opt_key == "retract_before_travel" - || *opt_key == "retract_layer_change" - || *opt_key == "retract_length" - || *opt_key == "retract_length_toolchange" - || *opt_key == "retract_lift" - || *opt_key == "retract_lift_above" - || *opt_key == "retract_lift_below" - || *opt_key == "retract_restart_extra" - || *opt_key == "retract_restart_extra_toolchange" - || *opt_key == "retract_speed" - || *opt_key == "slowdown_below_layer_time" - || *opt_key == "spiral_vase" - || *opt_key == "standby_temperature_delta" - || *opt_key == "start_gcode" - || *opt_key == "temperature" - || *opt_key == "threads" - || *opt_key == "toolchange_gcode" - || *opt_key == "travel_speed" - || *opt_key == "use_firmware_retraction" - || *opt_key == "use_relative_e_distances" - || *opt_key == "vibration_limit" - || *opt_key == "wipe" - || *opt_key == "z_offset") { + } else if (opt_key == "avoid_crossing_perimeters" + || opt_key == "bed_shape" + || opt_key == "bed_temperature" + || opt_key == "bridge_acceleration" + || opt_key == "bridge_fan_speed" + || opt_key == "complete_objects" + || opt_key == "cooling" + || opt_key == "default_acceleration" + || opt_key == "disable_fan_first_layers" + || opt_key == "duplicate_distance" + || opt_key == "end_gcode" + || opt_key == "extruder_clearance_height" + || opt_key == "extruder_clearance_radius" + || opt_key == "extruder_offset" + || opt_key == "extrusion_axis" + || opt_key == "extrusion_multiplier" + || opt_key == "fan_always_on" + || opt_key == "fan_below_layer_time" + || opt_key == "filament_colour" + || opt_key == "filament_diameter" + || opt_key == "first_layer_acceleration" + || opt_key == "first_layer_bed_temperature" + || opt_key == "first_layer_speed" + || opt_key == "first_layer_temperature" + || opt_key == "gcode_arcs" + || opt_key == "gcode_comments" + || opt_key == "gcode_flavor" + || opt_key == "infill_acceleration" + || opt_key == "infill_first" + || opt_key == "layer_gcode" + || opt_key == "min_fan_speed" + || opt_key == "max_fan_speed" + || opt_key == "min_print_speed" + || opt_key == "notes" + || opt_key == "only_retract_when_crossing_perimeters" + || opt_key == "output_filename_format" + || opt_key == "perimeter_acceleration" + || opt_key == "post_process" + || opt_key == "pressure_advance" + || opt_key == "retract_before_travel" + || opt_key == "retract_layer_change" + || opt_key == "retract_length" + || opt_key == "retract_length_toolchange" + || opt_key == "retract_lift" + || opt_key == "retract_lift_above" + || opt_key == "retract_lift_below" + || opt_key == "retract_restart_extra" + || opt_key == "retract_restart_extra_toolchange" + || opt_key == "retract_speed" + || opt_key == "slowdown_below_layer_time" + || opt_key == "spiral_vase" + || opt_key == "standby_temperature_delta" + || opt_key == "start_gcode" + || opt_key == "temperature" + || opt_key == "threads" + || opt_key == "toolchange_gcode" + || opt_key == "travel_speed" + || opt_key == "use_firmware_retraction" + || opt_key == "use_relative_e_distances" + || opt_key == "vibration_limit" + || opt_key == "wipe" + || opt_key == "z_offset") { // these options only affect G-code export, so nothing to invalidate - } else if (*opt_key == "first_layer_extrusion_width") { + } else if (opt_key == "first_layer_extrusion_width") { osteps.insert(posPerimeters); osteps.insert(posInfill); osteps.insert(posSupportMaterial); @@ -240,18 +243,31 @@ Print::invalidate_state_by_config_options(const std::vector steps.insert(psBrim); } else { // for legacy, if we can't handle this option let's invalidate all steps - return this->invalidate_all_steps(); + all = true; + break; } } + if (!diff.empty()) + this->config.apply(config, true); + bool invalidated = false; - for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { - if (this->invalidate_step(*step)) invalidated = true; - } - for (std::set::const_iterator ostep = osteps.begin(); ostep != osteps.end(); ++ostep) { - FOREACH_OBJECT(this, object) { - if ((*object)->invalidate_step(*ostep)) invalidated = true; - } + if (all) { + if (this->invalidate_all_steps()) + invalidated = true; + + for (PrintObject* object : this->objects) + if (object->invalidate_all_steps()) + invalidated = true; + } else { + for (const PrintStep &step : steps) + if (this->invalidate_step(step)) + invalidated = true; + + for (const PrintObjectStep &ostep : osteps) + for (PrintObject* object : this->objects) + if (object->invalidate_step(ostep)) + invalidated = true; } return invalidated; @@ -481,20 +497,12 @@ Print::apply_config(DynamicPrintConfig config) // apply variables to placeholder parser this->placeholder_parser.apply_config(config); - bool invalidated = false; - // handle changes to print config - t_config_option_keys print_diff = this->config.diff(config); - if (!print_diff.empty()) { - this->config.apply(config, true); - - if (this->invalidate_state_by_config_options(print_diff)) - invalidated = true; - } + bool invalidated = this->invalidate_state_by_config(config); // handle changes to object config defaults this->default_object_config.apply(config, true); - FOREACH_OBJECT(this, obj_ptr) { + for (PrintObject* object : this->objects) { // we don't assume that config contains a full ObjectConfig, // so we base it on the current print-wise default PrintObjectConfig new_config = this->default_object_config; @@ -502,19 +510,14 @@ Print::apply_config(DynamicPrintConfig config) // we override the new config with object-specific options { - DynamicPrintConfig model_object_config = (*obj_ptr)->model_object()->config; + DynamicPrintConfig model_object_config = object->model_object()->config; model_object_config.normalize(); new_config.apply(model_object_config, true); } // check whether the new config is different from the current one - t_config_option_keys diff = (*obj_ptr)->config.diff(new_config); - if (!diff.empty()) { - (*obj_ptr)->config.apply(new_config, true); - - if ((*obj_ptr)->invalidate_state_by_config_options(diff)) - invalidated = true; - } + if (object->invalidate_state_by_config(new_config)) + invalidated = true; } // handle changes to regions config defaults @@ -563,14 +566,8 @@ Print::apply_config(DynamicPrintConfig config) // if we're here and the new region config is different from the old // one, we need to apply the new config and invalidate all objects // (possible optimization: only invalidate objects using this region) - t_config_option_keys region_config_diff = region->config.diff(new_config); - if (!region_config_diff.empty()) { - region->config.apply(new_config); - FOREACH_OBJECT(this, o) { - if ((*o)->invalidate_state_by_config_options(region_config_diff)) - invalidated = true; - } - } + if (region->invalidate_state_by_config(new_config)) + invalidated = true; } } other_region_configs.insert(other_region_configs.end(), this_region_configs.begin(), this_region_configs.end()); diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 18fc760ef..5e050ccd7 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -55,6 +55,7 @@ class PrintRegion Print* print(); Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + bool invalidate_state_by_config(const PrintConfigBase &config); private: Print* _print; @@ -132,7 +133,7 @@ class PrintObject void delete_support_layer(int idx); // methods for handling state - bool invalidate_state_by_config_options(const std::vector &opt_keys); + bool invalidate_state_by_config(const PrintConfigBase &config); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); @@ -196,7 +197,7 @@ class Print PrintRegion* add_region(); // methods for handling state - bool invalidate_state_by_config_options(const std::vector &opt_keys); + bool invalidate_state_by_config(const PrintConfigBase &config); bool invalidate_step(PrintStep step); bool invalidate_all_steps(); bool step_done(PrintObjectStep step) const; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 0e752849f..5e841b637 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -212,90 +212,63 @@ PrintObject::delete_support_layer(int idx) } bool -PrintObject::invalidate_state_by_config_options(const std::vector &opt_keys) +PrintObject::invalidate_state_by_config(const PrintConfigBase &config) { + const t_config_option_keys diff = this->config.diff(config); + std::set steps; + bool all = false; // this method only accepts PrintObjectConfig and PrintRegionConfig option keys - for (std::vector::const_iterator opt_key = opt_keys.begin(); opt_key != opt_keys.end(); ++opt_key) { - if (*opt_key == "perimeters" - || *opt_key == "extra_perimeters" - || *opt_key == "gap_fill_speed" - || *opt_key == "overhangs" - || *opt_key == "first_layer_extrusion_width" - || *opt_key == "perimeter_extrusion_width" - || *opt_key == "thin_walls" - || *opt_key == "external_perimeters_first") { - steps.insert(posPerimeters); - } else if (*opt_key == "layer_height" - || *opt_key == "first_layer_height" - || *opt_key == "xy_size_compensation" - || *opt_key == "raft_layers") { + for (const t_config_option_key &opt_key : diff) { + if (opt_key == "layer_height" + || opt_key == "first_layer_height" + || opt_key == "xy_size_compensation" + || opt_key == "raft_layers") { + steps.insert(posSlice); + } else if (opt_key == "support_material_contact_distance") { steps.insert(posSlice); - } else if (*opt_key == "support_material" - || *opt_key == "support_material_angle" - || *opt_key == "support_material_extruder" - || *opt_key == "support_material_extrusion_width" - || *opt_key == "support_material_interface_layers" - || *opt_key == "support_material_interface_extruder" - || *opt_key == "support_material_interface_spacing" - || *opt_key == "support_material_interface_speed" - || *opt_key == "support_material_buildplate_only" - || *opt_key == "support_material_pattern" - || *opt_key == "support_material_spacing" - || *opt_key == "support_material_threshold" - || *opt_key == "dont_support_bridges" - || *opt_key == "first_layer_extrusion_width") { - steps.insert(posSupportMaterial); - } else if (*opt_key == "interface_shells" - || *opt_key == "infill_only_where_needed" - || *opt_key == "infill_every_layers" - || *opt_key == "solid_infill_every_layers" - || *opt_key == "bottom_solid_layers" - || *opt_key == "top_solid_layers" - || *opt_key == "solid_infill_below_area" - || *opt_key == "infill_extruder" - || *opt_key == "solid_infill_extruder" - || *opt_key == "infill_extrusion_width") { - steps.insert(posPrepareInfill); - } else if (*opt_key == "top_infill_pattern" - || *opt_key == "bottom_infill_pattern" - || *opt_key == "fill_angle" - || *opt_key == "fill_pattern" - || *opt_key == "top_infill_extrusion_width" - || *opt_key == "first_layer_extrusion_width" - || *opt_key == "infill_overlap") { - steps.insert(posInfill); - } else if (*opt_key == "fill_density" - || *opt_key == "solid_infill_extrusion_width") { - steps.insert(posPerimeters); - steps.insert(posPrepareInfill); - } else if (*opt_key == "external_perimeter_extrusion_width" - || *opt_key == "perimeter_extruder") { steps.insert(posPerimeters); steps.insert(posSupportMaterial); - } else if (*opt_key == "bridge_flow_ratio") { + } else if (opt_key == "support_material") { steps.insert(posPerimeters); - steps.insert(posInfill); - } else if (*opt_key == "seam_position" - || *opt_key == "support_material_speed" - || *opt_key == "bridge_speed" - || *opt_key == "external_perimeter_speed" - || *opt_key == "infill_speed" - || *opt_key == "perimeter_speed" - || *opt_key == "small_perimeter_speed" - || *opt_key == "solid_infill_speed" - || *opt_key == "top_solid_infill_speed") { + steps.insert(posSupportMaterial); + } else if (opt_key == "support_material_angle" + || opt_key == "support_material_extruder" + || opt_key == "support_material_extrusion_width" + || opt_key == "support_material_interface_layers" + || opt_key == "support_material_interface_extruder" + || opt_key == "support_material_interface_spacing" + || opt_key == "support_material_interface_speed" + || opt_key == "support_material_buildplate_only" + || opt_key == "support_material_pattern" + || opt_key == "support_material_spacing" + || opt_key == "support_material_threshold" + || opt_key == "dont_support_bridges") { + steps.insert(posSupportMaterial); + } else if (opt_key == "interface_shells" + || opt_key == "infill_only_where_needed") { + steps.insert(posPrepareInfill); + } else if (opt_key == "seam_position" + || opt_key == "support_material_speed") { // these options only affect G-code export, so nothing to invalidate } else { // for legacy, if we can't handle this option let's invalidate all steps - return this->invalidate_all_steps(); + all = true; + break; } } + if (!diff.empty()) + this->config.apply(config, true); + bool invalidated = false; - for (std::set::const_iterator step = steps.begin(); step != steps.end(); ++step) { - if (this->invalidate_step(*step)) invalidated = true; + if (all) { + invalidated = this->invalidate_all_steps(); + } else { + for (const PrintObjectStep &step : steps) + if (this->invalidate_step(step)) + invalidated = true; } return invalidated; diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index e6ef456bc..cb4e7fa2e 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -65,4 +65,91 @@ PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_la return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)this->config.bridge_flow_ratio : 0.0); } +bool +PrintRegion::invalidate_state_by_config(const PrintConfigBase &config) +{ + const t_config_option_keys diff = this->config.diff(config); + + std::set steps; + bool all = false; + + for (const t_config_option_key &opt_key : diff) { + if (opt_key == "perimeters" + || opt_key == "extra_perimeters" + || opt_key == "gap_fill_speed" + || opt_key == "overhangs" + || opt_key == "first_layer_extrusion_width" + || opt_key == "perimeter_extrusion_width" + || opt_key == "thin_walls" + || opt_key == "external_perimeters_first") { + steps.insert(posPerimeters); + } else if (opt_key == "first_layer_extrusion_width") { + steps.insert(posSupportMaterial); + } else if (opt_key == "infill_every_layers" + || opt_key == "solid_infill_every_layers" + || opt_key == "bottom_solid_layers" + || opt_key == "top_solid_layers" + || opt_key == "solid_infill_below_area" + || opt_key == "infill_extruder" + || opt_key == "solid_infill_extruder" + || opt_key == "infill_extrusion_width") { + steps.insert(posPrepareInfill); + } else if (opt_key == "top_infill_pattern" + || opt_key == "bottom_infill_pattern" + || opt_key == "fill_angle" + || opt_key == "fill_pattern" + || opt_key == "top_infill_extrusion_width" + || opt_key == "first_layer_extrusion_width" + || opt_key == "infill_overlap") { + steps.insert(posInfill); + } else if (opt_key == "solid_infill_extrusion_width") { + steps.insert(posPerimeters); + steps.insert(posPrepareInfill); + } else if (opt_key == "fill_density") { + const float &cur_value = config.opt("fill_density")->value; + const float &new_value = this->config.fill_density.value; + if ((cur_value == 0) != (new_value == 0)) + steps.insert(posPerimeters); + + steps.insert(posPrepareInfill); + } else if (opt_key == "external_perimeter_extrusion_width" + || opt_key == "perimeter_extruder") { + steps.insert(posPerimeters); + steps.insert(posSupportMaterial); + } else if (opt_key == "bridge_flow_ratio") { + steps.insert(posPerimeters); + steps.insert(posInfill); + } else if (opt_key == "bridge_speed" + || opt_key == "external_perimeter_speed" + || opt_key == "infill_speed" + || opt_key == "perimeter_speed" + || opt_key == "small_perimeter_speed" + || opt_key == "solid_infill_speed" + || opt_key == "top_solid_infill_speed") { + // these options only affect G-code export, so nothing to invalidate + } else { + // for legacy, if we can't handle this option let's invalidate all steps + all = true; + break; + } + } + + if (!diff.empty()) + this->config.apply(config, true); + + bool invalidated = false; + if (all) { + for (PrintObject* object : this->print()->objects) + if (object->invalidate_all_steps()) + invalidated = true; + } else { + for (const PrintObjectStep &step : steps) + for (PrintObject* object : this->print()->objects) + if (object->invalidate_step(step)) + invalidated = true; + } + + return invalidated; +} + } diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index e9a4c1a6d..59169b325 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -114,7 +114,6 @@ _constant() Ref add_support_layer(int id, coordf_t height, coordf_t print_z); void delete_support_layer(int idx); - bool invalidate_state_by_config_options(std::vector opt_keys); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); bool step_done(PrintObjectStep step) @@ -190,7 +189,6 @@ _constant() size_t region_count() %code%{ RETVAL = THIS->regions.size(); %}; - bool invalidate_state_by_config_options(std::vector opt_keys); bool invalidate_step(PrintStep step); bool invalidate_all_steps(); bool step_done(PrintStep step) From 7be97d319c8ee0cf0d086826188cbbfd406db8d2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 28 Mar 2017 17:38:31 +0200 Subject: [PATCH 56/68] Cleanup --- xs/src/libslic3r/Config.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 68f7f7e2d..68d4837c1 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -388,25 +388,6 @@ ConfigBase::option(const t_config_option_key &opt_key, bool create) { return this->optptr(opt_key, create); } -/* -template -T* -ConfigBase::opt(const t_config_option_key &opt_key, bool create) { - return dynamic_cast(this->option(opt_key, create)); -} -template ConfigOptionInt* ConfigBase::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBool* ConfigBase::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBools* ConfigBase::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionPercent* ConfigBase::opt(const t_config_option_key &opt_key, bool create); - - -template -const T* -ConfigBase::opt(const t_config_option_key &opt_key) const { - return dynamic_cast(this->option(opt_key)); -} -*/ - void ConfigBase::load(const std::string &file) { From d5a0e78c1620d53df79f0c1cbca4ca91108770d0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 28 Mar 2017 18:02:33 +0200 Subject: [PATCH 57/68] Fixed regression in --load. #3816 --- slic3r.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/slic3r.pl b/slic3r.pl index ed03c838a..c4d081989 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -124,6 +124,7 @@ die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line # apply command line config on top of default config my $config = Slic3r::Config->new_from_defaults; + $config->apply($_) for @external_configs; $config->apply($cli_config); $config->validate; From 10830ec23b30015ebfa5ef6e5d1186b247ddc3ff Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Tue, 28 Mar 2017 23:46:54 -0500 Subject: [PATCH 58/68] Added translation marker macro for strings in libslic3r. It's just there to tag strings for poedit to pick up. --- xs/src/libslic3r/libslic3r.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 50b4241de..94b8141de 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -32,6 +32,8 @@ void confess_at(const char *file, int line, const char *func, const char *pat, . #define STDMOVE(WHAT) (WHAT) #endif +// dummy macro to mark strings for translation for gettext/poedit +#define __TRANS(s) s namespace Slic3r { constexpr auto SLIC3R_VERSION = "1.3.0-dev"; From 0c8f5eeb905bcb2c95fb40c45023f53f4bd995f7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Mar 2017 17:47:02 +0200 Subject: [PATCH 59/68] Whenever user opens and closes the preset editor, apply the new defaults to the overrides. This is less confusing. #3814 --- lib/Slic3r/GUI/Plater.pm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 536f8be64..60027cb5d 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -554,7 +554,8 @@ sub _on_select_preset { wxTheApp->save_settings; - my $config = $self->config; + # Ignore overrides in the plater, we only care about the preset configs. + my $config = $self->config(1); $self->on_extruders_change(scalar @{$config->get('nozzle_diameter')}); @@ -571,10 +572,11 @@ sub _on_select_preset { # Add/remove options (we do it this way for preserving current options) foreach my $opt_key (@$overridable) { - if (!$o_config->has($opt_key)) { - # Populate option with the default value taken from configuration - $o_config->set($opt_key, $config->get($opt_key)); - } + # Populate option with the default value taken from configuration + # (re-set the override always, because if we here it means user + # switched to this preset or opened/closed the editor, so he expects + # the new values set in the editor to be used). + $o_config->set($opt_key, $config->get($opt_key)); } foreach my $opt_key (@{$o_config->get_keys}) { # Keep options listed among overridable and options added on the fly @@ -758,7 +760,7 @@ sub show_preset_editor { # Returns the current config by merging the selected presets and the overrides. sub config { - my ($self) = @_; + my ($self, $ignore_overrides) = @_; # use a DynamicConfig because FullPrintConfig is not enough my $config = Slic3r::Config->new_from_defaults; @@ -791,7 +793,8 @@ sub config { $config->apply($filament_config); } $config->apply($_->dirty_config) for @{ $presets{print} }; - $config->apply($self->{settings_override_config}); + $config->apply($self->{settings_override_config}) + unless $ignore_overrides; return $config; } From 467dc324f61ebb324916faef07a748226e4c3f42 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Mar 2017 18:48:46 +0200 Subject: [PATCH 60/68] Code sign Mac packages. #2875 --- package/osx/make_dmg.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package/osx/make_dmg.sh b/package/osx/make_dmg.sh index d62fe991b..76ea206b3 100755 --- a/package/osx/make_dmg.sh +++ b/package/osx/make_dmg.sh @@ -117,5 +117,13 @@ make_plist echo $PkgInfoContents >$appfolder/Contents/PkgInfo +if [[ -e "${KEYCHAIN_FILE}" ]]; then + echo "Signing app..." + security list-keychains -s "${KEYCHAIN_FILE}" + security default-keychain -s "${KEYCHAIN_FILE}" + security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_FILE}" + codesign --sign "${KEYCHAIN_IDENTITY}" "$appfolder" +fi + echo "Creating dmg file...." hdiutil create -fs HFS+ -srcfolder "$appfolder" -volname "$appname" "$dmgfile" From 7abc1b1a4b8ed0041e48e4f003b04e22753ca9e9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Mar 2017 20:32:19 +0200 Subject: [PATCH 61/68] Make codesign call compatible with MacOS X >= 10.9.5. #2875 --- package/osx/make_dmg.sh | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/package/osx/make_dmg.sh b/package/osx/make_dmg.sh index 76ea206b3..15b56fa9b 100755 --- a/package/osx/make_dmg.sh +++ b/package/osx/make_dmg.sh @@ -62,31 +62,28 @@ PP_BIN=$(which pp) SLIC3R_DIR=$(perl -MCwd=realpath -e "print realpath '${WD}/../../'") if [[ -d "${appfolder}" ]]; then - echo "Deleting old working folder." + echo "Deleting old working folder: ${appfolder}" rm -rf ${appfolder} fi if [[ -e "${dmgfile}" ]]; then - echo "Deleting old dmg ${dmgfile}." + echo "Deleting old dmg: ${dmgfile}" rm -rf ${dmgfile} fi -echo "Creating new app folder at $appfolder." +echo "Creating new app folder: $appfolder" mkdir -p $appfolder mkdir -p $macosfolder mkdir -p $resourcefolder echo "Copying resources..." -cp -r $SLIC3R_DIR/var $macosfolder/ +cp -rf $SLIC3R_DIR/var $macosfolder/ mv $macosfolder/var/Slic3r.icns $resourcefolder echo "Copying Slic3r..." cp $SLIC3R_DIR/slic3r.pl $macosfolder/slic3r.pl -cp -RP $SLIC3R_DIR/local-lib $macosfolder/local-lib -cp -RP $SLIC3R_DIR/lib/* $macosfolder/local-lib/lib/perl5/ -find $macosfolder/local-lib -name man -type d -delete -find $macosfolder/local-lib -name .packlist -delete -rm -rf $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/include +cp -fRP $SLIC3R_DIR/local-lib $macosfolder/local-lib +cp -fRP $SLIC3R_DIR/lib/* $macosfolder/local-lib/lib/perl5/ echo "Relocating dylib paths..." for bundle in $(find $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level/auto/Wx -name '*.bundle') $(find $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level/Alien/wxWidgets -name '*.dylib' -type f); do @@ -97,11 +94,11 @@ for bundle in $(find $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level done echo "Copying startup script..." -cp $WD/startup_script.sh $macosfolder/$appname +cp -f $WD/startup_script.sh $macosfolder/$appname chmod +x $macosfolder/$appname echo "Copying perl from $PERL_BIN" -cp $PERL_BIN $macosfolder/perl-local +cp -f $PERL_BIN $macosfolder/perl-local ${PP_BIN} -M attributes -M base -M bytes -M B -M POSIX \ -M FindBin -M Unicode::Normalize -M Tie::Handle \ -M Time::Local -M Math::Trig \ @@ -110,19 +107,28 @@ ${PP_BIN} -M attributes -M base -M bytes -M B -M POSIX \ -M strict -M utf8 -M parent \ -B -p -e "print 123" -o $WD/_tmp/test.par unzip $WD/_tmp/test.par -d $WD/_tmp/ -cp -r $WD/_tmp/lib/* $macosfolder/local-lib/lib/perl5/ +cp -rf $WD/_tmp/lib/* $macosfolder/local-lib/lib/perl5/ rm -rf $WD/_tmp +echo "Cleaning bundle" +find $macosfolder/local-lib -name man -type d -delete +find $macosfolder/local-lib -name .packlist -delete +find $macosfolder/local-lib -name .meta -exec rm -rf "{}" \; +find $macosfolder/local-lib -name '*.h' -delete +find $macosfolder/local-lib -type d -empty -delete +rm -rf $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/include + make_plist echo $PkgInfoContents >$appfolder/Contents/PkgInfo if [[ -e "${KEYCHAIN_FILE}" ]]; then echo "Signing app..." + chmod -R +w $macosfolder/* security list-keychains -s "${KEYCHAIN_FILE}" security default-keychain -s "${KEYCHAIN_FILE}" security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_FILE}" - codesign --sign "${KEYCHAIN_IDENTITY}" "$appfolder" + codesign --sign "${KEYCHAIN_IDENTITY}" --deep "$appfolder" fi echo "Creating dmg file...." From 3ba37b3ca55e6fc370b254b01b9a78822e0c9c17 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Mar 2017 21:14:41 +0200 Subject: [PATCH 62/68] One more fix for codesigned OSX packages. #2875 --- slic3r.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slic3r.pl b/slic3r.pl index c4d081989..75bf3b961 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -6,7 +6,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/lib"; - use local::lib "$FindBin::Bin/local-lib"; + use local::lib '--no-create', "$FindBin::Bin/local-lib"; } use File::Basename qw(basename); From ebc8409f3f4fa86e690c0d18750e216851cd73a0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Mar 2017 21:17:34 +0200 Subject: [PATCH 63/68] Minor pruning of OSX packages --- package/osx/make_dmg.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/osx/make_dmg.sh b/package/osx/make_dmg.sh index 15b56fa9b..a800f23f2 100755 --- a/package/osx/make_dmg.sh +++ b/package/osx/make_dmg.sh @@ -111,7 +111,8 @@ cp -rf $WD/_tmp/lib/* $macosfolder/local-lib/lib/perl5/ rm -rf $WD/_tmp echo "Cleaning bundle" -find $macosfolder/local-lib -name man -type d -delete +rm -rf $macosfolder/local-lib/bin +rm -rf $macosfolder/local-lib/man find $macosfolder/local-lib -name .packlist -delete find $macosfolder/local-lib -name .meta -exec rm -rf "{}" \; find $macosfolder/local-lib -name '*.h' -delete From 99943024a9e3288cccaa6f15bc512dd95ea38216 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Thu, 30 Mar 2017 00:21:32 -0500 Subject: [PATCH 64/68] Permit firmware retraction when firmware is Repetier (repetier-firmware supports it). Fixes #3821 --- lib/Slic3r/Config.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 7ccca6a97..b68ef2a3e 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -189,8 +189,11 @@ sub validate { 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 and Machinekit firmware\n" - if $self->use_firmware_retraction && $self->gcode_flavor ne 'smoothie' && $self->gcode_flavor ne 'reprap' && $self->gcode_flavor ne 'machinekit'; + 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}; From 5e1f3e883da0ac3575f242ce83b18ab0e64c7e21 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Thu, 30 Mar 2017 00:42:44 -0500 Subject: [PATCH 65/68] add xsgui to list of branches to build with travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5e6930fbf..a34433a9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ perl: branches: only: - master + - xsgui sudo: false cache: apt: true From 35fc1decfa6cdd73f2b7ad711bfe5cb24b719c9a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 30 Mar 2017 08:15:26 +0200 Subject: [PATCH 66/68] Fixed regression causing no_controller not to hide the USB/Serial configuration options in Printer config. Fixes #3819 --- lib/Slic3r/GUI.pm | 5 +---- lib/Slic3r/GUI/MainFrame.pm | 5 +---- lib/Slic3r/GUI/PresetEditor.pm | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index eb5a7bd01..d90c56c34 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -148,10 +148,7 @@ sub OnInit { # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); - $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( - # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - no_controller => $no_controller // $Settings->{_}{no_controller}, - ); + $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new; $self->SetTopWindow($frame); # load init bundle diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index b1de58b8b..c4ceb7780 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -18,7 +18,7 @@ our $qs_last_output_file; our $last_config; sub new { - my ($class, %params) = @_; + my ($class) = @_; my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); if ($^O eq 'MSWin32') { @@ -27,9 +27,6 @@ sub new { $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); } - # store input params - # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - $self->{no_controller} = $params{no_controller}; $self->{loaded} = 0; # initialize tabpanel and menubar diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index c4c722f10..84b0f1a8e 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -1184,7 +1184,6 @@ sub overridable_options { sub build { my $self = shift; - my (%params) = @_; $self->{extruders_count} = 1; @@ -1231,8 +1230,7 @@ sub build { } }); } - if (!$params{no_controller}) - { + unless ($Slic3r::GUI::Settings->{_}{no_controller}) { my $optgroup = $page->new_optgroup('USB/Serial connection'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Serial port', From 4a2eeaf27f834784182e8b6366c73b202114bf6f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 30 Mar 2017 08:46:56 +0200 Subject: [PATCH 67/68] One more fix for #3819 --- lib/Slic3r/GUI/PresetEditor.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 84b0f1a8e..2f2ed604f 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -1370,7 +1370,7 @@ sub build { $self->{extruder_pages} = []; $self->_build_extruder_pages; - $self->_update_serial_ports if (!$params{no_controller}); + $self->_update_serial_ports unless $Slic3r::GUI::Settings->{_}{no_controller}; } sub _update_serial_ports { From 0e129e765bfcb62bf217cabca6161969c950a141 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 30 Mar 2017 08:54:59 +0200 Subject: [PATCH 68/68] Improvements to make_dmg.sh, strip more things --- lib/Slic3r.pm | 6 +++++- package/osx/make_dmg.sh | 34 +++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index ca09d71f9..37f242e72 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -42,7 +42,11 @@ warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n" use FindBin; # Path to the images. -our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] }; +my $varpath = decode_path($FindBin::Bin) . "/var"; +if ($^O eq 'darwin' && !-d $varpath) { + $varpath = decode_path($FindBin::Bin) . "/../Resources/var"; +} +our $var = sub { "$varpath/$_[0]" }; use Moo 1.003001; diff --git a/package/osx/make_dmg.sh b/package/osx/make_dmg.sh index a800f23f2..f82464daa 100755 --- a/package/osx/make_dmg.sh +++ b/package/osx/make_dmg.sh @@ -77,8 +77,8 @@ mkdir -p $macosfolder mkdir -p $resourcefolder echo "Copying resources..." -cp -rf $SLIC3R_DIR/var $macosfolder/ -mv $macosfolder/var/Slic3r.icns $resourcefolder +cp -rf $SLIC3R_DIR/var $resourcefolder/ +mv $resourcefolder/var/Slic3r.icns $resourcefolder echo "Copying Slic3r..." cp $SLIC3R_DIR/slic3r.pl $macosfolder/slic3r.pl @@ -106,18 +106,38 @@ ${PP_BIN} -M attributes -M base -M bytes -M B -M POSIX \ -M warnings -M local::lib \ -M strict -M utf8 -M parent \ -B -p -e "print 123" -o $WD/_tmp/test.par -unzip $WD/_tmp/test.par -d $WD/_tmp/ +unzip -o $WD/_tmp/test.par -d $WD/_tmp/ cp -rf $WD/_tmp/lib/* $macosfolder/local-lib/lib/perl5/ rm -rf $WD/_tmp echo "Cleaning bundle" rm -rf $macosfolder/local-lib/bin rm -rf $macosfolder/local-lib/man -find $macosfolder/local-lib -name .packlist -delete -find $macosfolder/local-lib -name .meta -exec rm -rf "{}" \; -find $macosfolder/local-lib -name '*.h' -delete -find $macosfolder/local-lib -type d -empty -delete +rm -f $macosfolder/local-lib/lib/perl5/Algorithm/*.pl +rm -rf $macosfolder/local-lib/lib/perl5/unicore +rm -rf $macosfolder/local-lib/lib/perl5/App +rm -rf $macosfolder/local-lib/lib/perl5/Devel/CheckLib.pm +rm -rf $macosfolder/local-lib/lib/perl5/ExtUtils +rm -rf $macosfolder/local-lib/lib/perl5/Module/Build* +rm -rf $macosfolder/local-lib/lib/perl5/TAP +rm -rf $macosfolder/local-lib/lib/perl5/Test* +find -d $macosfolder/local-lib -name '*.pod' -delete +find -d $macosfolder/local-lib -name .packlist -delete +find -d $macosfolder/local-lib -name .meta -exec rm -rf "{}" \; +find -d $macosfolder/local-lib -name '*.h' -delete +find -d $macosfolder/local-lib -name wxPerl.app -exec rm -rf "{}" \; +find -d $macosfolder/local-lib -type d -path '*/Wx/*' \( -name WebView \ + -or -name DocView -or -name STC -or -name IPC \ + -or -name AUI -or -name Calendar -or -name DataView \ + -or -name DateTime -or -name Media -or -name PerlTest \ + -or -name Ribbon \) -exec rm -rf "{}" \; +find -d $macosfolder/local-lib -name libwx_osx_cocoau_ribbon-3.0.0.2.0.dylib -delete +find -d $macosfolder/local-lib -name libwx_osx_cocoau_aui-3.0.0.2.0.dylib -delete +find -d $macosfolder/local-lib -name libwx_osx_cocoau_media-3.0.0.2.0.dylib -delete +find -d $macosfolder/local-lib -name libwx_osx_cocoau_stc-3.0.0.2.0.dylib -delete +find -d $macosfolder/local-lib -name libwx_osx_cocoau_webview-3.0.0.2.0.dylib -delete rm -rf $macosfolder/local-lib/lib/perl5/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/include +find -d $macosfolder/local-lib -type d -empty -delete make_plist