MainFrame.pm 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. # The main frame, the parent of all.
  2. package Slic3r::GUI::MainFrame;
  3. use strict;
  4. use warnings;
  5. use utf8;
  6. use File::Basename qw(basename dirname);
  7. use List::Util qw(min);
  8. use Slic3r::Geometry qw(X Y Z);
  9. use Wx qw(:frame :bitmap :id :misc :panel :sizer :menu :dialog :filedialog
  10. :font :icon :aui wxTheApp);
  11. use Wx::AUI;
  12. use Wx::Event qw(EVT_CLOSE EVT_AUINOTEBOOK_PAGE_CHANGED EVT_AUINOTEBOOK_PAGE_CLOSE);
  13. use base 'Wx::Frame';
  14. our $qs_last_input_file;
  15. our $qs_last_output_file;
  16. our $last_config;
  17. sub new {
  18. my ($class) = @_;
  19. my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
  20. if ($^O eq 'MSWin32') {
  21. $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r.ico"), wxBITMAP_TYPE_ICO));
  22. } else {
  23. $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
  24. }
  25. $self->{loaded} = 0;
  26. $self->{preset_editor_tabs} = {}; # group => panel
  27. # initialize tabpanel and menubar
  28. $self->_init_tabpanel;
  29. $self->_init_menubar;
  30. # set default tooltip timer in msec
  31. # SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
  32. # (SetAutoPop is not available on GTK.)
  33. eval { Wx::ToolTip::SetAutoPop(32767) };
  34. # initialize status bar
  35. $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
  36. $self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/");
  37. $self->SetStatusBar($self->{statusbar});
  38. $self->{loaded} = 1;
  39. # initialize layout
  40. {
  41. my $sizer = Wx::BoxSizer->new(wxVERTICAL);
  42. $sizer->Add($self->{tabpanel}, 1, wxEXPAND);
  43. $sizer->SetSizeHints($self);
  44. $self->SetSizer($sizer);
  45. $self->Fit;
  46. $self->SetMinSize([760, 490]);
  47. $self->SetSize($self->GetMinSize);
  48. wxTheApp->restore_window_pos($self, "main_frame");
  49. $self->Show;
  50. $self->Layout;
  51. }
  52. # declare events
  53. EVT_CLOSE($self, sub {
  54. my (undef, $event) = @_;
  55. if ($event->CanVeto) {
  56. if (!$self->{plater}->prompt_unsaved_changes) {
  57. $event->Veto;
  58. return;
  59. }
  60. if ($self->{controller} && $self->{controller}->printing) {
  61. my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?",
  62. 'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
  63. if ($confirm->ShowModal == wxID_NO) {
  64. $event->Veto;
  65. return;
  66. }
  67. }
  68. }
  69. # save window size
  70. wxTheApp->save_window_pos($self, "main_frame");
  71. # propagate event
  72. $event->Skip;
  73. });
  74. return $self;
  75. }
  76. sub _init_tabpanel {
  77. my ($self) = @_;
  78. $self->{tabpanel} = my $panel = Wx::AuiNotebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxAUI_NB_TOP);
  79. EVT_AUINOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub {
  80. my $panel = $self->{tabpanel}->GetPage($self->{tabpanel}->GetSelection);
  81. $panel->OnActivate if $panel->can('OnActivate');
  82. if ($self->{tabpanel}->GetSelection > 1) {
  83. $self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag | wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
  84. } elsif(($Slic3r::GUI::Settings->{_}{show_host} == 0) && ($self->{tabpanel}->GetSelection == 1)){
  85. $self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag | wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
  86. } else {
  87. $self->{tabpanel}->SetWindowStyle($self->{tabpanel}->GetWindowStyleFlag & ~wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
  88. }
  89. });
  90. EVT_AUINOTEBOOK_PAGE_CLOSE($self, $self->{tabpanel}, sub {
  91. my $panel = $self->{tabpanel}->GetPage($self->{tabpanel}->GetSelection);
  92. if ($panel->isa('Slic3r::GUI::PresetEditor')) {
  93. delete $self->{preset_editor_tabs}{$panel->name};
  94. }
  95. wxTheApp->CallAfter(sub {
  96. $self->{tabpanel}->SetSelection(0);
  97. });
  98. });
  99. $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater");
  100. $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller")
  101. if ($Slic3r::GUI::Settings->{_}{show_host});
  102. }
  103. sub _init_menubar {
  104. my ($self) = @_;
  105. # File menu
  106. my $fileMenu = Wx::Menu->new;
  107. {
  108. wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF/3MF…\tCtrl+O", 'Open a model', sub {
  109. $self->{plater}->add if $self->{plater};
  110. }, undef, 'brick_add.png');
  111. wxTheApp->append_menu_item($fileMenu, "Open 2.5D TIN mesh…", 'Import a 2.5D TIN mesh', sub {
  112. $self->{plater}->add_tin if $self->{plater};
  113. }, undef, 'map_add.png');
  114. $fileMenu->AppendSeparator();
  115. wxTheApp->append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub {
  116. $self->load_config_file;
  117. }, undef, 'plugin_add.png');
  118. wxTheApp->append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub {
  119. $self->export_config;
  120. }, undef, 'plugin_go.png');
  121. wxTheApp->append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub {
  122. $self->load_configbundle;
  123. }, undef, 'lorry_add.png');
  124. wxTheApp->append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub {
  125. $self->export_configbundle;
  126. }, undef, 'lorry_go.png');
  127. $fileMenu->AppendSeparator();
  128. my $repeat;
  129. wxTheApp->append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice file', sub {
  130. wxTheApp->CallAfter(sub {
  131. $self->quick_slice;
  132. $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
  133. });
  134. }, undef, 'cog_go.png');
  135. wxTheApp->append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice file and save as', sub {
  136. wxTheApp->CallAfter(sub {
  137. $self->quick_slice(save_as => 1);
  138. $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
  139. });
  140. }, undef, 'cog_go.png');
  141. $repeat = wxTheApp->append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub {
  142. wxTheApp->CallAfter(sub {
  143. $self->quick_slice(reslice => 1);
  144. });
  145. }, undef, 'cog_go.png');
  146. $repeat->Enable(0);
  147. $fileMenu->AppendSeparator();
  148. wxTheApp->append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG', sub {
  149. $self->quick_slice(save_as => 1, export_svg => 1);
  150. }, undef, 'shape_handles.png');
  151. $fileMenu->AppendSeparator();
  152. wxTheApp->append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub {
  153. $self->repair_stl;
  154. }, undef, 'wrench.png');
  155. $fileMenu->AppendSeparator();
  156. # Cmd+, is standard on OS X - what about other operating systems?
  157. wxTheApp->append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub {
  158. Slic3r::GUI::Preferences->new($self)->ShowModal;
  159. }, wxID_PREFERENCES);
  160. $fileMenu->AppendSeparator();
  161. wxTheApp->append_menu_item($fileMenu, "&Quit", 'Quit Slic3r', sub {
  162. $self->Close(0);
  163. }, wxID_EXIT);
  164. }
  165. # Plater menu
  166. {
  167. my $plater = $self->{plater};
  168. $self->{plater_menu} = Wx::Menu->new;
  169. {
  170. my $selectMenu = $self->{plater_select_menu} = Wx::Menu->new;
  171. wxTheApp->append_submenu($self->{plater_menu}, "Select", 'Select an object in the plater', $selectMenu, undef, 'brick.png');
  172. }
  173. wxTheApp->append_menu_item($self->{plater_menu}, "Undo\tCtrl+Z", 'Undo', sub {
  174. $plater->undo;
  175. }, undef, 'arrow_undo.png');
  176. wxTheApp->append_menu_item($self->{plater_menu}, "Redo\tCtrl+Shift+Z", 'Redo', sub {
  177. $plater->redo;
  178. }, undef, 'arrow_redo.png');
  179. wxTheApp->append_menu_item($self->{plater_menu}, "Select Next Object\tCtrl+Right", 'Select Next Object in the plater', sub {
  180. $plater->select_next;
  181. }, undef, 'arrow_right.png');
  182. wxTheApp->append_menu_item($self->{plater_menu}, "Select Prev Object\tCtrl+Left", 'Select Previous Object in the plater', sub {
  183. $plater->select_prev;
  184. }, undef, 'arrow_left.png');
  185. wxTheApp->append_menu_item($self->{plater_menu}, "Zoom In\tCtrl+up", 'Zoom In',
  186. sub { $self->{plater}->zoom('in') }, undef, 'zoom_in.png');
  187. wxTheApp->append_menu_item($self->{plater_menu}, "Zoom Out\tCtrl+down", 'Zoom Out',
  188. sub { $self->{plater}->zoom('out') }, undef, 'zoom_out.png');
  189. $self->{plater_menu}->AppendSeparator();
  190. wxTheApp->append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub {
  191. $plater->export_gcode;
  192. }, undef, 'cog_go.png');
  193. wxTheApp->append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub {
  194. $plater->export_stl;
  195. }, undef, 'brick_go.png');
  196. wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as AMF...", 'Export current plate as AMF, including all modifier meshes', sub {
  197. $plater->export_amf;
  198. }, undef, 'brick_go.png');
  199. wxTheApp->append_menu_item($self->{plater_menu}, "Export plate with modifiers as 3MF...", 'Export current plate as 3MF, including all modifier meshes', sub {
  200. $plater->export_tmf;
  201. }, undef, 'brick_go.png');
  202. $self->{object_menu} = $self->{plater}->object_menu;
  203. $self->on_plater_object_list_changed(0);
  204. $self->on_plater_selection_changed(0);
  205. }
  206. # Settings menu
  207. my $settingsMenu = Wx::Menu->new;
  208. {
  209. wxTheApp->append_menu_item($settingsMenu, "P&rint Settings…\tCtrl+1", 'Show the print settings editor', sub {
  210. $self->{plater}->show_preset_editor('print');
  211. }, undef, 'cog.png');
  212. wxTheApp->append_menu_item($settingsMenu, "&Filament Settings…\tCtrl+2", 'Show the filament settings editor', sub {
  213. $self->{plater}->show_preset_editor('filament');
  214. }, undef, 'spool.png');
  215. wxTheApp->append_menu_item($settingsMenu, "Print&er Settings…\tCtrl+3", 'Show the printer settings editor', sub {
  216. $self->{plater}->show_preset_editor('printer');
  217. }, undef, 'printer_empty.png');
  218. }
  219. # View menu
  220. {
  221. $self->{viewMenu} = Wx::Menu->new;
  222. wxTheApp->append_menu_item($self->{viewMenu}, "Top\tCtrl+4" , 'Top View' , sub { $self->select_view('top' ); });
  223. wxTheApp->append_menu_item($self->{viewMenu}, "Bottom\tCtrl+5" , 'Bottom View' , sub { $self->select_view('bottom' ); });
  224. wxTheApp->append_menu_item($self->{viewMenu}, "Left\tCtrl+6" , 'Left View' , sub { $self->select_view('left' ); });
  225. wxTheApp->append_menu_item($self->{viewMenu}, "Right\tCtrl+7" , 'Right View' , sub { $self->select_view('right' ); });
  226. wxTheApp->append_menu_item($self->{viewMenu}, "Front\tCtrl+8" , 'Front View' , sub { $self->select_view('front' ); });
  227. wxTheApp->append_menu_item($self->{viewMenu}, "Back\tCtrl+9" , 'Back View' , sub { $self->select_view('back' ); });
  228. wxTheApp->append_menu_item($self->{viewMenu}, "Diagonal\tCtrl+0", 'Diagonal View', sub { $self->select_view('diagonal'); });
  229. $self->{viewMenu}->AppendSeparator();
  230. $self->{color_toolpaths_by_role} = wxTheApp->append_menu_item($self->{viewMenu},
  231. "Color Toolpaths by Role",
  232. 'Color toolpaths according to perimeter/infill/support material',
  233. sub {
  234. $Slic3r::GUI::Settings->{_}{color_toolpaths_by} = 'role';
  235. wxTheApp->save_settings;
  236. $self->{plater}{preview3D}->reload_print;
  237. },
  238. undef, undef, wxITEM_RADIO
  239. );
  240. $self->{color_toolpaths_by_extruder} = wxTheApp->append_menu_item($self->{viewMenu},
  241. "Color Toolpaths by Filament",
  242. 'Color toolpaths using the configured extruder/filament color',
  243. sub {
  244. $Slic3r::GUI::Settings->{_}{color_toolpaths_by} = 'extruder';
  245. wxTheApp->save_settings;
  246. $self->{plater}{preview3D}->reload_print;
  247. },
  248. undef, undef, wxITEM_RADIO
  249. );
  250. if ($Slic3r::GUI::Settings->{_}{color_toolpaths_by} eq 'role') {
  251. $self->{color_toolpaths_by_role}->Check(1);
  252. } else {
  253. $self->{color_toolpaths_by_extruder}->Check(1);
  254. }
  255. }
  256. # Window menu
  257. my $windowMenu = Wx::Menu->new;
  258. {
  259. wxTheApp->append_menu_item($windowMenu, "&Plater\tCtrl+T", 'Show the plater', sub {
  260. $self->select_tab(0);
  261. }, undef, 'application_view_tile.png');
  262. wxTheApp->append_menu_item($windowMenu, "&Controller\tCtrl+Y", 'Show the printer controller', sub {
  263. $self->select_tab(1);
  264. }, undef, 'printer_empty.png');
  265. wxTheApp->append_menu_item($windowMenu, "DLP Projector…\tCtrl+P", 'Open projector window for DLP printing', sub {
  266. $self->{plater}->pause_background_process;
  267. Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal;
  268. $self->{plater}->resume_background_process;
  269. }, undef, 'film.png');
  270. }
  271. # Help menu
  272. my $helpMenu = Wx::Menu->new;
  273. {
  274. wxTheApp->append_menu_item($helpMenu, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard", sub {
  275. $self->config_wizard;
  276. });
  277. $helpMenu->AppendSeparator();
  278. wxTheApp->append_menu_item($helpMenu, "Slic3r &Website", 'Open the Slic3r website in your browser', sub {
  279. Wx::LaunchDefaultBrowser('http://slic3r.org/');
  280. });
  281. my $versioncheck = wxTheApp->append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub {
  282. wxTheApp->check_version(1);
  283. });
  284. $versioncheck->Enable(wxTheApp->have_version_check);
  285. wxTheApp->append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub {
  286. Wx::LaunchDefaultBrowser('http://manual.slic3r.org/');
  287. });
  288. $helpMenu->AppendSeparator();
  289. wxTheApp->append_menu_item($helpMenu, "&About Slic3r", 'Show about dialog', sub {
  290. wxTheApp->about;
  291. });
  292. }
  293. # menubar
  294. # assign menubar to frame after appending items, otherwise special items
  295. # will not be handled correctly
  296. {
  297. my $menubar = Wx::MenuBar->new;
  298. $menubar->Append($fileMenu, "&File");
  299. $menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu};
  300. $menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu};
  301. $menubar->Append($settingsMenu, "&Settings");
  302. $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu};
  303. $menubar->Append($windowMenu, "&Window");
  304. $menubar->Append($helpMenu, "&Help");
  305. $self->SetMenuBar($menubar);
  306. }
  307. }
  308. sub is_loaded {
  309. my ($self) = @_;
  310. return $self->{loaded};
  311. }
  312. sub on_undo_redo_stacks_changed {
  313. my $self = shift;
  314. # Enable undo or redo if they have operations in their stack.
  315. $self->{plater_menu}->Enable($self->{plater_menu}->FindItem("Undo\tCtrl+Z"), $#{$self->{plater}->{undo_stack}} < 0 ? 0 : 1);
  316. $self->{plater_menu}->Enable( $self->{plater_menu}->FindItem("Redo\tCtrl+Shift+Z"), $#{$self->{plater}->{redo_stack}} < 0 ? 0 : 1);
  317. }
  318. sub on_plater_object_list_changed {
  319. my ($self, $have_objects) = @_;
  320. return if !defined $self->{plater_menu};
  321. $self->{plater_menu}->Enable($_->GetId, $have_objects)
  322. for $self->{plater_menu}->GetMenuItems;
  323. $self->on_undo_redo_stacks_changed;
  324. }
  325. sub on_plater_selection_changed {
  326. my ($self, $have_selection) = @_;
  327. return if !defined $self->{object_menu};
  328. $self->{object_menu}->Enable($_->GetId, $have_selection)
  329. for $self->{object_menu}->GetMenuItems;
  330. $self->on_undo_redo_stacks_changed;
  331. }
  332. sub quick_slice {
  333. my $self = shift;
  334. my %params = @_;
  335. my $progress_dialog;
  336. eval {
  337. # validate configuration
  338. my $config = $self->{plater}->config;
  339. $config->validate;
  340. # select input file
  341. my $input_file;
  342. my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
  343. if (!$params{reslice}) {
  344. my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/3MF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  345. if ($dialog->ShowModal != wxID_OK) {
  346. $dialog->Destroy;
  347. return;
  348. }
  349. $input_file = Slic3r::decode_path($dialog->GetPaths);
  350. $dialog->Destroy;
  351. $qs_last_input_file = $input_file unless $params{export_svg};
  352. } else {
  353. if (!defined $qs_last_input_file) {
  354. Wx::MessageDialog->new($self, "No previously sliced file.",
  355. 'Error', wxICON_ERROR | wxOK)->ShowModal();
  356. return;
  357. }
  358. if (! -e $qs_last_input_file) {
  359. Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.",
  360. 'File Not Found', wxICON_ERROR | wxOK)->ShowModal();
  361. return;
  362. }
  363. $input_file = $qs_last_input_file;
  364. }
  365. my $input_file_basename = basename($input_file);
  366. $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
  367. wxTheApp->save_settings;
  368. my $print_center;
  369. {
  370. my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape});
  371. $print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center});
  372. }
  373. my $sprint = Slic3r::Print::Simple->new(
  374. print_center => $print_center,
  375. status_cb => sub {
  376. my ($percent, $message) = @_;
  377. return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/;
  378. $progress_dialog->Update($percent, "$message…");
  379. },
  380. );
  381. # keep model around
  382. my $model = Slic3r::Model->read_from_file($input_file);
  383. $sprint->apply_config($config);
  384. $sprint->set_model($model);
  385. # FIXME: populate placeholders (preset names etc.)
  386. # select output file
  387. my $output_file;
  388. if ($params{reslice}) {
  389. $output_file = $qs_last_output_file if defined $qs_last_output_file;
  390. } elsif ($params{save_as}) {
  391. $output_file = $sprint->output_filepath;
  392. $output_file =~ s/\.gcode$/.svg/i if $params{export_svg};
  393. my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
  394. wxTheApp->output_path(dirname($output_file)),
  395. basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  396. if ($dlg->ShowModal != wxID_OK) {
  397. $dlg->Destroy;
  398. return;
  399. }
  400. $output_file = Slic3r::decode_path($dlg->GetPath);
  401. $qs_last_output_file = $output_file unless $params{export_svg};
  402. $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
  403. wxTheApp->save_settings;
  404. $dlg->Destroy;
  405. }
  406. # show processbar dialog
  407. $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…",
  408. 100, $self, 0);
  409. $progress_dialog->Pulse;
  410. {
  411. my @warnings = ();
  412. local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  413. $sprint->output_file($output_file);
  414. if ($params{export_svg}) {
  415. $sprint->export_svg;
  416. } else {
  417. $sprint->export_gcode;
  418. }
  419. $sprint->status_cb(undef);
  420. Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
  421. }
  422. $progress_dialog->Destroy;
  423. undef $progress_dialog;
  424. my $message = "$input_file_basename was successfully sliced.";
  425. wxTheApp->notify($message);
  426. Wx::MessageDialog->new($self, $message, 'Slicing Done!',
  427. wxOK | wxICON_INFORMATION)->ShowModal;
  428. };
  429. Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog });
  430. }
  431. sub repair_stl {
  432. my $self = shift;
  433. my $input_file;
  434. {
  435. my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
  436. my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  437. if ($dialog->ShowModal != wxID_OK) {
  438. $dialog->Destroy;
  439. return;
  440. }
  441. $input_file = Slic3r::decode_path($dialog->GetPaths);
  442. $dialog->Destroy;
  443. }
  444. my $output_file = $input_file;
  445. {
  446. $output_file =~ s/\.stl$/_fixed.obj/i;
  447. my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file),
  448. basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  449. if ($dlg->ShowModal != wxID_OK) {
  450. $dlg->Destroy;
  451. return undef;
  452. }
  453. $output_file = Slic3r::decode_path($dlg->GetPath);
  454. $dlg->Destroy;
  455. }
  456. my $tmesh = Slic3r::TriangleMesh->new;
  457. $tmesh->ReadSTLFile($input_file);
  458. $tmesh->repair;
  459. $tmesh->WriteOBJFile($output_file);
  460. Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair");
  461. }
  462. sub export_config {
  463. my $self = shift;
  464. my $config = $self->{plater}->config;
  465. eval {
  466. # validate configuration
  467. $config->validate;
  468. };
  469. Slic3r::GUI::catch_error($self) and return;
  470. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
  471. my $filename = $last_config ? basename($last_config) : "config.ini";
  472. my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename,
  473. &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  474. if ($dlg->ShowModal == wxID_OK) {
  475. my $file = Slic3r::decode_path($dlg->GetPath);
  476. $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
  477. wxTheApp->save_settings;
  478. $last_config = $file;
  479. $config->save($file);
  480. }
  481. $dlg->Destroy;
  482. }
  483. sub load_config_file {
  484. my $self = shift;
  485. my ($file) = @_;
  486. if (!$file) {
  487. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
  488. my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
  489. &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  490. return unless $dlg->ShowModal == wxID_OK;
  491. $file = Slic3r::decode_path($dlg->GetPaths);
  492. $dlg->Destroy;
  493. }
  494. $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
  495. wxTheApp->save_settings;
  496. $last_config = $file;
  497. my $name = wxTheApp->add_external_preset($file);
  498. $self->{plater}->load_presets;
  499. $self->{plater}->select_preset_by_name($name, $_) for qw(print filament printer);
  500. }
  501. sub export_configbundle {
  502. my $self = shift;
  503. eval {
  504. # validate current configuration in case it's dirty
  505. $self->{plater}->config->validate;
  506. };
  507. Slic3r::GUI::catch_error($self) and return;
  508. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
  509. my $filename = "Slic3r_config_bundle.ini";
  510. my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename,
  511. &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  512. if ($dlg->ShowModal == wxID_OK) {
  513. my $file = Slic3r::decode_path($dlg->GetPath);
  514. $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
  515. wxTheApp->save_settings;
  516. # leave default category empty to prevent the bundle from being parsed as a normal config file
  517. my $ini = { _ => {} };
  518. $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter);
  519. $ini->{presets} = $Slic3r::GUI::Settings->{presets};
  520. foreach my $section (qw(print filament printer)) {
  521. my @presets = @{wxTheApp->presets->{$section}};
  522. foreach my $preset (@presets) {
  523. next if $preset->default || $preset->external;
  524. $ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_};
  525. }
  526. }
  527. Slic3r::Config->write_ini($file, $ini);
  528. }
  529. $dlg->Destroy;
  530. }
  531. sub load_configbundle {
  532. my ($self, $file, $skip_no_id) = @_;
  533. if (!$file) {
  534. my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
  535. my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini",
  536. &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  537. return unless $dlg->ShowModal == wxID_OK;
  538. $file = Slic3r::decode_path($dlg->GetPaths);
  539. $dlg->Destroy;
  540. }
  541. $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
  542. wxTheApp->save_settings;
  543. # load .ini file
  544. my $ini = Slic3r::Config->read_ini($file);
  545. if ($ini->{settings}) {
  546. $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}};
  547. wxTheApp->save_settings;
  548. }
  549. if ($ini->{presets}) {
  550. $Slic3r::GUI::Settings->{presets} = $ini->{presets};
  551. wxTheApp->save_settings;
  552. }
  553. my $imported = 0;
  554. INI_BLOCK: foreach my $ini_category (sort keys %$ini) {
  555. next unless $ini_category =~ /^(print|filament|printer):(.+)$/;
  556. my ($section, $preset_name) = ($1, $2);
  557. my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category});
  558. next if $skip_no_id && !$config->get($section . "_settings_id");
  559. {
  560. my @current_presets = @{wxTheApp->presets->{$section}};
  561. my %current_ids = map { $_ => 1 }
  562. grep $_,
  563. map $_->dirty_config->get($section . "_settings_id"),
  564. @current_presets;
  565. next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")};
  566. }
  567. $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name);
  568. Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name;
  569. $imported++;
  570. }
  571. $self->{plater}->load_presets;
  572. return if !$imported;
  573. my $message = sprintf "%d presets successfully imported.", $imported;
  574. Slic3r::GUI::show_info($self, $message);
  575. }
  576. sub load_config {
  577. my ($self, $config) = @_;
  578. $self->{plater}->load_config($config);
  579. }
  580. sub config_wizard {
  581. my $self = shift;
  582. if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) {
  583. foreach my $group (qw(print filament printer)) {
  584. my $name = 'My Settings';
  585. $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $group, $name);
  586. $Slic3r::GUI::Settings->{presets}{$group} = "$name.ini";
  587. $self->{plater}->load_presets;
  588. $self->{plater}->select_preset_by_name($name, $group);
  589. }
  590. }
  591. }
  592. sub select_tab {
  593. my ($self, $tab) = @_;
  594. $self->{tabpanel}->SetSelection($tab);
  595. }
  596. # Set a camera direction, zoom to all objects.
  597. sub select_view {
  598. my ($self, $direction) = @_;
  599. $self->{plater}->select_view($direction);
  600. }
  601. 1;