GUI.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. package Slic3r::GUI;
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use File::Basename qw(basename);
  6. use FindBin;
  7. use List::Util qw(first);
  8. use Slic3r::GUI::MainFrame;
  9. use Slic3r::GUI::Plater;
  10. use Slic3r::GUI::Plater::3D;
  11. use Slic3r::GUI::Plater::3DPreview;
  12. use Wx::Locale gettext => 'L';
  13. our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
  14. use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font);
  15. use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU);
  16. use base 'Wx::App';
  17. use constant FILE_WILDCARDS => {
  18. known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.zip.amf;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA',
  19. stl => 'STL files (*.stl)|*.stl;*.STL',
  20. obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
  21. amf => 'AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML',
  22. threemf => '3MF files (*.3mf)|*.3mf;*.3MF',
  23. prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA',
  24. ini => 'INI files *.ini|*.ini;*.INI',
  25. gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
  26. };
  27. use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf threemf prusa)};
  28. # Datadir provided on the command line.
  29. our $datadir;
  30. our $no_plater;
  31. our @cb;
  32. our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  33. $small_font->SetPointSize(11) if &Wx::wxMAC;
  34. our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  35. $small_bold_font->SetPointSize(11) if &Wx::wxMAC;
  36. $small_bold_font->SetWeight(wxFONTWEIGHT_BOLD);
  37. our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
  38. $medium_font->SetPointSize(12);
  39. our $grey = Wx::Colour->new(200,200,200);
  40. # Events to be sent from a C++ menu implementation:
  41. # 1) To inform about a change of the application language.
  42. our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType;
  43. # 2) To inform about a change of Preferences.
  44. our $PREFERENCES_EVENT = Wx::NewEventType;
  45. # To inform AppConfig about Slic3r version available online
  46. our $VERSION_ONLINE_EVENT = Wx::NewEventType;
  47. sub OnInit {
  48. my ($self) = @_;
  49. $self->SetAppName('Slic3rPE');
  50. $self->SetAppDisplayName('Slic3r Prusa Edition');
  51. Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
  52. # Set the Slic3r data directory at the Slic3r XS module.
  53. # Unix: ~/.Slic3r
  54. # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
  55. # Mac: "~/Library/Application Support/Slic3r"
  56. Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir);
  57. Slic3r::GUI::set_wxapp($self);
  58. $self->{app_config} = Slic3r::GUI::AppConfig->new;
  59. Slic3r::GUI::set_app_config($self->{app_config});
  60. $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
  61. Slic3r::GUI::set_preset_bundle($self->{preset_bundle});
  62. # just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory
  63. # supplied as argument to --datadir; in that case we should still run the wizard
  64. eval { $self->{preset_bundle}->setup_directories() };
  65. if ($@) {
  66. warn $@ . "\n";
  67. fatal_error(undef, $@);
  68. }
  69. my $app_conf_exists = $self->{app_config}->exists;
  70. # load settings
  71. $self->{app_config}->load if $app_conf_exists;
  72. $self->{app_config}->set('version', $Slic3r::VERSION);
  73. $self->{app_config}->save;
  74. $self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT);
  75. Slic3r::GUI::set_preset_updater($self->{preset_updater});
  76. Slic3r::GUI::load_language();
  77. # Suppress the '- default -' presets.
  78. $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0);
  79. eval { $self->{preset_bundle}->load_presets($self->{app_config}); };
  80. if ($@) {
  81. warn $@ . "\n";
  82. show_error(undef, $@);
  83. }
  84. # application frame
  85. print STDERR "Creating main frame...\n";
  86. Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new);
  87. $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
  88. no_plater => $no_plater,
  89. lang_ch_event => $LANGUAGE_CHANGE_EVENT,
  90. preferences_event => $PREFERENCES_EVENT,
  91. );
  92. $self->SetTopWindow($frame);
  93. # This makes CallAfter() work
  94. EVT_IDLE($self->{mainframe}, sub {
  95. while (my $cb = shift @cb) {
  96. $cb->();
  97. }
  98. $self->{app_config}->save if $self->{app_config}->dirty;
  99. });
  100. # On OS X the UI tends to freeze in weird ways if modal dialogs (config wizard, update notifications, ...)
  101. # are shown before or in the same event callback with the main frame creation.
  102. # Therefore we schedule them for later using CallAfter.
  103. $self->CallAfter(sub {
  104. eval {
  105. if (! $self->{preset_updater}->config_update()) {
  106. $self->{mainframe}->Close;
  107. }
  108. };
  109. if ($@) {
  110. show_error(undef, $@);
  111. $self->{mainframe}->Close;
  112. }
  113. });
  114. $self->CallAfter(sub {
  115. if (! Slic3r::GUI::config_wizard_startup($app_conf_exists)) {
  116. # Only notify if there was not wizard so as not to bother too much ...
  117. $self->{preset_updater}->slic3r_update_notify();
  118. }
  119. $self->{preset_updater}->sync($self->{preset_bundle});
  120. });
  121. # The following event is emited by the C++ menu implementation of application language change.
  122. EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{
  123. print STDERR "LANGUAGE_CHANGE_EVENT\n";
  124. $self->recreate_GUI;
  125. });
  126. # The following event is emited by the C++ menu implementation of preferences change.
  127. EVT_COMMAND($self, -1, $PREFERENCES_EVENT, sub{
  128. $self->update_ui_from_settings;
  129. });
  130. # The following event is emited by PresetUpdater (C++) to inform about
  131. # the newer Slic3r application version avaiable online.
  132. EVT_COMMAND($self, -1, $VERSION_ONLINE_EVENT, sub {
  133. my ($self, $event) = @_;
  134. my $version = $event->GetString;
  135. $self->{app_config}->set('version_online', $version);
  136. $self->{app_config}->save;
  137. });
  138. return 1;
  139. }
  140. sub recreate_GUI{
  141. print STDERR "recreate_GUI\n";
  142. my ($self) = @_;
  143. my $topwindow = $self->GetTopWindow();
  144. $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
  145. no_plater => $no_plater,
  146. lang_ch_event => $LANGUAGE_CHANGE_EVENT,
  147. preferences_event => $PREFERENCES_EVENT,
  148. );
  149. if($topwindow)
  150. {
  151. $self->SetTopWindow($frame);
  152. $topwindow->Destroy;
  153. }
  154. EVT_IDLE($self->{mainframe}, sub {
  155. while (my $cb = shift @cb) {
  156. $cb->();
  157. }
  158. $self->{app_config}->save if $self->{app_config}->dirty;
  159. });
  160. # On OSX the UI was not initialized correctly if the wizard was called
  161. # before the UI was up and running.
  162. $self->CallAfter(sub {
  163. # Run the config wizard, don't offer the "reset user profile" checkbox.
  164. Slic3r::GUI::config_wizard_startup(1);
  165. });
  166. }
  167. sub system_info {
  168. my ($self) = @_;
  169. my $slic3r_info = Slic3r::slic3r_info(format => 'html');
  170. my $copyright_info = Slic3r::copyright_info(format => 'html');
  171. my $system_info = Slic3r::system_info(format => 'html');
  172. my $opengl_info;
  173. my $opengl_info_txt = '';
  174. if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) &&
  175. defined($self->{mainframe}->{plater}->{canvas3D})) {
  176. $opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1);
  177. $opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1);
  178. }
  179. # my $about = Slic3r::GUI::SystemInfo->new(
  180. # parent => undef,
  181. # slic3r_info => $slic3r_info,
  182. # system_info => $system_info,
  183. # opengl_info => $opengl_info,
  184. # text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt,
  185. # );
  186. # $about->ShowModal;
  187. # $about->Destroy;
  188. }
  189. # static method accepting a wxWindow object as first parameter
  190. sub catch_error {
  191. my ($self, $cb, $message_dialog) = @_;
  192. if (my $err = $@) {
  193. $cb->() if $cb;
  194. $message_dialog
  195. ? $message_dialog->($err, 'Error', wxOK | wxICON_ERROR)
  196. : Slic3r::GUI::show_error($self, $err);
  197. return 1;
  198. }
  199. return 0;
  200. }
  201. # static method accepting a wxWindow object as first parameter
  202. sub show_error {
  203. my ($parent, $message) = @_;
  204. Slic3r::GUI::show_error_id($parent ? $parent->GetId() : 0, $message);
  205. }
  206. # static method accepting a wxWindow object as first parameter
  207. sub show_info {
  208. my ($parent, $message, $title) = @_;
  209. Wx::MessageDialog->new($parent, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
  210. }
  211. # static method accepting a wxWindow object as first parameter
  212. sub fatal_error {
  213. show_error(@_);
  214. exit 1;
  215. }
  216. # static method accepting a wxWindow object as first parameter
  217. sub warning_catcher {
  218. my ($self, $message_dialog) = @_;
  219. return sub {
  220. my $message = shift;
  221. return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/;
  222. my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
  223. $message_dialog
  224. ? $message_dialog->(@params)
  225. : Wx::MessageDialog->new($self, @params)->ShowModal;
  226. };
  227. }
  228. sub notify {
  229. my ($self, $message) = @_;
  230. my $frame = $self->GetTopWindow;
  231. # try harder to attract user attention on OS X
  232. $frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO)
  233. unless ($frame->IsActive);
  234. # There used to be notifier using a Growl application for OSX, but Growl is dead.
  235. # The notifier also supported the Linux X D-bus notifications, but that support was broken.
  236. #TODO use wxNotificationMessage?
  237. }
  238. # Called after the Preferences dialog is closed and the program settings are saved.
  239. # Update the UI based on the current preferences.
  240. sub update_ui_from_settings {
  241. my ($self) = @_;
  242. $self->{mainframe}->update_ui_from_settings;
  243. }
  244. sub open_model {
  245. my ($self, $window) = @_;
  246. my $dlg_title = L('Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):');
  247. my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, $dlg_title,
  248. $self->{app_config}->get_last_dir, "",
  249. MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
  250. if ($dialog->ShowModal != wxID_OK) {
  251. $dialog->Destroy;
  252. return;
  253. }
  254. my @input_files = $dialog->GetPaths;
  255. $dialog->Destroy;
  256. return @input_files;
  257. }
  258. sub CallAfter {
  259. my ($self, $cb) = @_;
  260. push @cb, $cb;
  261. }
  262. sub append_menu_item {
  263. my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_;
  264. $id //= &Wx::NewId();
  265. my $item = Wx::MenuItem->new($menu, $id, $string, $description // '', $kind // 0);
  266. $self->set_menu_item_icon($item, $icon);
  267. $menu->Append($item);
  268. EVT_MENU($self, $id, $cb);
  269. return $item;
  270. }
  271. sub append_submenu {
  272. my ($self, $menu, $string, $description, $submenu, $id, $icon) = @_;
  273. $id //= &Wx::NewId();
  274. my $item = Wx::MenuItem->new($menu, $id, $string, $description // '');
  275. $self->set_menu_item_icon($item, $icon);
  276. $item->SetSubMenu($submenu);
  277. $menu->Append($item);
  278. return $item;
  279. }
  280. sub set_menu_item_icon {
  281. my ($self, $menuItem, $icon) = @_;
  282. # SetBitmap was not available on OS X before Wx 0.9927
  283. if ($icon && $menuItem->can('SetBitmap')) {
  284. $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG));
  285. }
  286. }
  287. 1;