Projector.pm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. # DLP Projector screen for the SLA (stereolitography) print process
  2. package Slic3r::GUI::Projector;
  3. use strict;
  4. use warnings;
  5. use Wx qw(:dialog :id :misc :sizer :systemsettings :bitmap :button :icon wxTheApp);
  6. use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER EVT_SPINCTRL EVT_SLIDER);
  7. use base qw(Wx::Dialog Class::Accessor);
  8. use utf8;
  9. __PACKAGE__->mk_accessors(qw(config config2 screen controller _optgroups));
  10. sub new {
  11. my ($class, $parent) = @_;
  12. my $self = $class->SUPER::new($parent, -1, "Projector for DLP", wxDefaultPosition, wxDefaultSize);
  13. $self->config2({
  14. display => 0,
  15. show_bed => 1,
  16. invert_y => 0,
  17. zoom => 100,
  18. exposure_time => 2,
  19. bottom_exposure_time => 7,
  20. settle_time => 1.5,
  21. bottom_layers => 3,
  22. z_lift => 5,
  23. z_lift_speed => 8,
  24. offset => [0,0],
  25. });
  26. my $ini = eval { Slic3r::Config->read_ini("$Slic3r::GUI::datadir/DLP.ini") };
  27. if ($ini) {
  28. foreach my $opt_id (keys %{$ini->{_}}) {
  29. my $value = $ini->{_}{$opt_id};
  30. if ($opt_id eq 'offset') {
  31. $value = [ split /,/, $value ];
  32. }
  33. $self->config2->{$opt_id} = $value;
  34. }
  35. }
  36. my $sizer = Wx::BoxSizer->new(wxVERTICAL);
  37. $self->config(Slic3r::Config->new_from_defaults(
  38. qw(serial_port serial_speed bed_shape start_gcode end_gcode z_offset)
  39. ));
  40. $self->config->apply(wxTheApp->{mainframe}->config);
  41. my @optgroups = ();
  42. {
  43. push @optgroups, my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
  44. parent => $self,
  45. title => 'USB/Serial connection',
  46. config => $self->config,
  47. label_width => 200,
  48. );
  49. $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  50. {
  51. my $line = Slic3r::GUI::OptionsGroup::Line->new(
  52. label => 'Serial port',
  53. );
  54. my $serial_port = $optgroup->get_option('serial_port');
  55. $serial_port->side_widget(sub {
  56. my ($parent) = @_;
  57. my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
  58. wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
  59. $btn->SetToolTipString("Rescan serial ports")
  60. if $btn->can('SetToolTipString');
  61. EVT_BUTTON($self, $btn, sub {
  62. $optgroup->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]);
  63. });
  64. return $btn;
  65. });
  66. my $serial_test = sub {
  67. my ($parent) = @_;
  68. my $btn = $self->{serial_test_btn} = Wx::Button->new($parent, -1,
  69. "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
  70. $btn->SetFont($Slic3r::GUI::small_font);
  71. if ($Slic3r::GUI::have_button_icons) {
  72. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("wrench.png"), wxBITMAP_TYPE_PNG));
  73. }
  74. EVT_BUTTON($self, $btn, sub {
  75. my $sender = Slic3r::GCode::Sender->new;
  76. my $res = $sender->connect(
  77. $self->{config}->serial_port,
  78. $self->{config}->serial_speed,
  79. );
  80. if ($res && $sender->wait_connected) {
  81. Slic3r::GUI::show_info($self, "Connection to printer works correctly.", "Success!");
  82. } else {
  83. Slic3r::GUI::show_error($self, "Connection failed.");
  84. }
  85. });
  86. return $btn;
  87. };
  88. $line->append_option($serial_port);
  89. $line->append_option($optgroup->get_option('serial_speed'));
  90. $line->append_widget($serial_test);
  91. $optgroup->append_line($line);
  92. }
  93. }
  94. {
  95. push @optgroups, my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
  96. parent => $self,
  97. title => 'G-code',
  98. config => $self->config,
  99. label_width => 200,
  100. );
  101. $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  102. {
  103. my $option = $optgroup->get_option('start_gcode');
  104. $option->height(50);
  105. $option->full_width(1);
  106. $optgroup->append_single_option_line($option);
  107. }
  108. {
  109. my $option = $optgroup->get_option('end_gcode');
  110. $option->height(50);
  111. $option->full_width(1);
  112. $optgroup->append_single_option_line($option);
  113. }
  114. }
  115. my $on_change = sub {
  116. my ($opt_id, $value) = @_;
  117. $self->config2->{$opt_id} = $value;
  118. $self->screen->reposition;
  119. $self->show_print_time;
  120. my $serialized = {};
  121. foreach my $opt_id (keys %{$self->config2}) {
  122. my $value = $self->config2->{$opt_id};
  123. if (ref($value) eq 'ARRAY') {
  124. $value = join ',', @$value;
  125. }
  126. $serialized->{$opt_id} = $value;
  127. }
  128. Slic3r::Config->write_ini(
  129. "$Slic3r::GUI::datadir/DLP.ini",
  130. { _ => $serialized });
  131. };
  132. {
  133. push @optgroups, my $optgroup = Slic3r::GUI::OptionsGroup->new(
  134. parent => $self,
  135. title => 'Projection',
  136. on_change => $on_change,
  137. label_width => 200,
  138. );
  139. $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  140. {
  141. my $line = Slic3r::GUI::OptionsGroup::Line->new(
  142. label => 'Display',
  143. );
  144. my @displays = 0 .. (Wx::Display::GetCount()-1);
  145. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  146. opt_id => 'display',
  147. type => 'select',
  148. label => 'Display',
  149. tooltip => '',
  150. labels => [@displays],
  151. values => [@displays],
  152. default => $self->config2->{display},
  153. ));
  154. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  155. opt_id => 'zoom',
  156. type => 'percent',
  157. label => 'Zoom %',
  158. tooltip => '',
  159. default => $self->config2->{zoom},
  160. min => 0.1,
  161. max => 100,
  162. ));
  163. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  164. opt_id => 'offset',
  165. type => 'point',
  166. label => 'Offset',
  167. tooltip => '',
  168. default => $self->config2->{offset},
  169. ));
  170. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  171. opt_id => 'invert_y',
  172. type => 'bool',
  173. label => 'Invert Y',
  174. tooltip => '',
  175. default => $self->config2->{invert_y},
  176. ));
  177. $optgroup->append_line($line);
  178. }
  179. $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
  180. opt_id => 'show_bed',
  181. type => 'bool',
  182. label => 'Show bed',
  183. tooltip => '',
  184. default => $self->config2->{show_bed},
  185. ));
  186. }
  187. {
  188. push @optgroups, my $optgroup = Slic3r::GUI::OptionsGroup->new(
  189. parent => $self,
  190. title => 'Print',
  191. on_change => $on_change,
  192. label_width => 200,
  193. );
  194. $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  195. {
  196. my $line = Slic3r::GUI::OptionsGroup::Line->new(
  197. label => 'Time (seconds)',
  198. );
  199. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  200. opt_id => 'bottom_exposure_time',
  201. type => 'f',
  202. label => 'Bottom exposure',
  203. tooltip => '',
  204. default => $self->config2->{bottom_exposure_time},
  205. ));
  206. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  207. opt_id => 'exposure_time',
  208. type => 'f',
  209. label => 'Exposure',
  210. tooltip => '',
  211. default => $self->config2->{exposure_time},
  212. ));
  213. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  214. opt_id => 'settle_time',
  215. type => 'f',
  216. label => 'Settle',
  217. tooltip => '',
  218. default => $self->config2->{settle_time},
  219. ));
  220. $optgroup->append_line($line);
  221. }
  222. $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
  223. opt_id => 'bottom_layers',
  224. type => 'i',
  225. label => 'Bottom layers',
  226. tooltip => '',
  227. default => $self->config2->{bottom_layers},
  228. ));
  229. {
  230. my $line = Slic3r::GUI::OptionsGroup::Line->new(
  231. label => 'Z Lift',
  232. );
  233. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  234. opt_id => 'z_lift',
  235. type => 'f',
  236. label => 'Distance',
  237. sidetext => 'mm',
  238. tooltip => '',
  239. default => $self->config2->{z_lift},
  240. ));
  241. $line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
  242. opt_id => 'z_lift_speed',
  243. type => 'f',
  244. label => 'Speed',
  245. sidetext => 'mm/s',
  246. tooltip => '',
  247. default => $self->config2->{z_lift_speed},
  248. ));
  249. $optgroup->append_line($line);
  250. }
  251. }
  252. $self->_optgroups([@optgroups]);
  253. {
  254. my $sizer1 = Wx::BoxSizer->new(wxHORIZONTAL);
  255. $sizer->Add($sizer1, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  256. {
  257. my $btn = $self->{btn_manual_control} = Wx::Button->new($self, -1, 'Manual Control', wxDefaultPosition, wxDefaultSize);
  258. if ($Slic3r::GUI::have_button_icons) {
  259. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG));
  260. }
  261. $sizer1->Add($btn, 0);
  262. EVT_BUTTON($self, $btn, sub {
  263. my $sender = Slic3r::GCode::Sender->new;
  264. my $res = $sender->connect(
  265. $self->config->serial_port,
  266. $self->config->serial_speed,
  267. );
  268. if (!$res || !$sender->wait_connected) {
  269. Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
  270. return;
  271. }
  272. my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
  273. ($self, $self->config, $sender);
  274. $dlg->ShowModal;
  275. $sender->disconnect;
  276. });
  277. }
  278. {
  279. my $btn = $self->{btn_print} = Wx::Button->new($self, -1, 'Print', wxDefaultPosition, wxDefaultSize);
  280. if ($Slic3r::GUI::have_button_icons) {
  281. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
  282. }
  283. $sizer1->Add($btn, 0);
  284. EVT_BUTTON($self, $btn, sub {
  285. $self->controller->start_print;
  286. $self->_update_buttons;
  287. $self->_set_status('');
  288. });
  289. }
  290. {
  291. my $btn = $self->{btn_stop} = Wx::Button->new($self, -1, 'Stop/Black', wxDefaultPosition, wxDefaultSize);
  292. if ($Slic3r::GUI::have_button_icons) {
  293. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG));
  294. }
  295. $sizer1->Add($btn, 0);
  296. EVT_BUTTON($self, $btn, sub {
  297. $self->controller->stop_print;
  298. $self->_update_buttons;
  299. $self->_set_status('');
  300. });
  301. }
  302. {
  303. {
  304. my $text = Wx::StaticText->new($self, -1, "Layer:", wxDefaultPosition, wxDefaultSize);
  305. $text->SetFont($Slic3r::GUI::small_font);
  306. $sizer1->Add($text, 0, wxEXPAND | wxLEFT, 10);
  307. }
  308. {
  309. my $spin = $self->{layers_spinctrl} = Wx::SpinCtrl->new($self, -1, 0, wxDefaultPosition, [60,-1],
  310. 0, 0, 300, 0);
  311. $sizer1->Add($spin, 0);
  312. EVT_SPINCTRL($self, $spin, sub {
  313. my $value = $spin->GetValue;
  314. $self->{layers_slider}->SetValue($value);
  315. $self->controller->project_layer($value);
  316. $self->_update_buttons;
  317. });
  318. }
  319. {
  320. my $slider = $self->{layers_slider} = Wx::Slider->new(
  321. $self, -1,
  322. 0, # default
  323. 0, # min
  324. 300, # max
  325. wxDefaultPosition,
  326. wxDefaultSize,
  327. );
  328. $sizer1->Add($slider, 1);
  329. EVT_SLIDER($self, $slider, sub {
  330. my $value = $slider->GetValue;
  331. $self->{layers_spinctrl}->SetValue($value);
  332. $self->controller->project_layer($value);
  333. $self->_update_buttons;
  334. });
  335. }
  336. }
  337. my $sizer2 = Wx::BoxSizer->new(wxHORIZONTAL);
  338. $sizer->Add($sizer2, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  339. {
  340. $self->{status_text} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize);
  341. $self->{status_text}->SetFont($Slic3r::GUI::small_font);
  342. $sizer2->Add($self->{status_text}, 1 | wxEXPAND);
  343. }
  344. }
  345. {
  346. my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
  347. EVT_BUTTON($self, wxID_CLOSE, sub {
  348. $self->_close;
  349. });
  350. $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  351. }
  352. $self->SetSizer($sizer);
  353. $sizer->SetSizeHints($self);
  354. # reuse existing screen if any
  355. if ($Slic3r::GUI::DLP_projection_screen) {
  356. $self->screen($Slic3r::GUI::DLP_projection_screen);
  357. $self->screen->config($self->config);
  358. $self->screen->config2($self->config2);
  359. } else {
  360. $self->screen(Slic3r::GUI::Projector::Screen->new($parent, $self->config, $self->config2));
  361. $Slic3r::GUI::DLP_projection_screen = $self->screen;
  362. }
  363. $self->screen->reposition;
  364. $self->screen->Show;
  365. wxTheApp->{mainframe}->Hide;
  366. # initialize controller
  367. $self->controller(Slic3r::GUI::Projector::Controller->new(
  368. config => $self->config,
  369. config2 => $self->config2,
  370. screen => $self->screen,
  371. on_project_layer => sub {
  372. my ($layer_num) = @_;
  373. $self->{layers_spinctrl}->SetValue($layer_num);
  374. $self->{layers_slider}->SetValue($layer_num);
  375. my $duration = $self->controller->remaining_print_time;
  376. $self->_set_status(sprintf "Printing layer %d/%d (z = %.2f); %d minutes and %d seconds left",
  377. $layer_num, $self->controller->layer_count,
  378. $self->controller->current_layer_height,
  379. int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer
  380. },
  381. on_print_completed => sub {
  382. $self->_update_buttons;
  383. $self->_set_status('');
  384. Wx::Bell();
  385. },
  386. ));
  387. {
  388. my $max = $self->controller->layer_count-1;
  389. $self->{layers_spinctrl}->SetRange(0, $max);
  390. $self->{layers_slider}->SetRange(0, $max);
  391. }
  392. $self->_update_buttons;
  393. $self->show_print_time;
  394. return $self;
  395. }
  396. sub _update_buttons {
  397. my ($self) = @_;
  398. my $is_printing = $self->controller->is_printing;
  399. my $is_projecting = $self->controller->is_projecting;
  400. $self->{btn_manual_control}->Show(!$is_printing);
  401. $self->{btn_print}->Show(!$is_printing && !$is_projecting);
  402. $self->{btn_stop}->Show($is_printing || $is_projecting);
  403. $self->{layers_spinctrl}->Enable(!$is_printing);
  404. $self->{layers_slider}->Enable(!$is_printing);
  405. if ($is_printing) {
  406. $_->disable for @{$self->_optgroups};
  407. } else {
  408. $_->enable for @{$self->_optgroups};
  409. }
  410. $self->Layout;
  411. }
  412. sub _set_status {
  413. my ($self, $status) = @_;
  414. $self->{status_text}->SetLabel($status // '');
  415. $self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
  416. $self->{status_text}->Refresh;
  417. $self->Layout;
  418. }
  419. sub show_print_time {
  420. my ($self) = @_;
  421. my $duration = $self->controller->print_time;
  422. $self->_set_status(sprintf "Estimated print time: %d minutes and %d seconds",
  423. int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer
  424. }
  425. sub _close {
  426. my $self = shift;
  427. # if projection screen is not on the same display as our dialog,
  428. # ask the user whether they want to keep it open
  429. my $keep_screen = 0;
  430. my $display_area = Wx::Display->new($self->config2->{display})->GetGeometry;
  431. if (!$display_area->Contains($self->GetScreenPosition)) {
  432. my $res = Wx::MessageDialog->new($self, "Do you want to keep the black screen open?", 'Black screen', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
  433. $keep_screen = ($res == wxID_YES);
  434. }
  435. if ($keep_screen) {
  436. $self->screen->config(undef);
  437. $self->screen->config2(undef);
  438. $self->screen->Refresh;
  439. } else {
  440. $self->screen->Destroy;
  441. $self->screen(undef);
  442. $Slic3r::GUI::DLP_projection_screen = undef;
  443. }
  444. wxTheApp->{mainframe}->Show;
  445. my $printer_tab = wxTheApp->{mainframe}{options_tabs}{printer};
  446. $printer_tab->load_config($self->config);
  447. $self->EndModal(wxID_OK);
  448. }
  449. package Slic3r::GUI::Projector::Controller;
  450. use Moo;
  451. use Wx qw(wxTheApp :id :timer);
  452. use Wx::Event qw(EVT_TIMER);
  453. use Slic3r::Print::State ':steps';
  454. use Time::HiRes qw(gettimeofday tv_interval);
  455. has 'config' => (is => 'ro', required => 1);
  456. has 'config2' => (is => 'ro', required => 1);
  457. has 'screen' => (is => 'ro', required => 1);
  458. has 'on_project_layer' => (is => 'rw');
  459. has 'on_print_completed' => (is => 'rw');
  460. has 'sender' => (is => 'rw');
  461. has 'timer' => (is => 'rw');
  462. has 'is_printing' => (is => 'rw', default => sub { 0 });
  463. has '_print' => (is => 'rw');
  464. has '_layers' => (is => 'rw');
  465. has '_heights' => (is => 'rw');
  466. has '_layer_num' => (is => 'rw');
  467. has '_timer_cb' => (is => 'rw');
  468. sub BUILD {
  469. my ($self) = @_;
  470. Slic3r::GUI::disable_screensaver();
  471. $self->set_print(wxTheApp->{mainframe}->{plater}->{print});
  472. # projection timer
  473. my $timer_id = &Wx::NewId();
  474. $self->timer(Wx::Timer->new($self->screen, $timer_id));
  475. EVT_TIMER($self->screen, $timer_id, sub {
  476. my $cb = $self->_timer_cb;
  477. $self->_timer_cb(undef);
  478. $cb->();
  479. });
  480. }
  481. sub delay {
  482. my ($self, $wait, $cb) = @_;
  483. $self->_timer_cb($cb);
  484. $self->timer->Start($wait * 1000, wxTIMER_ONE_SHOT);
  485. }
  486. sub set_print {
  487. my ($self, $print) = @_;
  488. # make sure layers were sliced
  489. {
  490. my $progress_dialog;
  491. foreach my $object (@{$print->objects}) {
  492. next if $object->step_done(STEP_SLICE);
  493. $progress_dialog //= Wx::ProgressDialog->new('Slicing…', "Processing layers…", 100, undef, 0);
  494. $progress_dialog->Pulse;
  495. $object->slice;
  496. }
  497. $progress_dialog->Destroy if $progress_dialog;
  498. }
  499. $self->_print($print);
  500. # sort layers by Z
  501. my %layers = ();
  502. foreach my $layer (map { @{$_->layers}, @{$_->support_layers} } @{$print->objects}) {
  503. my $height = $layer->print_z;
  504. $layers{$height} //= [];
  505. push @{$layers{$height}}, $layer;
  506. }
  507. $self->_layers({ %layers });
  508. $self->_heights([ sort { $a <=> $b } keys %layers ]);
  509. }
  510. sub layer_count {
  511. my ($self) = @_;
  512. return scalar @{$self->_heights};
  513. }
  514. sub current_layer_height {
  515. my ($self) = @_;
  516. return $self->_heights->[$self->_layer_num];
  517. }
  518. sub start_print {
  519. my ($self) = @_;
  520. {
  521. $self->sender(Slic3r::GCode::Sender->new);
  522. my $res = $self->sender->connect(
  523. $self->config->serial_port,
  524. $self->config->serial_speed,
  525. );
  526. if (!$res || !$self->sender->wait_connected) {
  527. Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
  528. return;
  529. }
  530. Slic3r::debugf "connected to " . $self->config->serial_port . "\n";
  531. # send custom start G-code
  532. $self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->start_gcode;
  533. }
  534. $self->is_printing(1);
  535. # TODO: block until the G1 command has been performed
  536. # we could do this with M400 + M115 but maybe it's not portable
  537. $self->delay(5, sub {
  538. # start with black
  539. Slic3r::debugf "starting black projection\n";
  540. $self->_layer_num(-1);
  541. $self->screen->project_layers(undef);
  542. $self->delay($self->config2->{settle_time}, sub {
  543. $self->project_next_layer;
  544. });
  545. });
  546. }
  547. sub stop_print {
  548. my ($self) = @_;
  549. if ($self->sender) {
  550. $self->sender->disconnect;
  551. }
  552. $self->is_printing(0);
  553. $self->timer->Stop;
  554. $self->_timer_cb(undef);
  555. $self->screen->project_layers(undef);
  556. }
  557. sub print_completed {
  558. my ($self) = @_;
  559. # send custom end G-code
  560. if ($self->sender) {
  561. $self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->end_gcode;
  562. }
  563. # call this before the on_print_completed callback otherwise buttons
  564. # won't be updated correctly
  565. $self->stop_print;
  566. $self->on_print_completed->()
  567. if $self->is_printing && $self->on_print_completed;
  568. }
  569. sub is_projecting {
  570. my ($self) = @_;
  571. return defined $self->screen->layers;
  572. }
  573. sub project_layer {
  574. my ($self, $layer_num) = @_;
  575. if (!defined $layer_num || $layer_num >= $self->layer_count) {
  576. $self->screen->project_layers(undef);
  577. return;
  578. }
  579. my @layers = @{ $self->_layers->{ $self->_heights->[$layer_num] } };
  580. $self->screen->project_layers([ @layers ]);
  581. }
  582. sub project_next_layer {
  583. my ($self) = @_;
  584. $self->_layer_num($self->_layer_num + 1);
  585. Slic3r::debugf "projecting layer %d\n", $self->_layer_num;
  586. if ($self->_layer_num >= $self->layer_count) {
  587. $self->print_completed;
  588. return;
  589. }
  590. $self->on_project_layer->($self->_layer_num) if $self->on_project_layer;
  591. if ($self->sender) {
  592. my $z = $self->current_layer_height + $self->config->z_offset;
  593. my $F = $self->config2->{z_lift_speed} * 60;
  594. if ($self->config2->{z_lift} != 0) {
  595. $self->sender->send(sprintf("G1 Z%.5f F%d", $z + $self->config2->{z_lift}, $F), 1);
  596. }
  597. $self->sender->send(sprintf("G1 Z%.5f F%d", $z, $F), 1);
  598. }
  599. # TODO: we should block until G1 commands have been performed, see note below
  600. $self->delay($self->config2->{settle_time}, sub {
  601. $self->project_layer($self->_layer_num);
  602. # get exposure time
  603. my $time = $self->config2->{exposure_time};
  604. if ($self->_layer_num < $self->config2->{bottom_layers}) {
  605. $time = $self->config2->{bottom_exposure_time};
  606. }
  607. $self->delay($time, sub {
  608. $self->screen->project_layers(undef);
  609. $self->project_next_layer;
  610. });
  611. });
  612. }
  613. sub remaining_print_time {
  614. my ($self) = @_;
  615. my $remaining_layers = @{$self->_heights} - $self->_layer_num;
  616. my $remaining_bottom_layers = $self->_layer_num >= $self->config2->{bottom_layers}
  617. ? 0
  618. : $self->config2->{bottom_layers} - $self->_layer_num;
  619. return $remaining_bottom_layers * $self->config2->{bottom_exposure_time}
  620. + ($remaining_layers - $remaining_bottom_layers) * $self->config2->{exposure_time}
  621. + $remaining_layers * $self->config2->{settle_time};
  622. }
  623. sub print_time {
  624. my ($self) = @_;
  625. return $self->config2->{bottom_layers} * $self->config2->{bottom_exposure_time}
  626. + (@{$self->_heights} - $self->config2->{bottom_layers}) * $self->config2->{exposure_time}
  627. + @{$self->_heights} * $self->config2->{settle_time};
  628. }
  629. sub DESTROY {
  630. my ($self) = @_;
  631. $self->timer->Stop if $self->timer;
  632. $self->sender->disconnect if $self->sender;
  633. Slic3r::GUI::enable_screensaver();
  634. }
  635. package Slic3r::GUI::Projector::Screen;
  636. use Wx qw(:dialog :id :misc :sizer :colour :pen :brush :font wxBG_STYLE_CUSTOM);
  637. use Wx::Event qw(EVT_PAINT EVT_SIZE);
  638. use base qw(Wx::Dialog Class::Accessor);
  639. use List::Util qw(min);
  640. use Slic3r::Geometry qw(X Y unscale scale);
  641. use Slic3r::Geometry::Clipper qw(intersection_pl);
  642. __PACKAGE__->mk_accessors(qw(config config2 scaling_factor bed_origin layers));
  643. sub new {
  644. my ($class, $parent, $config, $config2) = @_;
  645. my $self = $class->SUPER::new($parent, -1, "Projector", wxDefaultPosition, wxDefaultSize, 0);
  646. $self->config($config);
  647. $self->config2($config2);
  648. $self->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
  649. EVT_SIZE($self, \&_resize);
  650. EVT_PAINT($self, \&_repaint);
  651. $self->_resize;
  652. return $self;
  653. }
  654. sub reposition {
  655. my ($self) = @_;
  656. my $display = Wx::Display->new($self->config2->{display});
  657. my $area = $display->GetGeometry;
  658. $self->Move($area->GetPosition);
  659. # ShowFullScreen doesn't use the right screen
  660. #$self->ShowFullScreen($self->config2->{fullscreen});
  661. $self->SetSize($area->GetSize);
  662. $self->_resize;
  663. $self->Refresh;
  664. }
  665. sub _resize {
  666. my ($self) = @_;
  667. return if !$self->config;
  668. my ($cw, $ch) = $self->GetSizeWH;
  669. # get bed shape polygon
  670. my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
  671. my $bb = $bed_polygon->bounding_box;
  672. my $size = $bb->size;
  673. my $center = $bb->center;
  674. # calculate the scaling factor needed for constraining print bed area inside preview
  675. # scaling_factor is expressed in pixel / mm
  676. $self->scaling_factor(min($cw / unscale($size->x), $ch / unscale($size->y))); #)
  677. # apply zoom to scaling factor
  678. if ($self->config2->{zoom} != 0) {
  679. # TODO: make sure min and max in the option config are enforced
  680. $self->scaling_factor($self->scaling_factor * ($self->config2->{zoom}/100));
  681. }
  682. # calculate the displacement needed for centering bed on screen
  683. $self->bed_origin([
  684. $cw/2 - (unscale($center->x) - $self->config2->{offset}->[X]) * $self->scaling_factor,
  685. $ch/2 - (unscale($center->y) - $self->config2->{offset}->[Y]) * $self->scaling_factor, #))
  686. ]);
  687. $self->Refresh;
  688. }
  689. sub project_layers {
  690. my ($self, $layers) = @_;
  691. $self->layers($layers);
  692. $self->Refresh;
  693. }
  694. sub _repaint {
  695. my ($self) = @_;
  696. my $dc = Wx::AutoBufferedPaintDC->new($self);
  697. my ($cw, $ch) = $self->GetSizeWH;
  698. return if $cw == 0; # when canvas is not rendered yet, size is 0,0
  699. $dc->SetPen(Wx::Pen->new(wxBLACK, 1, wxSOLID));
  700. $dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
  701. $dc->DrawRectangle(0, 0, $cw, $ch);
  702. return if !$self->config;
  703. # turn size into max visible coordinates
  704. # TODO: or should we use ClientArea?
  705. $cw--;
  706. $ch--;
  707. # draw bed
  708. if ($self->config2->{show_bed}) {
  709. $dc->SetPen(Wx::Pen->new(wxRED, 2, wxSOLID));
  710. $dc->SetBrush(Wx::Brush->new(wxWHITE, wxTRANSPARENT));
  711. # draw contour
  712. my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
  713. $dc->DrawPolygon($self->scaled_points_to_pixel($bed_polygon), 0, 0);
  714. # draw grid
  715. $dc->SetPen(Wx::Pen->new(wxRED, 1, wxSOLID));
  716. {
  717. my $bb = $bed_polygon->bounding_box;
  718. my $step = scale 10; # 1cm grid
  719. my @polylines = ();
  720. for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
  721. push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]);
  722. }
  723. for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
  724. push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
  725. }
  726. $dc->DrawLine(map @$_, @$_)
  727. for map $self->scaled_points_to_pixel([ @$_[0,-1] ]),
  728. @{intersection_pl(\@polylines, [$bed_polygon])};
  729. }
  730. # draw axes orientation
  731. $dc->SetPen(Wx::Pen->new(wxWHITE, 4, wxSOLID));
  732. {
  733. foreach my $endpoint ([10, 0], [0, 10]) {
  734. $dc->DrawLine(
  735. map @{$self->unscaled_point_to_pixel($_)}, [0,0], $endpoint
  736. );
  737. }
  738. $dc->SetTextForeground(wxWHITE);
  739. $dc->SetFont(Wx::Font->new(20, wxDEFAULT, wxNORMAL, wxNORMAL));
  740. $dc->DrawText("X", @{$self->unscaled_point_to_pixel([10, -2])});
  741. $dc->DrawText("Y", @{$self->unscaled_point_to_pixel([-2, 10])});
  742. }
  743. }
  744. return if !defined $self->layers;
  745. # get layers at this height
  746. # draw layers
  747. $dc->SetPen(Wx::Pen->new(wxWHITE, 1, wxSOLID));
  748. foreach my $layer (@{$self->layers}) {
  749. my @polygons = sort { $a->contains_point($b->first_point) ? -1 : 1 } map @$_, @{ $layer->slices };
  750. foreach my $copy (@{$layer->object->_shifted_copies}) {
  751. foreach my $polygon (@polygons) {
  752. $polygon = $polygon->clone;
  753. $polygon->translate(@$copy);
  754. if ($polygon->is_counter_clockwise) {
  755. $dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID));
  756. } else {
  757. $dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
  758. }
  759. $dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0);
  760. }
  761. }
  762. }
  763. }
  764. # convert a model coordinate into a pixel coordinate
  765. sub unscaled_point_to_pixel {
  766. my ($self, $point) = @_;
  767. my $zero = $self->bed_origin;
  768. my $p = [
  769. $point->[X] * $self->scaling_factor + $zero->[X],
  770. $point->[Y] * $self->scaling_factor + $zero->[Y],
  771. ];
  772. if (!$self->config2->{invert_y}) {
  773. my $ch = $self->GetSize->GetHeight;
  774. $p->[Y] = $ch - $p->[Y];
  775. }
  776. return $p;
  777. }
  778. sub scaled_points_to_pixel {
  779. my ($self, $points) = @_;
  780. return [
  781. map $self->unscaled_point_to_pixel($_),
  782. map Slic3r::Pointf->new_unscale(@$_),
  783. @$points
  784. ];
  785. }
  786. 1;