GUI.pm 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package Slic3r::GUI;
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use File::Basename qw(basename);
  6. use FindBin;
  7. use Slic3r::GUI::AboutDialog;
  8. use Slic3r::GUI::BedShapeDialog;
  9. use Slic3r::GUI::ConfigWizard;
  10. use Slic3r::GUI::MainFrame;
  11. use Slic3r::GUI::Notifier;
  12. use Slic3r::GUI::Plater;
  13. use Slic3r::GUI::Plater::2D;
  14. use Slic3r::GUI::Plater::ObjectPartsPanel;
  15. use Slic3r::GUI::Plater::ObjectCutDialog;
  16. use Slic3r::GUI::Plater::ObjectPreviewDialog;
  17. use Slic3r::GUI::Plater::ObjectSettingsDialog;
  18. use Slic3r::GUI::Plater::OverrideSettingsPanel;
  19. use Slic3r::GUI::Preferences;
  20. use Slic3r::GUI::ProgressStatusBar;
  21. use Slic3r::GUI::OptionsGroup;
  22. use Slic3r::GUI::SimpleTab;
  23. use Slic3r::GUI::Tab;
  24. our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1";
  25. use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
  26. :filedialog);
  27. use Wx::Event qw(EVT_IDLE);
  28. use base 'Wx::App';
  29. use constant FILE_WILDCARDS => {
  30. known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
  31. stl => 'STL files (*.stl)|*.stl;*.STL',
  32. obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
  33. amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
  34. ini => 'INI files *.ini|*.ini;*.INI',
  35. gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
  36. svg => 'SVG files *.svg|*.svg;*.SVG',
  37. };
  38. use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)};
  39. our $datadir;
  40. our $no_plater;
  41. our $mode;
  42. our $autosave;
  43. our @cb;
  44. our $Settings = {
  45. _ => {
  46. mode => 'simple',
  47. version_check => 1,
  48. autocenter => 1,
  49. background_processing => 1,
  50. },
  51. };
  52. our $have_button_icons = &Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/;
  53. our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  54. $small_font->SetPointSize(11) if !&Wx::wxMSW;
  55. our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  56. $medium_font->SetPointSize(12);
  57. sub OnInit {
  58. my ($self) = @_;
  59. $self->SetAppName('Slic3r');
  60. Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
  61. $self->{notifier} = Slic3r::GUI::Notifier->new;
  62. # locate or create data directory
  63. $datadir ||= Wx::StandardPaths::Get->GetUserDataDir;
  64. $datadir = Slic3r::encode_path($datadir);
  65. Slic3r::debugf "Data directory: %s\n", $datadir;
  66. # just checking for existence of $datadir is not enough: it may be an empty directory
  67. # supplied as argument to --datadir; in that case we should still run the wizard
  68. my $run_wizard = (-d $datadir && -e "$datadir/slic3r.ini") ? 0 : 1;
  69. for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") {
  70. mkdir or $self->fatal_error("Slic3r was unable to create its data directory at $_ (errno: $!).")
  71. unless -d $_;
  72. }
  73. # load settings
  74. my $last_version;
  75. if (-f "$datadir/slic3r.ini") {
  76. my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") };
  77. $Settings = $ini if $ini;
  78. $last_version = $Settings->{_}{version};
  79. $Settings->{_}{mode} ||= 'expert';
  80. $Settings->{_}{autocenter} //= 1;
  81. $Settings->{_}{background_processing} //= 1;
  82. }
  83. $Settings->{_}{version} = $Slic3r::VERSION;
  84. $self->save_settings;
  85. # application frame
  86. Wx::Image::AddHandler(Wx::PNGHandler->new);
  87. $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
  88. mode => $mode // $Settings->{_}{mode},
  89. no_plater => $no_plater,
  90. );
  91. $self->SetTopWindow($frame);
  92. if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) {
  93. # user was running another Slic3r version on this computer
  94. if (!defined $last_version || $last_version =~ /^0\./) {
  95. show_info($self->{mainframe}, "Hello! Support material was improved since the "
  96. . "last version of Slic3r you used. It is strongly recommended to revert "
  97. . "your support material settings to the factory defaults and start from "
  98. . "those. Enjoy and provide feedback!", "Support Material");
  99. }
  100. }
  101. $self->{mainframe}->config_wizard if $run_wizard;
  102. $self->check_version
  103. if $self->have_version_check
  104. && ($Settings->{_}{version_check} // 1)
  105. && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400);
  106. EVT_IDLE($frame, sub {
  107. while (my $cb = shift @cb) {
  108. $cb->();
  109. }
  110. });
  111. return 1;
  112. }
  113. sub about {
  114. my ($self) = @_;
  115. my $about = Slic3r::GUI::AboutDialog->new(undef);
  116. $about->ShowModal;
  117. $about->Destroy;
  118. }
  119. # static method accepting a wxWindow object as first parameter
  120. sub catch_error {
  121. my ($self, $cb, $message_dialog) = @_;
  122. if (my $err = $@) {
  123. $cb->() if $cb;
  124. $message_dialog
  125. ? $message_dialog->($err, 'Error', wxOK | wxICON_ERROR)
  126. : Slic3r::GUI::show_error($self, $err);
  127. return 1;
  128. }
  129. return 0;
  130. }
  131. # static method accepting a wxWindow object as first parameter
  132. sub show_error {
  133. my $self = shift;
  134. my ($message) = @_;
  135. Wx::MessageDialog->new($self, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal;
  136. }
  137. # static method accepting a wxWindow object as first parameter
  138. sub show_info {
  139. my $self = shift;
  140. my ($message, $title) = @_;
  141. Wx::MessageDialog->new($self, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
  142. }
  143. # static method accepting a wxWindow object as first parameter
  144. sub fatal_error {
  145. my $self = shift;
  146. $self->show_error(@_);
  147. exit 1;
  148. }
  149. # static method accepting a wxWindow object as first parameter
  150. sub warning_catcher {
  151. my ($self, $message_dialog) = @_;
  152. return sub {
  153. my $message = shift;
  154. return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/;
  155. my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
  156. $message_dialog
  157. ? $message_dialog->(@params)
  158. : Wx::MessageDialog->new($self, @params)->ShowModal;
  159. };
  160. }
  161. sub notify {
  162. my ($self, $message) = @_;
  163. my $frame = $self->GetTopWindow;
  164. # try harder to attract user attention on OS X
  165. $frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO)
  166. unless ($frame->IsActive);
  167. $self->{notifier}->notify($message);
  168. }
  169. sub save_settings {
  170. my ($self) = @_;
  171. Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings);
  172. }
  173. sub presets {
  174. my ($self, $section) = @_;
  175. my %presets = ();
  176. opendir my $dh, "$Slic3r::GUI::datadir/$section" or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n";
  177. foreach my $file (grep /\.ini$/i, readdir $dh) {
  178. my $name = basename($file);
  179. $name =~ s/\.ini$//;
  180. $presets{$name} = "$Slic3r::GUI::datadir/$section/$file";
  181. }
  182. closedir $dh;
  183. return %presets;
  184. }
  185. sub have_version_check {
  186. my ($self) = @_;
  187. # return an explicit 0
  188. return ($Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1") || 0;
  189. }
  190. sub check_version {
  191. my ($self, %p) = @_;
  192. Slic3r::debugf "Checking for updates...\n";
  193. @_ = ();
  194. threads->create(sub {
  195. my $ua = LWP::UserAgent->new;
  196. $ua->timeout(10);
  197. my $response = $ua->get('http://slic3r.org/updatecheck');
  198. if ($response->is_success) {
  199. if ($response->decoded_content =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
  200. my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
  201. 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
  202. Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
  203. } else {
  204. Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual};
  205. }
  206. $Settings->{_}{last_version_check} = time();
  207. $self->save_settings;
  208. } else {
  209. Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
  210. }
  211. Slic3r::thread_cleanup();
  212. })->detach;
  213. }
  214. sub output_path {
  215. my ($self, $dir) = @_;
  216. return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path})
  217. ? $Settings->{_}{last_output_path}
  218. : $dir;
  219. }
  220. sub open_model {
  221. my ($self, $window) = @_;
  222. my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory}
  223. || $Slic3r::GUI::Settings->{recent}{config_directory}
  224. || '';
  225. my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "",
  226. MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
  227. if ($dialog->ShowModal != wxID_OK) {
  228. $dialog->Destroy;
  229. return;
  230. }
  231. my @input_files = $dialog->GetPaths;
  232. $dialog->Destroy;
  233. return @input_files;
  234. }
  235. sub CallAfter {
  236. my ($self, $cb) = @_;
  237. push @cb, $cb;
  238. }
  239. 1;