diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 556003429..ab73a10e0 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -8,6 +8,7 @@ use FindBin; use Slic3r::GUI::AboutDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::MainFrame; +use Slic3r::GUI::Notifier; use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::2D; use Slic3r::GUI::Plater::ObjectPartsPanel; @@ -16,8 +17,8 @@ use Slic3r::GUI::Plater::ObjectPreviewDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; use Slic3r::GUI::Preferences; +use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::OptionsGroup; -use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; @@ -28,6 +29,17 @@ use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow use Wx::Event qw(EVT_IDLE); use base 'Wx::App'; +use constant FILE_WILDCARDS => { + known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML', + stl => 'STL files (*.stl)|*.stl;*.STL', + obj => 'OBJ files (*.obj)|*.obj;*.OBJ', + amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', + ini => 'INI files *.ini|*.ini;*.INI', + gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC', + svg => 'SVG files *.svg|*.svg;*.SVG', +}; +use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)}; + our $datadir; our $no_plater; our $mode; @@ -50,7 +62,7 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); sub OnInit { - my $self = shift; + my ($self) = @_; $self->SetAppName('Slic3r'); Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; @@ -81,30 +93,29 @@ sub OnInit { $Settings->{_}{background_processing} //= 1; } $Settings->{_}{version} = $Slic3r::VERSION; - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; # application frame Wx::Image::AddHandler(Wx::PNGHandler->new); - my $frame = Slic3r::GUI::MainFrame->new( + $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( mode => $mode // $Settings->{_}{mode}, no_plater => $no_plater, ); - $self->{skeinpanel} = $frame->{skeinpanel}; $self->SetTopWindow($frame); if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) { # user was running another Slic3r version on this computer if (!defined $last_version || $last_version =~ /^0\./) { - show_info($self->{skeinpanel}, "Hello! Support material was improved since the " + show_info($self->{mainframe}, "Hello! Support material was improved since the " . "last version of Slic3r you used. It is strongly recommended to revert " . "your support material settings to the factory defaults and start from " . "those. Enjoy and provide feedback!", "Support Material"); } } - $self->{skeinpanel}->config_wizard if $run_wizard; + $self->{mainframe}->config_wizard if $run_wizard; - Slic3r::GUI->check_version - if Slic3r::GUI->have_version_check + &Wx::wxTheApp->check_version + if &Wx::wxTheApp->have_version_check && ($Settings->{_}{version_check} // 1) && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); @@ -118,13 +129,14 @@ sub OnInit { } sub about { - my $frame = shift; + my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new($frame); + my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; } +# static method accepting a wxWindow object as first parameter sub catch_error { my ($self, $cb, $message_dialog) = @_; if (my $err = $@) { @@ -137,24 +149,28 @@ sub catch_error { return 0; } +# static method accepting a wxWindow object as first parameter sub show_error { my $self = shift; my ($message) = @_; Wx::MessageDialog->new($self, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal; } +# static method accepting a wxWindow object as first parameter sub show_info { my $self = shift; my ($message, $title) = @_; Wx::MessageDialog->new($self, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal; } +# static method accepting a wxWindow object as first parameter sub fatal_error { my $self = shift; $self->show_error(@_); exit 1; } +# static method accepting a wxWindow object as first parameter sub warning_catcher { my ($self, $message_dialog) = @_; return sub { @@ -168,8 +184,7 @@ sub warning_catcher { } sub notify { - my $self = shift; - my ($message) = @_; + my ($self, $message) = @_; my $frame = $self->GetTopWindow; # try harder to attract user attention on OS X @@ -180,13 +195,12 @@ sub notify { } sub save_settings { - my $class = shift; - + my ($self) = @_; Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings); } sub presets { - my ($class, $section) = @_; + my ($self, $section) = @_; my %presets = (); opendir my $dh, "$Slic3r::GUI::datadir/$section" or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; @@ -201,15 +215,15 @@ sub presets { } sub have_version_check { - my $class = shift; + my ($self) = @_; # return an explicit 0 return ($Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1") || 0; } sub check_version { - my $class = shift; - my %p = @_; + my ($self, %p) = @_; + Slic3r::debugf "Checking for updates...\n"; @_ = (); @@ -226,7 +240,7 @@ sub check_version { Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual}; } $Settings->{_}{last_version_check} = time(); - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; } else { Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual}; } @@ -235,8 +249,7 @@ sub check_version { } sub output_path { - my $class = shift; - my ($dir) = @_; + my ($self, $dir) = @_; return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path}) ? $Settings->{_}{last_output_path} @@ -244,14 +257,14 @@ sub output_path { } sub open_model { - my ($self) = @_; + my ($self, $window) = @_; my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", - &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", + MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -263,189 +276,8 @@ sub open_model { } sub CallAfter { - my $class = shift; - my ($cb) = @_; + my ($self, $cb) = @_; push @cb, $cb; } -package Slic3r::GUI::ProgressStatusBar; -use Wx qw(:gauge :misc); -use base 'Wx::StatusBar'; - -sub new { - my $class = shift; - my $self = $class->SUPER::new(@_); - - $self->{busy} = 0; - $self->{timer} = Wx::Timer->new($self); - $self->{prog} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize); - $self->{prog}->Hide; - $self->{cancelbutton} = Wx::Button->new($self, -1, "Cancel", wxDefaultPosition, wxDefaultSize); - $self->{cancelbutton}->Hide; - - $self->SetFieldsCount(3); - $self->SetStatusWidths(-1, 150, 155); - - Wx::Event::EVT_TIMER($self, \&OnTimer, $self->{timer}); - Wx::Event::EVT_SIZE($self, \&OnSize); - Wx::Event::EVT_BUTTON($self, $self->{cancelbutton}, sub { - $self->{cancel_cb}->(); - $self->{cancelbutton}->Hide; - }); - - return $self; -} - -sub DESTROY { - my $self = shift; - $self->{timer}->Stop if $self->{timer} && $self->{timer}->IsRunning; -} - -sub OnSize { - my ($self, $event) = @_; - - my %fields = ( - # 0 is reserved for status text - 1 => $self->{cancelbutton}, - 2 => $self->{prog}, - ); - - foreach (keys %fields) { - my $rect = $self->GetFieldRect($_); - my $offset = &Wx::wxGTK ? 1 : 0; # add a cosmetic 1 pixel offset on wxGTK - my $pos = [$rect->GetX + $offset, $rect->GetY + $offset]; - $fields{$_}->Move($pos); - $fields{$_}->SetSize($rect->GetWidth - $offset, $rect->GetHeight); - } - - $event->Skip; -} - -sub OnTimer { - my ($self, $event) = @_; - - if ($self->{prog}->IsShown) { - $self->{timer}->Stop; - } - $self->{prog}->Pulse if $self->{_busy}; -} - -sub SetCancelCallback { - my $self = shift; - my ($cb) = @_; - $self->{cancel_cb} = $cb; - $cb ? $self->{cancelbutton}->Show : $self->{cancelbutton}->Hide; -} - -sub Run { - my $self = shift; - my $rate = shift || 100; - if (!$self->{timer}->IsRunning) { - $self->{timer}->Start($rate); - } -} - -sub GetProgress { - my $self = shift; - return $self->{prog}->GetValue; -} - -sub SetProgress { - my $self = shift; - my ($val) = @_; - if (!$self->{prog}->IsShown) { - $self->ShowProgress(1); - } - if ($val == $self->{prog}->GetRange) { - $self->{prog}->SetValue(0); - $self->ShowProgress(0); - } else { - $self->{prog}->SetValue($val); - } -} - -sub SetRange { - my $self = shift; - my ($val) = @_; - - if ($val != $self->{prog}->GetRange) { - $self->{prog}->SetRange($val); - } -} - -sub ShowProgress { - my $self = shift; - my ($show) = @_; - - $self->{prog}->Show($show); - $self->{prog}->Pulse; -} - -sub StartBusy { - my $self = shift; - my $rate = shift || 100; - - $self->{_busy} = 1; - $self->ShowProgress(1); - if (!$self->{timer}->IsRunning) { - $self->{timer}->Start($rate); - } -} - -sub StopBusy { - my $self = shift; - - $self->{timer}->Stop; - $self->ShowProgress(0); - $self->{prog}->SetValue(0); - $self->{_busy} = 0; -} - -sub IsBusy { - my $self = shift; - return $self->{_busy}; -} - -package Slic3r::GUI::Notifier; - -sub new { - my $class = shift; - my $self; - - $self->{icon} = "$Slic3r::var/Slic3r.png"; - - if (eval 'use Growl::GNTP; 1') { - # register with growl - eval { - $self->{growler} = Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $self->{icon}); - $self->{growler}->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]); - }; - } - - bless $self, $class; - - return $self; -} - -sub notify { - my ($self, $message) = @_; - my $title = 'Slicing Done!'; - - eval { - $self->{growler}->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message) - if $self->{growler}; - }; - # Net::DBus is broken in multithreaded environment - if (0 && eval 'use Net::DBus; 1') { - eval { - my $session = Net::DBus->session; - my $serv = $session->get_service('org.freedesktop.Notifications'); - my $notifier = $serv->get_object('/org/freedesktop/Notifications', - 'org.freedesktop.Notifications'); - $notifier->Notify('Slic3r', 0, $self->{icon}, $title, $message, [], {}, -1); - undef $Net::DBus::bus_session; - }; - } -} - 1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 3b30293ab..6c666d450 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -3,7 +3,9 @@ use strict; use warnings; use utf8; -use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu); +use List::Util qw(min); +use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog + :font :icon); use Wx::Event qw(EVT_CLOSE EVT_MENU); use base 'Wx::Frame'; @@ -43,21 +45,106 @@ use constant MI_WEBSITE => &Wx::NewId; use constant MI_VERSIONCHECK => &Wx::NewId; use constant MI_DOCUMENTATION => &Wx::NewId; +our $last_input_file; +our $last_output_file; +our $last_config; + sub new { my ($class, %params) = @_; - my $self = Wx::Frame->new(undef, -1, 'Slic3r', wxDefaultPosition, [760, 470], wxDEFAULT_FRAME_STYLE); + my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, [760, 470], wxDEFAULT_FRAME_STYLE); $self->SetIcon(Wx::Icon->new("$Slic3r::var/Slic3r_128px.png", wxBITMAP_TYPE_PNG) ); - $self->{skeinpanel} = Slic3r::GUI::SkeinPanel->new($self, - mode => $params{mode}, - no_plater => $params{no_plater}, - ); - # status bar + # store input params + $self->{mode} = $params{mode}; + $self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/; + $self->{no_plater} = $params{no_plater}; + $self->{loaded} = 0; + + # initialize tabpanel and menubar + $self->_init_tabpanel; + $self->_init_menubar; + + # initialize status bar $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1); $self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/"); $self->SetStatusBar($self->{statusbar}); + $self->{loaded} = 1; + + # declare events + EVT_CLOSE($self, sub { + my (undef, $event) = @_; + if ($event->CanVeto && !$self->check_unsaved_changes) { + $event->Veto; + return; + } + $event->Skip; + }); + + # initialize layout + { + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add($self->{tabpanel}, 1, wxEXPAND); + $sizer->SetSizeHints($self); + $self->SetSizer($sizer); + $self->Fit; + $self->SetMinSize($self->GetSize); + $self->Show; + $self->Layout; + } + + return $self; +} + +sub _init_tabpanel { + my ($self) = @_; + + $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater") + unless $self->{no_plater}; + $self->{options_tabs} = {}; + + my $simple_config; + if ($self->{mode} eq 'simple') { + $simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini") + if -e "$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, + on_value_change => sub { + $self->{plater}->on_config_change(@_) if $self->{plater}; # propagate config change events to the plater + 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 + $tab->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode'); + $Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini'; + &Wx::wxTheApp->save_settings; + } + $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; + } + }, + on_presets_changed => sub { + $self->{plater}->update_presets($tab_name, @_) if $self->{plater}; + }, + ); + $panel->AddPage($tab, $tab->title); + $tab->load_config($simple_config) if $simple_config; + } +} + +sub _init_menubar { + my ($self) = @_; + # File menu my $fileMenu = Wx::Menu->new; { @@ -79,25 +166,25 @@ sub new { $fileMenu->Append(wxID_PREFERENCES, "Preferences…", 'Application preferences'); $fileMenu->AppendSeparator(); $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r'); - EVT_MENU($self, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file }); - EVT_MENU($self, MI_LOAD_CONFBUNDLE, sub { $self->{skeinpanel}->load_configbundle }); - EVT_MENU($self, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config }); - EVT_MENU($self, MI_EXPORT_CONFBUNDLE, sub { $self->{skeinpanel}->export_configbundle }); - EVT_MENU($self, MI_QUICK_SLICE, sub { $self->{skeinpanel}->quick_slice; - $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($self, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->quick_slice(reslice => 1) }); - EVT_MENU($self, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->quick_slice(save_as => 1); - $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($self, MI_SLICE_SVG, sub { $self->{skeinpanel}->quick_slice(save_as => 1, export_svg => 1) }); - EVT_MENU($self, MI_REPAIR_STL, sub { $self->{skeinpanel}->repair_stl }); - EVT_MENU($self, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); + EVT_MENU($self, MI_LOAD_CONF, sub { $self->load_config_file }); + EVT_MENU($self, MI_LOAD_CONFBUNDLE, sub { $self->load_configbundle }); + EVT_MENU($self, MI_EXPORT_CONF, sub { $self->export_config }); + EVT_MENU($self, MI_EXPORT_CONFBUNDLE, sub { $self->export_configbundle }); + EVT_MENU($self, MI_QUICK_SLICE, sub { $self->quick_slice; + $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file) }); + EVT_MENU($self, MI_REPEAT_QUICK, sub { $self->quick_slice(reslice => 1) }); + EVT_MENU($self, MI_QUICK_SAVE_AS, sub { $self->quick_slice(save_as => 1); + $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file) }); + EVT_MENU($self, MI_SLICE_SVG, sub { $self->quick_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($self, MI_REPAIR_STL, sub { $self->repair_stl }); + EVT_MENU($self, MI_COMBINE_STLS, sub { $self->combine_stls }); EVT_MENU($self, wxID_PREFERENCES, sub { Slic3r::GUI::Preferences->new($self)->ShowModal }); EVT_MENU($self, wxID_EXIT, sub {$_[0]->Close(0)}); } # Plater menu - unless ($params{no_plater}) { - my $plater = $self->{skeinpanel}{plater}; + unless ($self->{no_plater}) { + my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; $self->{plater_menu}->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code'); @@ -136,15 +223,15 @@ sub new { # Window menu my $windowMenu = Wx::Menu->new; { - my $tab_count = $params{no_plater} ? 3 : 4; - $windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater') unless $params{no_plater}; + my $tab_count = $self->{no_plater} ? 3 : 4; + $windowMenu->Append(MI_TAB_PLATER, "Select &Plater Tab\tCtrl+1", 'Show the plater') unless $self->{no_plater}; $windowMenu->Append(MI_TAB_PRINT, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings'); $windowMenu->Append(MI_TAB_FILAMENT, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings'); $windowMenu->Append(MI_TAB_PRINTER, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings'); - EVT_MENU($self, MI_TAB_PLATER, sub { $self->{skeinpanel}->select_tab(0) }) unless $params{no_plater}; - EVT_MENU($self, MI_TAB_PRINT, sub { $self->{skeinpanel}->select_tab($tab_count-3) }); - EVT_MENU($self, MI_TAB_FILAMENT, sub { $self->{skeinpanel}->select_tab($tab_count-2) }); - EVT_MENU($self, MI_TAB_PRINTER, sub { $self->{skeinpanel}->select_tab($tab_count-1) }); + EVT_MENU($self, MI_TAB_PLATER, sub { $self->select_tab(0) }) unless $self->{no_plater}; + EVT_MENU($self, MI_TAB_PRINT, sub { $self->select_tab($tab_count-3) }); + EVT_MENU($self, MI_TAB_FILAMENT, sub { $self->select_tab($tab_count-2) }); + EVT_MENU($self, MI_TAB_PRINTER, sub { $self->select_tab($tab_count-1) }); } # Help menu @@ -154,15 +241,15 @@ sub new { $helpMenu->AppendSeparator(); $helpMenu->Append(MI_WEBSITE, "Slic3r &Website", 'Open the Slic3r website in your browser'); my $versioncheck = $helpMenu->Append(MI_VERSIONCHECK, "Check for &Updates...", 'Check for new Slic3r versions'); - $versioncheck->Enable(Slic3r::GUI->have_version_check); + $versioncheck->Enable(&Wx::wxTheApp->have_version_check); $helpMenu->Append(MI_DOCUMENTATION, "Slic3r &Manual", 'Open the Slic3r manual in your browser'); $helpMenu->AppendSeparator(); $helpMenu->Append(wxID_ABOUT, "&About Slic3r", 'Show about dialog'); - EVT_MENU($self, MI_CONF_WIZARD, sub { $self->{skeinpanel}->config_wizard }); + EVT_MENU($self, MI_CONF_WIZARD, sub { $self->config_wizard }); EVT_MENU($self, MI_WEBSITE, sub { Wx::LaunchDefaultBrowser('http://slic3r.org/') }); - EVT_MENU($self, MI_VERSIONCHECK, sub { Slic3r::GUI->check_version(manual => 1) }); + EVT_MENU($self, MI_VERSIONCHECK, sub { &Wx::wxTheApp->check_version(manual => 1) }); EVT_MENU($self, MI_DOCUMENTATION, sub { Wx::LaunchDefaultBrowser('http://manual.slic3r.org/') }); - EVT_MENU($self, wxID_ABOUT, \&about); + EVT_MENU($self, wxID_ABOUT, sub { &Wx::wxTheApp->about }); } # menubar @@ -177,22 +264,11 @@ sub new { $menubar->Append($helpMenu, "&Help"); $self->SetMenuBar($menubar); } - - EVT_CLOSE($self, sub { - my (undef, $event) = @_; - if ($event->CanVeto && !$self->{skeinpanel}->check_unsaved_changes) { - $event->Veto; - return; - } - $event->Skip; - }); - - $self->Fit; - $self->SetMinSize($self->GetSize); - $self->Show; - $self->Layout; - - return $self; +} + +sub is_loaded { + my ($self) = @_; + return $self->{loaded}; } sub on_plater_selection_changed { @@ -203,4 +279,464 @@ sub on_plater_selection_changed { for $self->{object_menu}->GetMenuItems; } +sub quick_slice { + my $self = shift; + my %params = @_; + + my $progress_dialog; + eval { + # validate configuration + my $config = $self->config; + $config->validate; + + # select input file + my $input_file; + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; + if (!$params{reslice}) { + my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + return; + } + $input_file = $dialog->GetPaths; + $dialog->Destroy; + $last_input_file = $input_file unless $params{export_svg}; + } else { + if (!defined $last_input_file) { + Wx::MessageDialog->new($self, "No previously sliced file.", + 'Error', wxICON_ERROR | wxOK)->ShowModal(); + return; + } + if (! -e $last_input_file) { + Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.", + 'File Not Found', wxICON_ERROR | wxOK)->ShowModal(); + return; + } + $input_file = $last_input_file; + } + my $input_file_basename = basename($input_file); + $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); + &Wx::wxTheApp->save_settings; + + my $sprint = Slic3r::Print::Simple->new( + status_cb => sub { + my ($percent, $message) = @_; + return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/; + $progress_dialog->Update($percent, "$message…"); + }, + ); + + $sprint->apply_config($config); + $sprint->set_model(Slic3r::Model->read_from_file($input_file)); + + { + my $extra = $self->extra_variables; + $sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; + } + + # select output file + my $output_file; + if ($params{reslice}) { + $output_file = $last_output_file if defined $last_output_file; + } elsif ($params{save_as}) { + $output_file = $sprint->expanded_output_filepath; + $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; + my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', + &Wx::wxTheApp->output_path(dirname($output_file)), + basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return; + } + $output_file = $dlg->GetPath; + $last_output_file = $output_file unless $params{export_svg}; + $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); + &Wx::wxTheApp->save_settings; + $dlg->Destroy; + } + + # show processbar dialog + $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", + 100, $self, 0); + $progress_dialog->Pulse; + + { + my @warnings = (); + local $SIG{__WARN__} = sub { push @warnings, $_[0] }; + + $sprint->output_file($output_file); + if ($params{export_svg}) { + $sprint->export_svg; + } else { + $sprint->export_gcode; + } + $sprint->status_cb(undef); + Slic3r::GUI::warning_catcher($self)->($_) for @warnings; + } + $progress_dialog->Destroy; + undef $progress_dialog; + + my $message = "$input_file_basename was successfully sliced."; + &Wx::wxTheApp->notify($message); + Wx::MessageDialog->new($self, $message, 'Slicing Done!', + wxOK | wxICON_INFORMATION)->ShowModal; + }; + Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); +} + +sub repair_stl { + my $self = shift; + + my $input_file; + { + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; + my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + return; + } + $input_file = $dialog->GetPaths; + $dialog->Destroy; + } + + my $output_file = $input_file; + { + $output_file =~ s/\.stl$/_fixed.obj/i; + my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file), + basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return undef; + } + $output_file = $dlg->GetPath; + $dlg->Destroy; + } + + my $tmesh = Slic3r::TriangleMesh->new; + $tmesh->ReadSTLFile(Slic3r::encode_path($input_file)); + $tmesh->repair; + $tmesh->WriteOBJFile(Slic3r::encode_path($output_file)); + Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); +} + +sub extra_variables { + my $self = shift; + + my %extra_variables = (); + if ($self->{mode} eq 'expert') { + $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name} + for qw(print filament printer); + } + return { %extra_variables }; +} + +sub export_config { + my $self = shift; + + my $config = $self->config; + eval { + # validate configuration + $config->validate; + }; + Slic3r::GUI::catch_error($self) and return; + + my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; + my $filename = $last_config ? basename($last_config) : "config.ini"; + my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, + &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal == wxID_OK) { + my $file = $dlg->GetPath; + $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); + &Wx::wxTheApp->save_settings; + $last_config = $file; + $config->save($file); + } + $dlg->Destroy; +} + +sub load_config_file { + my $self = shift; + 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); + return unless $dlg->ShowModal == wxID_OK; + ($file) = $dlg->GetPaths; + $dlg->Destroy; + } + $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); + &Wx::wxTheApp->save_settings; + $last_config = $file; + for my $tab (values %{$self->{options_tabs}}) { + $tab->load_config_file($file); + } +} + +sub export_configbundle { + my $self = shift; + + eval { + # validate current configuration in case it's dirty + $self->config->validate; + }; + Slic3r::GUI::catch_error($self) and return; + + my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; + my $filename = "Slic3r_config_bundle.ini"; + my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, + &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal == wxID_OK) { + my $file = $dlg->GetPath; + $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); + &Wx::wxTheApp->save_settings; + + # leave default category empty to prevent the bundle from being parsed as a normal config file + my $ini = { _ => {} }; + $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter mode); + $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 = &Wx::wxTheApp->presets($section); + foreach my $preset_name (keys %presets) { + my $config = Slic3r::Config->load($presets{$preset_name}); + $ini->{"$section:$preset_name"} = $config->as_ini->{_}; + } + } + + Slic3r::Config->write_ini($file, $ini); + } + $dlg->Destroy; +} + +sub load_configbundle { + my $self = shift; + + 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); + return unless $dlg->ShowModal == wxID_OK; + my ($file) = $dlg->GetPaths; + $dlg->Destroy; + + $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); + &Wx::wxTheApp->save_settings; + + # load .ini file + my $ini = Slic3r::Config->read_ini($file); + + if ($ini->{settings}) { + $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; + &Wx::wxTheApp->save_settings; + } + if ($ini->{presets}) { + $Slic3r::GUI::Settings->{presets} = $ini->{presets}; + &Wx::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; + foreach my $ini_category (sort keys %$ini) { + next unless $ini_category =~ /^(print|filament|printer):(.+)$/; + my ($section, $preset_name) = ($1, $2); + my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); + $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name); + $imported++; + } + if ($self->{mode} eq 'expert') { + foreach my $tab (values %{$self->{options_tabs}}) { + $tab->load_presets; + } + } + 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); + } +} + +sub load_config { + my $self = shift; + my ($config) = @_; + + foreach my $tab (values %{$self->{options_tabs}}) { + $tab->set_value($_, $config->$_) for @{$config->get_keys}; + } +} + +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; + } + } + $self->load_config($config); + if ($self->{mode} eq 'expert') { + for my $tab (values %{$self->{options_tabs}}) { + $tab->save_preset('My Settings'); + } + } + } +} + +sub combine_stls { + my $self = shift; + + # get input files + my @input_files = (); + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; + { + my $dlg_message = 'Choose one or more files to combine (STL/OBJ)'; + while (1) { + my $dialog = Wx::FileDialog->new($self, "$dlg_message:", $dir, "", &Slic3r::GUI::MODEL_WILDCARD, + wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + last; + } + push @input_files, $dialog->GetPaths; + $dialog->Destroy; + $dlg_message .= " or hit Cancel if you have finished"; + $dir = dirname($input_files[0]); + } + return if !@input_files; + } + + # get output file + my $output_file = $input_files[0]; + { + $output_file =~ s/\.(?:stl|obj)$/.amf.xml/i; + my $dlg = Wx::FileDialog->new($self, 'Save multi-material AMF file as:', dirname($output_file), + basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{amf}, wxFD_SAVE); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return; + } + $output_file = $dlg->GetPath; + } + + my @models = eval { map Slic3r::Model->read_from_file($_), @input_files }; + Slic3r::GUI::show_error($self, $@) if $@; + + my $new_model = Slic3r::Model->new; + my $new_object = $new_model->add_object; + for my $m (0 .. $#models) { + my $model = $models[$m]; + + my $material_name = basename($input_files[$m]); + $material_name =~ s/\.(stl|obj)$//i; + + $new_model->set_material($m, { Name => $material_name }); + $new_object->add_volume( + material_id => $m, + mesh => $model->objects->[0]->volumes->[0]->mesh, + ); + } + + Slic3r::Format::AMF->write_file($output_file, $new_model); +} + +=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; + + # 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 { + # TODO: handle dirty presets. + # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. + my $i = -1; + foreach my $preset_idx ($self->{plater}->filament_presets) { + $i++; + my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); + my $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 support_material support_material_interface); + } + + return $config; +} + +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $tab (values %{$self->{options_tabs}}) { + $changed = 1 if $tab->set_value($opt_key, $value); + } + return $changed; +} + +sub check_unsaved_changes { + my $self = shift; + + my @dirty = map $_->title, grep $_->is_dirty, values %{$self->{options_tabs}}; + 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}->ChangeSelection($tab); +} + 1; diff --git a/lib/Slic3r/GUI/Notifier.pm b/lib/Slic3r/GUI/Notifier.pm new file mode 100644 index 000000000..78d31de3c --- /dev/null +++ b/lib/Slic3r/GUI/Notifier.pm @@ -0,0 +1,41 @@ +package Slic3r::GUI::Notifier; +use Moo; + +has 'growler' => (is => 'rw'); + +my $icon = "$Slic3r::var/Slic3r.png"; + +sub BUILD { + my ($self) = @_; + + if (eval 'use Growl::GNTP; 1') { + # register with growl + eval { + $self->growler(Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $icon)); + $self->growler->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]); + }; + } +} + +sub notify { + my ($self, $message) = @_; + my $title = 'Slicing Done!'; + + eval { + $self->growler->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message) + if $self->growler; + }; + # Net::DBus is broken in multithreaded environment + if (0 && eval 'use Net::DBus; 1') { + eval { + my $session = Net::DBus->session; + my $serv = $session->get_service('org.freedesktop.Notifications'); + my $notifier = $serv->get_object('/org/freedesktop/Notifications', + 'org.freedesktop.Notifications'); + $notifier->Notify('Slic3r', 0, $icon, $title, $message, [], {}, -1); + undef $Net::DBus::bus_session; + }; + } +} + +1; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 69c60114b..b614ab9df 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -208,7 +208,7 @@ sub _build_field { }; EVT_COMBOBOX($self->parent, $field, sub { # Without CallAfter, the field text is not populated on Windows. - Slic3r::GUI->CallAfter(sub { + &Wx::wxTheApp->CallAfter(sub { $field->SetValue($opt->{values}[ $field->GetSelection ]); # set the text field to the selected value $self->_on_change($opt_key, $on_change); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4938d8aef..70fef8b9b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -251,7 +251,7 @@ sub new { { my $presets; - if ($self->skeinpanel->{mode} eq 'expert') { + if ($self->GetFrame->{mode} eq 'expert') { $presets = Wx::BoxSizer->new(wxVERTICAL); my %group_labels = ( print => 'Print settings', @@ -352,23 +352,15 @@ sub on_select_preset { $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0]) . ".ini"; $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_]) for 1 .. $#filament_presets; - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; return; } - $self->skeinpanel->{options_tabs}{$group}->select_preset($choice->GetSelection); + $self->GetFrame->{options_tabs}{$group}->select_preset($choice->GetSelection); } sub GetFrame { my ($self) = @_; - - my $frame = &Wx::GetTopLevelParent($self); - bless $frame, 'Slic3r::GUI::MainFrame'; # Wx returns a generic Wx::Frame object - return $frame; -} - -sub skeinpanel { - my $self = shift; - return $self->GetParent->GetParent; + return &Wx::GetTopLevelParent($self); } sub update_presets { @@ -393,7 +385,7 @@ sub filament_presets { sub add { my $self = shift; - my @input_files = Slic3r::GUI::open_model($self); + my @input_files = &Wx::wxTheApp->open_model($self); $self->load_file($_) for @input_files; } @@ -402,7 +394,7 @@ sub load_file { my ($input_file) = @_; $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0); $process_dialog->Pulse; @@ -636,7 +628,7 @@ sub arrange { ]); eval { - $self->{model}->arrange_objects($self->skeinpanel->config->min_object_distance, $bb); + $self->{model}->arrange_objects($self->GetFrame->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 @@ -721,7 +713,7 @@ sub async_apply_config { $self->suspend_background_process; # apply new config - my $invalidated = $self->{print}->apply_config($self->skeinpanel->config); + my $invalidated = $self->{print}->apply_config($self->GetFrame->config); return if !$Slic3r::GUI::Settings->{_}{background_processing}; @@ -752,14 +744,14 @@ 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->skeinpanel->config->validate; + $self->GetFrame->config->validate; $self->{print}->validate; }; return if $@; # apply extra variables { - my $extra = $self->skeinpanel->extra_variables; + my $extra = $self->GetFrame->extra_variables; $self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; } @@ -838,14 +830,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->skeinpanel->config->validate; + $self->GetFrame->config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print - my $config = $self->skeinpanel->config; + my $config = $self->GetFrame->config; eval { # this will throw errors if config is not valid $config->validate; @@ -860,16 +852,16 @@ sub export_gcode { $self->{export_gcode_output_file} = $main::opt{output}; { my $default_output_file = $self->{print}->expanded_output_filepath($self->{export_gcode_output_file}); - my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', Slic3r::GUI->output_path(dirname($default_output_file)), - basename($default_output_file), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE); + my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', &Wx::wxTheApp->output_path(dirname($default_output_file)), + basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; $self->{export_gcode_output_file} = undef; return; } $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($dlg->GetPath); - Slic3r::GUI->save_settings; - $self->{export_gcode_output_file} = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath; + &Wx::wxTheApp->save_settings; + $self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $dlg->GetPath; $dlg->Destroy; } @@ -1005,12 +997,12 @@ sub _get_export_file { $output_file = $self->{print}->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/$suffix/i; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), - basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; } - $output_file = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath; + $output_file = $Slic3r::GUI::MainFrame::last_output_file = $dlg->GetPath; $dlg->Destroy; } return $output_file; @@ -1100,7 +1092,7 @@ sub on_config_change { $self->update if $opt_key eq 'print_center'; } - return if !$self->skeinpanel->is_loaded; + return if !$self->GetFrame->is_loaded; # (re)start timer $self->schedule_background_process; @@ -1288,7 +1280,7 @@ sub validate_config { my $self = shift; eval { - $self->skeinpanel->config->validate; + $self->GetFrame->config->validate; }; return 0 if Slic3r::GUI::catch_error($self); return 1; @@ -1296,7 +1288,7 @@ sub validate_config { sub statusbar { my $self = shift; - return $self->skeinpanel->GetParent->{statusbar}; + return $self->GetFrame->{statusbar}; } package Slic3r::GUI::Plater::DropTarget; diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index e61ccfcce..46f37a4d7 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -186,7 +186,7 @@ sub selection_changed { sub on_btn_load { my ($self, $is_modifier) = @_; - my @input_files = Slic3r::GUI::open_model($self); + my @input_files = &Wx::wxTheApp->open_model($self); foreach my $input_file (@input_files) { my $model = eval { Slic3r::Model->read_from_file($input_file) }; if ($@) { diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 2f9ee4402..e4dd5958f 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -109,7 +109,7 @@ sub update_optgroup { EVT_BUTTON($self, $btn, sub { $self->{config}->erase($opt_key); $self->{on_change}->() if $self->{on_change}; - Slic3r::GUI->CallAfter(sub { $self->update_optgroup }); + &Wx::wxTheApp->CallAfter(sub { $self->update_optgroup }); }); return $btn; }, diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index a22ab4154..a5abc2414 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -28,7 +28,7 @@ sub new { label => 'Check for updates', tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', default => $Slic3r::GUI::Settings->{_}{version_check} // 1, - readonly => !Slic3r::GUI->have_version_check, + readonly => !&Wx::wxTheApp->have_version_check, }, { opt_key => 'remember_output_path', @@ -77,7 +77,7 @@ sub _accept { } $Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}}; - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; $self->EndModal(wxID_OK); $self->Close; # needed on Linux diff --git a/lib/Slic3r/GUI/ProgressStatusBar.pm b/lib/Slic3r/GUI/ProgressStatusBar.pm new file mode 100644 index 000000000..b901740a9 --- /dev/null +++ b/lib/Slic3r/GUI/ProgressStatusBar.pm @@ -0,0 +1,142 @@ +package Slic3r::GUI::ProgressStatusBar; +use strict; +use warnings; + +use Wx qw(:gauge :misc); +use base 'Wx::StatusBar'; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{busy} = 0; + $self->{timer} = Wx::Timer->new($self); + $self->{prog} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize); + $self->{prog}->Hide; + $self->{cancelbutton} = Wx::Button->new($self, -1, "Cancel", wxDefaultPosition, wxDefaultSize); + $self->{cancelbutton}->Hide; + + $self->SetFieldsCount(3); + $self->SetStatusWidths(-1, 150, 155); + + Wx::Event::EVT_TIMER($self, \&OnTimer, $self->{timer}); + Wx::Event::EVT_SIZE($self, \&OnSize); + Wx::Event::EVT_BUTTON($self, $self->{cancelbutton}, sub { + $self->{cancel_cb}->(); + $self->{cancelbutton}->Hide; + }); + + return $self; +} + +sub DESTROY { + my $self = shift; + $self->{timer}->Stop if $self->{timer} && $self->{timer}->IsRunning; +} + +sub OnSize { + my ($self, $event) = @_; + + my %fields = ( + # 0 is reserved for status text + 1 => $self->{cancelbutton}, + 2 => $self->{prog}, + ); + + foreach (keys %fields) { + my $rect = $self->GetFieldRect($_); + my $offset = &Wx::wxGTK ? 1 : 0; # add a cosmetic 1 pixel offset on wxGTK + my $pos = [$rect->GetX + $offset, $rect->GetY + $offset]; + $fields{$_}->Move($pos); + $fields{$_}->SetSize($rect->GetWidth - $offset, $rect->GetHeight); + } + + $event->Skip; +} + +sub OnTimer { + my ($self, $event) = @_; + + if ($self->{prog}->IsShown) { + $self->{timer}->Stop; + } + $self->{prog}->Pulse if $self->{_busy}; +} + +sub SetCancelCallback { + my $self = shift; + my ($cb) = @_; + $self->{cancel_cb} = $cb; + $cb ? $self->{cancelbutton}->Show : $self->{cancelbutton}->Hide; +} + +sub Run { + my $self = shift; + my $rate = shift || 100; + if (!$self->{timer}->IsRunning) { + $self->{timer}->Start($rate); + } +} + +sub GetProgress { + my $self = shift; + return $self->{prog}->GetValue; +} + +sub SetProgress { + my $self = shift; + my ($val) = @_; + if (!$self->{prog}->IsShown) { + $self->ShowProgress(1); + } + if ($val == $self->{prog}->GetRange) { + $self->{prog}->SetValue(0); + $self->ShowProgress(0); + } else { + $self->{prog}->SetValue($val); + } +} + +sub SetRange { + my $self = shift; + my ($val) = @_; + + if ($val != $self->{prog}->GetRange) { + $self->{prog}->SetRange($val); + } +} + +sub ShowProgress { + my $self = shift; + my ($show) = @_; + + $self->{prog}->Show($show); + $self->{prog}->Pulse; +} + +sub StartBusy { + my $self = shift; + my $rate = shift || 100; + + $self->{_busy} = 1; + $self->ShowProgress(1); + if (!$self->{timer}->IsRunning) { + $self->{timer}->Start($rate); + } +} + +sub StopBusy { + my $self = shift; + + $self->{timer}->Stop; + $self->ShowProgress(0); + $self->{prog}->SetValue(0); + $self->{_busy} = 0; +} + +sub IsBusy { + my $self = shift; + return $self->{_busy}; +} + +1; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm deleted file mode 100644 index e489394a4..000000000 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ /dev/null @@ -1,554 +0,0 @@ -package Slic3r::GUI::SkeinPanel; -use strict; -use warnings; -use utf8; - -use File::Basename qw(basename dirname); -use List::Util qw(min); -use Slic3r::Geometry qw(X Y); -use Wx qw(:dialog :filedialog :font :icon :id :misc :notebook :panel :sizer); -use Wx::Event qw(EVT_BUTTON); -use base 'Wx::Panel'; - -our $last_input_file; -our $last_output_file; -our $last_config; - -use constant FILE_WILDCARDS => { - known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML', - stl => 'STL files (*.stl)|*.stl;*.STL', - obj => 'OBJ files (*.obj)|*.obj;*.OBJ', - amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', - ini => 'INI files *.ini|*.ini;*.INI', - gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC', - svg => 'SVG files *.svg|*.svg;*.SVG', -}; -use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)}; - -sub new { - my $class = shift; - my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{mode} = $params{mode}; - $self->{mode} = 'expert' if $self->{mode} !~ /^(?:simple|expert)$/; - $self->{loaded} = 0; - - $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); - $self->{tabpanel}->AddPage($self->{plater} = Slic3r::GUI::Plater->new($self->{tabpanel}), "Plater") - unless $params{no_plater}; - $self->{options_tabs} = {}; - - my $simple_config; - if ($self->{mode} eq 'simple') { - $simple_config = Slic3r::Config->load("$Slic3r::GUI::datadir/simple.ini") - if -e "$Slic3r::GUI::datadir/simple.ini"; - } - - my $class_prefix = $self->{mode} eq 'simple' ? "Slic3r::GUI::SimpleTab::" : "Slic3r::GUI::Tab::"; - my $init = 0; - for my $tab_name (qw(print filament printer)) { - my $tab; - $tab = $self->{options_tabs}{$tab_name} = ($class_prefix . ucfirst $tab_name)->new( - $self->{tabpanel}, - on_value_change => sub { - $self->{plater}->on_config_change(@_) if $self->{plater}; # propagate config change events to the plater - if ($init) { # 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 - $tab->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $tab->name, 'Simple Mode'); - $Slic3r::GUI::Settings->{presets}{$tab->name} = 'Simple Mode.ini'; - Slic3r::GUI->save_settings; - } - $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; - } - }, - on_presets_changed => sub { - $self->{plater}->update_presets($tab_name, @_) if $self->{plater}; - }, - ); - $self->{tabpanel}->AddPage($tab, $tab->title); - $tab->load_config($simple_config) if $simple_config; - } - $init = 1; - - my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($self->{tabpanel}, 1, wxEXPAND); - - $sizer->SetSizeHints($self); - $self->SetSizer($sizer); - $self->Layout; - - $self->{loaded} = 1; - return $self; -} - -sub is_loaded { - my ($self) = @_; - return $self->{loaded}; -} - -sub quick_slice { - my $self = shift; - my %params = @_; - - my $progress_dialog; - eval { - # validate configuration - my $config = $self->config; - $config->validate; - - # select input file - my $input_file; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - if (!$params{reslice}) { - my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - $input_file = $dialog->GetPaths; - $dialog->Destroy; - $last_input_file = $input_file unless $params{export_svg}; - } else { - if (!defined $last_input_file) { - Wx::MessageDialog->new($self, "No previously sliced file.", - 'Error', wxICON_ERROR | wxOK)->ShowModal(); - return; - } - if (! -e $last_input_file) { - Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.", - 'File Not Found', wxICON_ERROR | wxOK)->ShowModal(); - return; - } - $input_file = $last_input_file; - } - my $input_file_basename = basename($input_file); - $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); - Slic3r::GUI->save_settings; - - my $sprint = Slic3r::Print::Simple->new( - status_cb => sub { - my ($percent, $message) = @_; - return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/; - $progress_dialog->Update($percent, "$message…"); - }, - ); - - $sprint->apply_config($config); - $sprint->set_model(Slic3r::Model->read_from_file($input_file)); - - { - my $extra = $self->extra_variables; - $sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; - } - - # select output file - my $output_file; - if ($params{reslice}) { - $output_file = $last_output_file if defined $last_output_file; - } elsif ($params{save_as}) { - $output_file = $sprint->expanded_output_filepath; - $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; - my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', - Slic3r::GUI->output_path(dirname($output_file)), - basename($output_file), $params{export_svg} ? FILE_WILDCARDS->{svg} : FILE_WILDCARDS->{gcode}, wxFD_SAVE); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return; - } - $output_file = $dlg->GetPath; - $last_output_file = $output_file unless $params{export_svg}; - $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); - Slic3r::GUI->save_settings; - $dlg->Destroy; - } - - # show processbar dialog - $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", - 100, $self, 0); - $progress_dialog->Pulse; - - { - my @warnings = (); - local $SIG{__WARN__} = sub { push @warnings, $_[0] }; - - $sprint->output_file($output_file); - if ($params{export_svg}) { - $sprint->export_svg; - } else { - $sprint->export_gcode; - } - $sprint->status_cb(undef); - Slic3r::GUI::warning_catcher($self)->($_) for @warnings; - } - $progress_dialog->Destroy; - undef $progress_dialog; - - my $message = "$input_file_basename was successfully sliced."; - &Wx::wxTheApp->notify($message); - Wx::MessageDialog->new($self, $message, 'Slicing Done!', - wxOK | wxICON_INFORMATION)->ShowModal; - }; - Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); -} - -sub repair_stl { - my $self = shift; - - my $input_file; - { - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - $input_file = $dialog->GetPaths; - $dialog->Destroy; - } - - my $output_file = $input_file; - { - $output_file =~ s/\.stl$/_fixed.obj/i; - my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file), - basename($output_file), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return undef; - } - $output_file = $dlg->GetPath; - $dlg->Destroy; - } - - my $tmesh = Slic3r::TriangleMesh->new; - $tmesh->ReadSTLFile(Slic3r::encode_path($input_file)); - $tmesh->repair; - $tmesh->WriteOBJFile(Slic3r::encode_path($output_file)); - Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); -} - -sub extra_variables { - my $self = shift; - - my %extra_variables = (); - if ($self->{mode} eq 'expert') { - $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->current_preset->{name} - for qw(print filament printer); - } - return { %extra_variables }; -} - -sub export_config { - my $self = shift; - - my $config = $self->config; - eval { - # validate configuration - $config->validate; - }; - Slic3r::GUI::catch_error($self) and return; - - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = $last_config ? basename($last_config) : "config.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, - FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - Slic3r::GUI->save_settings; - $last_config = $file; - $config->save($file); - } - $dlg->Destroy; -} - -sub load_config_file { - my $self = shift; - 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", - FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - return unless $dlg->ShowModal == wxID_OK; - ($file) = $dlg->GetPaths; - $dlg->Destroy; - } - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - Slic3r::GUI->save_settings; - $last_config = $file; - for my $tab (values %{$self->{options_tabs}}) { - $tab->load_config_file($file); - } -} - -sub export_configbundle { - my $self = shift; - - eval { - # validate current configuration in case it's dirty - $self->config->validate; - }; - Slic3r::GUI::catch_error($self) and return; - - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = "Slic3r_config_bundle.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, - FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - Slic3r::GUI->save_settings; - - # leave default category empty to prevent the bundle from being parsed as a normal config file - my $ini = { _ => {} }; - $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter mode); - $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 = Slic3r::GUI->presets($section); - foreach my $preset_name (keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); - $ini->{"$section:$preset_name"} = $config->as_ini->{_}; - } - } - - Slic3r::Config->write_ini($file, $ini); - } - $dlg->Destroy; -} - -sub load_configbundle { - my $self = shift; - - 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", - FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - return unless $dlg->ShowModal == wxID_OK; - my ($file) = $dlg->GetPaths; - $dlg->Destroy; - - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - Slic3r::GUI->save_settings; - - # load .ini file - my $ini = Slic3r::Config->read_ini($file); - - if ($ini->{settings}) { - $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; - Slic3r::GUI->save_settings; - } - if ($ini->{presets}) { - $Slic3r::GUI::Settings->{presets} = $ini->{presets}; - Slic3r::GUI->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; - foreach my $ini_category (sort keys %$ini) { - next unless $ini_category =~ /^(print|filament|printer):(.+)$/; - my ($section, $preset_name) = ($1, $2); - my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); - $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name); - $imported++; - } - if ($self->{mode} eq 'expert') { - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; - } - } - 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); - } -} - -sub load_config { - my $self = shift; - my ($config) = @_; - - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->set_value($_, $config->$_) for @{$config->get_keys}; - } -} - -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; - } - } - $self->load_config($config); - if ($self->{mode} eq 'expert') { - for my $tab (values %{$self->{options_tabs}}) { - $tab->save_preset('My Settings'); - } - } - } -} - -sub combine_stls { - my $self = shift; - - # get input files - my @input_files = (); - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - { - my $dlg_message = 'Choose one or more files to combine (STL/OBJ)'; - while (1) { - my $dialog = Wx::FileDialog->new($self, "$dlg_message:", $dir, "", MODEL_WILDCARD, - wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - last; - } - push @input_files, $dialog->GetPaths; - $dialog->Destroy; - $dlg_message .= " or hit Cancel if you have finished"; - $dir = dirname($input_files[0]); - } - return if !@input_files; - } - - # get output file - my $output_file = $input_files[0]; - { - $output_file =~ s/\.(?:stl|obj)$/.amf.xml/i; - my $dlg = Wx::FileDialog->new($self, 'Save multi-material AMF file as:', dirname($output_file), - basename($output_file), FILE_WILDCARDS->{amf}, wxFD_SAVE); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return; - } - $output_file = $dlg->GetPath; - } - - my @models = eval { map Slic3r::Model->read_from_file($_), @input_files }; - Slic3r::GUI::show_error($self, $@) if $@; - - my $new_model = Slic3r::Model->new; - my $new_object = $new_model->add_object; - for my $m (0 .. $#models) { - my $model = $models[$m]; - - my $material_name = basename($input_files[$m]); - $material_name =~ s/\.(stl|obj)$//i; - - $new_model->set_material($m, { Name => $material_name }); - $new_object->add_volume( - material_id => $m, - mesh => $model->objects->[0]->volumes->[0]->mesh, - ); - } - - Slic3r::Format::AMF->write_file($output_file, $new_model); -} - -=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; - - # 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 { - # TODO: handle dirty presets. - # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. - my $i = -1; - foreach my $preset_idx ($self->{plater}->filament_presets) { - $i++; - my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); - my $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 support_material support_material_interface); - } - - return $config; -} - -sub set_value { - my $self = shift; - my ($opt_key, $value) = @_; - - my $changed = 0; - foreach my $tab (values %{$self->{options_tabs}}) { - $changed = 1 if $tab->set_value($opt_key, $value); - } - return $changed; -} - -sub check_unsaved_changes { - my $self = shift; - - my @dirty = map $_->title, grep $_->is_dirty, values %{$self->{options_tabs}}; - 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}->ChangeSelection($tab); -} - -1; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index fc7545941..6de7b908b 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -217,7 +217,7 @@ sub on_select_preset { $self->select_default_preset; } - Slic3r::GUI->save_settings; + &Wx::wxTheApp->save_settings; } sub get_preset_config { @@ -347,7 +347,7 @@ sub load_presets { name => '- default -', }]; - my %presets = Slic3r::GUI->presets($self->name); + my %presets = &Wx::wxTheApp->presets($self->name); foreach my $preset_name (sort keys %presets) { push @{$self->{presets}}, { name => $preset_name, diff --git a/slic3r.pl b/slic3r.pl index 0baef80ec..471e546d4 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -96,8 +96,8 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") { } $gui = Slic3r::GUI->new; setlocale(LC_NUMERIC, 'C'); - $gui->{skeinpanel}->load_config_file($_) for @{$opt{load}}; - $gui->{skeinpanel}->load_config($cli_config); + $gui->{mainframe}->load_config_file($_) for @{$opt{load}}; + $gui->{mainframe}->load_config($cli_config); $gui->MainLoop; exit; }