PrinterPanel.pm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. package Slic3r::GUI::Controller::PrinterPanel;
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use List::Util qw(first);
  6. use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :window :gauge :timer
  7. :textctrl :font :systemsettings);
  8. use Wx::Event qw(EVT_BUTTON EVT_MOUSEWHEEL EVT_TIMER EVT_SCROLLWIN);
  9. use base qw(Wx::Panel Class::Accessor);
  10. __PACKAGE__->mk_accessors(qw(printer_name config sender jobs
  11. printing status_timer temp_timer manual_control_config));
  12. use constant CONNECTION_TIMEOUT => 3; # seconds
  13. use constant STATUS_TIMER_INTERVAL => 1000; # milliseconds
  14. use constant TEMP_TIMER_INTERVAL => 5000; # milliseconds
  15. sub new {
  16. my ($class, $parent, $preset) = @_;
  17. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]);
  18. $self->printer_name($preset->name);
  19. $self->config($preset->dirty_config);
  20. $self->manual_control_config({
  21. xy_travel_speed => 130,
  22. z_travel_speed => 10,
  23. temperature => '',
  24. bed_temperature => '',
  25. });
  26. $self->jobs([]);
  27. # set up the timer that polls for updates
  28. {
  29. my $timer_id = &Wx::NewId();
  30. $self->status_timer(Wx::Timer->new($self, $timer_id));
  31. EVT_TIMER($self, $timer_id, sub {
  32. my ($self, $event) = @_;
  33. if ($self->printing) {
  34. my $queue_size = $self->sender->queue_size;
  35. $self->{gauge}->SetValue($self->{gauge}->GetRange - $queue_size);
  36. if ($queue_size == 0) {
  37. $self->print_completed;
  38. }
  39. }
  40. $self->append_to_log("$_\n") for @{$self->sender->purge_log};
  41. {
  42. my $temp = $self->sender->getT;
  43. if ($temp eq '') {
  44. $self->{temp_panel}->Hide;
  45. } else {
  46. if (!$self->{temp_panel}->IsShown) {
  47. $self->{temp_panel}->Show;
  48. $self->Layout;
  49. }
  50. $self->{temp_text}->SetLabel($temp . "°C");
  51. $temp = $self->sender->getB;
  52. if ($temp eq '') {
  53. $self->{bed_temp_text}->SetLabel('n.a.');
  54. } else {
  55. $self->{bed_temp_text}->SetLabel($temp . "°C");
  56. }
  57. }
  58. }
  59. });
  60. }
  61. # set up the timer that sends temperature requests
  62. # (responses are handled by status_timer)
  63. {
  64. my $timer_id = &Wx::NewId();
  65. $self->temp_timer(Wx::Timer->new($self, $timer_id));
  66. EVT_TIMER($self, $timer_id, sub {
  67. my ($self, $event) = @_;
  68. $self->sender->send("M105", 1); # send it through priority queue
  69. });
  70. }
  71. my $box = Wx::StaticBox->new($self, -1, "");
  72. my $sizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
  73. my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
  74. # printer name
  75. {
  76. my $text = Wx::StaticText->new($self, -1, $self->printer_name, wxDefaultPosition, [220,-1]);
  77. my $font = $text->GetFont;
  78. $font->SetPointSize(20);
  79. $text->SetFont($font);
  80. $left_sizer->Add($text, 0, wxEXPAND, 0);
  81. }
  82. # connection info
  83. {
  84. my $conn_sizer = Wx::FlexGridSizer->new(2, 2, 1, 0);
  85. $conn_sizer->SetFlexibleDirection(wxHORIZONTAL);
  86. $conn_sizer->AddGrowableCol(1, 1);
  87. $left_sizer->Add($conn_sizer, 0, wxEXPAND | wxTOP, 5);
  88. {
  89. my $text = Wx::StaticText->new($self, -1, "Port:", wxDefaultPosition, wxDefaultSize);
  90. $text->SetFont($Slic3r::GUI::small_font);
  91. $conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  92. }
  93. my $serial_port_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  94. {
  95. $self->{serial_port_combobox} = Wx::ComboBox->new($self, -1, $self->config->serial_port, wxDefaultPosition, wxDefaultSize, []);
  96. $self->{serial_port_combobox}->SetFont($Slic3r::GUI::small_font);
  97. $self->update_serial_ports;
  98. $serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
  99. }
  100. {
  101. $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
  102. wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
  103. $btn->SetToolTipString("Rescan serial ports")
  104. if $btn->can('SetToolTipString');
  105. $serial_port_sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL, 0);
  106. EVT_BUTTON($self, $btn, sub { $self->update_serial_ports });
  107. }
  108. $conn_sizer->Add($serial_port_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  109. {
  110. my $text = Wx::StaticText->new($self, -1, "Speed:", wxDefaultPosition, wxDefaultSize);
  111. $text->SetFont($Slic3r::GUI::small_font);
  112. $conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  113. }
  114. my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  115. {
  116. $self->{serial_speed_combobox} = Wx::ComboBox->new($self, -1, $self->config->serial_speed, wxDefaultPosition, wxDefaultSize,
  117. ["57600", "115200", "250000"]);
  118. $self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font);
  119. $serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0);
  120. }
  121. {
  122. $self->{btn_disconnect} = my $btn = Wx::Button->new($self, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
  123. $btn->SetFont($Slic3r::GUI::small_font);
  124. if ($Slic3r::GUI::have_button_icons) {
  125. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG));
  126. }
  127. $serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
  128. EVT_BUTTON($self, $btn, \&disconnect);
  129. }
  130. $conn_sizer->Add($serial_speed_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  131. }
  132. # buttons
  133. {
  134. $self->{btn_connect} = my $btn = Wx::Button->new($self, -1, "Connect to printer", wxDefaultPosition, [-1, 40]);
  135. my $font = $btn->GetFont;
  136. $font->SetPointSize($font->GetPointSize + 2);
  137. $btn->SetFont($font);
  138. if ($Slic3r::GUI::have_button_icons) {
  139. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("arrow_up.png"), wxBITMAP_TYPE_PNG));
  140. }
  141. $left_sizer->Add($btn, 0, wxTOP, 15);
  142. EVT_BUTTON($self, $btn, \&connect);
  143. }
  144. # print progress bar
  145. {
  146. my $gauge = $self->{gauge} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
  147. $left_sizer->Add($self->{gauge}, 0, wxEXPAND | wxTOP, 15);
  148. $gauge->Hide;
  149. }
  150. # status
  151. $self->{status_text} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [200,-1]);
  152. $left_sizer->Add($self->{status_text}, 1, wxEXPAND | wxTOP, 15);
  153. # manual control
  154. {
  155. $self->{btn_manual_control} = my $btn = Wx::Button->new($self, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
  156. $btn->SetFont($Slic3r::GUI::small_font);
  157. if ($Slic3r::GUI::have_button_icons) {
  158. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG));
  159. }
  160. $btn->Hide;
  161. $left_sizer->Add($btn, 0, wxTOP, 15);
  162. EVT_BUTTON($self, $btn, sub {
  163. $self->{manual_control_dialog} = my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
  164. ($self, $self->config, $self->sender, $self->manual_control_config);
  165. $dlg->ShowModal;
  166. undef $self->{manual_control_dialog};
  167. });
  168. }
  169. # temperature
  170. {
  171. my $temp_panel = $self->{temp_panel} = Wx::Panel->new($self, -1);
  172. my $temp_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  173. my $temp_font = Wx::Font->new($Slic3r::GUI::small_font);
  174. $temp_font->SetWeight(wxFONTWEIGHT_BOLD);
  175. {
  176. my $text = Wx::StaticText->new($temp_panel, -1, "Temperature:", wxDefaultPosition, wxDefaultSize);
  177. $text->SetFont($Slic3r::GUI::small_font);
  178. $temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
  179. $self->{temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
  180. $self->{temp_text}->SetFont($temp_font);
  181. $self->{temp_text}->SetForegroundColour(Wx::wxRED);
  182. $temp_sizer->Add($self->{temp_text}, 1, wxALIGN_CENTER_VERTICAL);
  183. }
  184. {
  185. my $text = Wx::StaticText->new($temp_panel, -1, "Bed:", wxDefaultPosition, wxDefaultSize);
  186. $text->SetFont($Slic3r::GUI::small_font);
  187. $temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
  188. $self->{bed_temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
  189. $self->{bed_temp_text}->SetFont($temp_font);
  190. $self->{bed_temp_text}->SetForegroundColour(Wx::wxRED);
  191. $temp_sizer->Add($self->{bed_temp_text}, 1, wxALIGN_CENTER_VERTICAL);
  192. }
  193. $temp_panel->SetSizer($temp_sizer);
  194. $temp_panel->Hide;
  195. $left_sizer->Add($temp_panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
  196. }
  197. # print jobs panel
  198. $self->{print_jobs_sizer} = my $print_jobs_sizer = Wx::BoxSizer->new(wxVERTICAL);
  199. {
  200. my $text = Wx::StaticText->new($self, -1, "Queue:", wxDefaultPosition, wxDefaultSize);
  201. $text->SetFont($Slic3r::GUI::small_font);
  202. $print_jobs_sizer->Add($text, 0, wxEXPAND, 0);
  203. $self->{jobs_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize,
  204. wxVSCROLL | wxBORDER_NONE);
  205. $self->{jobs_panel}->SetScrollbars(0, 1, 0, 1);
  206. $self->{jobs_panel_sizer} = Wx::BoxSizer->new(wxVERTICAL);
  207. $self->{jobs_panel}->SetSizer($self->{jobs_panel_sizer});
  208. $print_jobs_sizer->Add($self->{jobs_panel}, 1, wxEXPAND, 0);
  209. # TODO: fix this. We're trying to pass the scroll event to the parent but it
  210. # doesn't work.
  211. EVT_SCROLLWIN($self->{jobs_panel}, sub {
  212. my ($panel, $event) = @_;
  213. my $controller = $self->GetParent;
  214. my $new_event = Wx::ScrollWinEvent->new(
  215. $event->GetEventType,
  216. $event->GetPosition,
  217. $event->GetOrientation,
  218. );
  219. $controller->ProcessEvent($new_event);
  220. }) if 0;
  221. }
  222. my $log_sizer = Wx::BoxSizer->new(wxVERTICAL);
  223. {
  224. my $text = Wx::StaticText->new($self, -1, "Log:", wxDefaultPosition, wxDefaultSize);
  225. $text->SetFont($Slic3r::GUI::small_font);
  226. $log_sizer->Add($text, 0, wxEXPAND, 0);
  227. my $log = $self->{log_textctrl} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize,
  228. wxTE_MULTILINE | wxBORDER_NONE);
  229. $log->SetBackgroundColour($self->GetBackgroundColour);
  230. $log->SetFont($Slic3r::GUI::small_font);
  231. $log->SetEditable(0);
  232. $log_sizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
  233. }
  234. $sizer->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
  235. $sizer->Add($print_jobs_sizer, 2, wxEXPAND | wxALL, 0);
  236. $sizer->Add($log_sizer, 1, wxEXPAND | wxLEFT, 15);
  237. $self->SetSizer($sizer);
  238. $self->SetMinSize($self->GetSize);
  239. $self->_update_connection_controls;
  240. return $self;
  241. }
  242. sub append_to_log {
  243. my ($self, $text) = @_;
  244. $self->{log_textctrl}->AppendText($text);
  245. }
  246. sub is_connected {
  247. my ($self) = @_;
  248. return $self->sender && $self->sender->is_connected;
  249. }
  250. sub _update_connection_controls {
  251. my ($self) = @_;
  252. $self->{btn_connect}->Show;
  253. $self->{btn_disconnect}->Hide;
  254. $self->{serial_port_combobox}->Enable;
  255. $self->{serial_speed_combobox}->Enable;
  256. $self->{btn_rescan_serial}->Enable;
  257. $self->{btn_manual_control}->Hide;
  258. $self->{btn_manual_control}->Disable;
  259. if ($self->is_connected) {
  260. $self->{btn_connect}->Hide;
  261. $self->{btn_manual_control}->Show;
  262. if (!$self->printing || $self->printing->paused) {
  263. $self->{btn_disconnect}->Show;
  264. $self->{btn_manual_control}->Enable;
  265. }
  266. $self->{serial_port_combobox}->Disable;
  267. $self->{serial_speed_combobox}->Disable;
  268. $self->{btn_rescan_serial}->Disable;
  269. }
  270. $self->Layout;
  271. }
  272. sub set_status {
  273. my ($self, $status) = @_;
  274. $self->{status_text}->SetLabel($status);
  275. $self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
  276. $self->{status_text}->Refresh;
  277. $self->Layout;
  278. }
  279. sub connect {
  280. my ($self) = @_;
  281. return if $self->is_connected;
  282. $self->set_status("Connecting...");
  283. $self->sender(Slic3r::GCode::Sender->new);
  284. my $res = $self->sender->connect(
  285. $self->{serial_port_combobox}->GetValue,
  286. $self->{serial_speed_combobox}->GetValue,
  287. );
  288. if (!$res) {
  289. $self->set_status("Connection failed. Check serial port and speed.");
  290. } else {
  291. if ($self->sender->wait_connected) {
  292. $self->set_status("Printer is online. You can now start printing from the queue on the right.");
  293. $self->status_timer->Start(STATUS_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
  294. $self->temp_timer->Start(TEMP_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
  295. # request temperature now, without waiting for the timer
  296. $self->sender->send("M105", 1);
  297. # Update the printer preset with the new connection info
  298. {
  299. my $preset = first { $_->name eq $self->printer_name } @{wxTheApp->presets->{printer}};
  300. if ($preset) {
  301. $preset->load_config;
  302. $preset->_dirty_config->set('serial_port', $self->{serial_port_combobox}->GetValue);
  303. $preset->_dirty_config->set('serial_speed', $self->{serial_speed_combobox}->GetValue);
  304. $preset->save([ 'serial_port', 'serial_speed' ]);
  305. }
  306. }
  307. } else {
  308. $self->set_status("Connection failed. Check serial port and speed.");
  309. }
  310. }
  311. $self->_update_connection_controls;
  312. $self->reload_jobs;
  313. }
  314. sub disconnect {
  315. my ($self) = @_;
  316. $self->status_timer->Stop;
  317. $self->temp_timer->Stop;
  318. return if !$self->is_connected;
  319. $self->printing->printing(0) if $self->printing;
  320. $self->printing(undef);
  321. $self->{gauge}->Hide;
  322. $self->{temp_panel}->Hide;
  323. $self->sender->disconnect;
  324. $self->set_status("");
  325. $self->_update_connection_controls;
  326. $self->reload_jobs;
  327. }
  328. sub update_serial_ports {
  329. my ($self) = @_;
  330. my $cb = $self->{serial_port_combobox};
  331. my $current = $cb->GetValue;
  332. $cb->Clear;
  333. $cb->Append($_) for wxTheApp->scan_serial_ports;
  334. $cb->SetValue($current);
  335. }
  336. sub load_print_job {
  337. my ($self, $gcode_file, $filament_stats) = @_;
  338. push @{$self->jobs}, my $job = Slic3r::GUI::Controller::PrinterPanel::PrintJob->new(
  339. id => time() . $gcode_file . rand(1000),
  340. gcode_file => $gcode_file,
  341. filament_stats => $filament_stats,
  342. );
  343. $self->reload_jobs;
  344. return $job;
  345. }
  346. sub delete_job {
  347. my ($self, $job) = @_;
  348. $self->jobs([ grep $_->id ne $job->id, @{$self->jobs} ]);
  349. $self->reload_jobs;
  350. }
  351. sub print_job {
  352. my ($self, $job) = @_;
  353. $self->printing($job);
  354. $job->printing(1);
  355. $self->reload_jobs;
  356. open my $fh, '<', $job->gcode_file;
  357. my $line_count = 0;
  358. while (my $row = <$fh>) {
  359. $self->sender->send($row);
  360. $line_count++;
  361. }
  362. close $fh;
  363. $self->_update_connection_controls;
  364. $self->{gauge}->SetRange($line_count);
  365. $self->{gauge}->SetValue(0);
  366. $self->{gauge}->Enable;
  367. $self->{gauge}->Show;
  368. $self->Layout;
  369. $self->set_status('Printing...');
  370. $self->append_to_log(sprintf "=====\n");
  371. $self->append_to_log(sprintf "Printing %s\n", $job->name);
  372. $self->append_to_log(sprintf "Print started at %s\n", $self->_timestamp);
  373. }
  374. sub print_completed {
  375. my ($self) = @_;
  376. my $job = $self->printing;
  377. $self->printing(undef);
  378. $job->printing(0);
  379. $job->printed(1);
  380. $self->_update_connection_controls;
  381. $self->{gauge}->Hide;
  382. $self->Layout;
  383. $self->set_status('Print completed.');
  384. $self->append_to_log(sprintf "Print completed at %s\n", $self->_timestamp);
  385. $self->reload_jobs;
  386. }
  387. sub reload_jobs {
  388. my ($self) = @_;
  389. # reorder jobs
  390. @{$self->jobs} = sort { ($a->printed <=> $b->printed) || ($a->timestamp <=> $b->timestamp) }
  391. @{$self->jobs};
  392. # remove all panels
  393. foreach my $child ($self->{jobs_panel_sizer}->GetChildren) {
  394. my $window = $child->GetWindow;
  395. $self->{jobs_panel_sizer}->Detach($window);
  396. # now $child does not exist anymore
  397. $window->Destroy;
  398. }
  399. # re-add all panels
  400. foreach my $job (@{$self->jobs}) {
  401. my $panel = Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel->new($self->{jobs_panel}, $job);
  402. $self->{jobs_panel_sizer}->Add($panel, 0, wxEXPAND | wxBOTTOM, 5);
  403. $panel->on_delete_job(sub {
  404. my ($job) = @_;
  405. $self->delete_job($job);
  406. });
  407. $panel->on_print_job(sub {
  408. my ($job) = @_;
  409. $self->print_job($job);
  410. });
  411. $panel->on_pause_print(sub {
  412. my ($job) = @_;
  413. $self->sender->pause_queue;
  414. $job->paused(1);
  415. $self->reload_jobs;
  416. $self->_update_connection_controls;
  417. $self->{gauge}->Disable;
  418. $self->set_status('Print is paused. Click on Resume to continue.');
  419. });
  420. $panel->on_abort_print(sub {
  421. my ($job) = @_;
  422. $self->sender->purge_queue;
  423. $self->printing(undef);
  424. $job->printing(0);
  425. $job->paused(0);
  426. $self->reload_jobs;
  427. $self->_update_connection_controls;
  428. $self->{gauge}->Disable;
  429. $self->{gauge}->Hide;
  430. $self->set_status('Print was aborted.');
  431. $self->append_to_log(sprintf "Print aborted at %s\n", $self->_timestamp);
  432. });
  433. $panel->on_resume_print(sub {
  434. my ($job) = @_;
  435. $self->sender->resume_queue;
  436. $job->paused(0);
  437. $self->reload_jobs;
  438. $self->_update_connection_controls;
  439. $self->{gauge}->Enable;
  440. $self->set_status('Printing...');
  441. });
  442. $panel->enable_print if $self->is_connected && !$self->printing;
  443. EVT_MOUSEWHEEL($panel, sub {
  444. my (undef, $event) = @_;
  445. Wx::PostEvent($self->{jobs_panel}, $event);
  446. $event->Skip;
  447. });
  448. }
  449. $self->{jobs_panel}->Layout;
  450. $self->{print_jobs_sizer}->Layout;
  451. }
  452. sub _timestamp {
  453. my ($self) = @_;
  454. my @time = localtime(time);
  455. return sprintf '%02d:%02d:%02d', @time[2,1,0];
  456. }
  457. package Slic3r::GUI::Controller::PrinterPanel::PrintJob;
  458. use Moo;
  459. use File::Basename qw(basename);
  460. has 'id' => (is => 'ro', required => 1);
  461. has 'timestamp' => (is => 'ro', default => sub { time });
  462. has 'gcode_file' => (is => 'ro', required => 1);
  463. has 'filament_stats' => (is => 'rw');
  464. has 'printing' => (is => 'rw', default => sub { 0 });
  465. has 'paused' => (is => 'rw', default => sub { 0 });
  466. has 'printed' => (is => 'rw', default => sub { 0 });
  467. sub name {
  468. my ($self) = @_;
  469. return basename($self->gcode_file);
  470. }
  471. package Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel;
  472. use strict;
  473. use warnings;
  474. use utf8;
  475. use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :font :dialog :icon :timer
  476. :colour :brush :pen);
  477. use Wx::Event qw(EVT_BUTTON EVT_TIMER EVT_ERASE_BACKGROUND);
  478. use base qw(Wx::Panel Class::Accessor);
  479. __PACKAGE__->mk_accessors(qw(job on_delete_job on_print_job on_pause_print on_resume_print
  480. on_abort_print blink_timer duration queued));
  481. sub new {
  482. my ($class, $parent, $job) = @_;
  483. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
  484. # Estimate print duration
  485. {
  486. my $estimator = Slic3r::GCode::TimeEstimator->new;
  487. $estimator->parse_file($job->gcode_file);
  488. $self->duration($estimator->time);
  489. }
  490. $self->job($job);
  491. $self->queued(scalar localtime);
  492. $self->SetBackgroundColour(Wx::SystemSettings::GetColour(Wx::wxSYS_COLOUR_LISTBOX));
  493. {
  494. my $white_brush = Wx::Brush->new($self->GetBackgroundColour, wxSOLID);
  495. my $pen = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
  496. EVT_ERASE_BACKGROUND($self, sub {
  497. my ($self, $event) = @_;
  498. my $dc = $event->GetDC;
  499. my $size = $self->GetSize;
  500. $dc->SetBrush($white_brush);
  501. $dc->SetPen($pen);
  502. $dc->DrawRoundedRectangle(0, 0, $size->GetWidth,$size->GetHeight, 6);
  503. });
  504. }
  505. my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
  506. {
  507. $self->{job_name_textctrl} = my $text = Wx::StaticText->new($self, -1, $job->name, wxDefaultPosition, wxDefaultSize);
  508. my $font = $text->GetFont;
  509. $font->SetWeight(wxFONTWEIGHT_BOLD);
  510. $text->SetFont($font);
  511. if ($job->printed) {
  512. $text->SetForegroundColour($Slic3r::GUI::grey);
  513. }
  514. $text->SetToolTipString("Queued on " . $self->queued)
  515. if $text->can('SetToolTipString');
  516. $left_sizer->Add($text, 0, wxEXPAND, 0);
  517. }
  518. {
  519. my $stats = join "\n",
  520. map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)",
  521. sort keys %{$job->filament_stats};
  522. $stats .= sprintf "\nEstimated time: %d hours and %d minutes",
  523. int($self->duration/3600), int($self->duration/60) % 60;
  524. my $text = Wx::StaticText->new($self, -1, $stats, wxDefaultPosition, wxDefaultSize);
  525. $text->SetFont($Slic3r::GUI::small_font);
  526. if ($job->printed && !$job->printing) {
  527. $text->SetForegroundColour($Slic3r::GUI::grey);
  528. }
  529. $left_sizer->Add($text, 0, wxEXPAND | wxTOP, 6);
  530. }
  531. my $buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
  532. my $button_style = Wx::wxBORDER_NONE | wxBU_EXACTFIT;
  533. {
  534. my $btn = $self->{btn_delete} = Wx::Button->new($self, -1, 'Delete',
  535. wxDefaultPosition, wxDefaultSize, $button_style);
  536. $btn->SetToolTipString("Delete this job from print queue")
  537. if $btn->can('SetToolTipString');
  538. $btn->SetFont($Slic3r::GUI::small_font);
  539. if ($Slic3r::GUI::have_button_icons) {
  540. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG));
  541. }
  542. if ($job->printing) {
  543. $btn->Hide;
  544. }
  545. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  546. EVT_BUTTON($self, $btn, sub {
  547. my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete this print job?", 'Delete Job', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
  548. return unless $res == wxID_YES;
  549. wxTheApp->CallAfter(sub {
  550. $self->on_delete_job->($job);
  551. });
  552. });
  553. }
  554. {
  555. my $label = $job->printed ? 'Print Again' : 'Print This';
  556. my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
  557. $button_style);
  558. $btn->SetFont($Slic3r::GUI::small_bold_font);
  559. if ($Slic3r::GUI::have_button_icons) {
  560. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
  561. $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG));
  562. #$btn->SetBitmapPosition(wxRIGHT);
  563. }
  564. $btn->Hide;
  565. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  566. EVT_BUTTON($self, $btn, sub {
  567. wxTheApp->CallAfter(sub {
  568. $self->on_print_job->($job);
  569. });
  570. });
  571. }
  572. {
  573. my $btn = $self->{btn_pause} = Wx::Button->new($self, -1, "Pause", wxDefaultPosition, wxDefaultSize,
  574. $button_style);
  575. $btn->SetFont($Slic3r::GUI::small_font);
  576. if (!$job->printing || $job->paused) {
  577. $btn->Hide;
  578. }
  579. if ($Slic3r::GUI::have_button_icons) {
  580. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_pause.png"), wxBITMAP_TYPE_PNG));
  581. $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
  582. }
  583. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  584. EVT_BUTTON($self, $btn, sub {
  585. wxTheApp->CallAfter(sub {
  586. $self->on_pause_print->($job);
  587. });
  588. });
  589. }
  590. {
  591. my $btn = $self->{btn_resume} = Wx::Button->new($self, -1, "Resume", wxDefaultPosition, wxDefaultSize,
  592. $button_style);
  593. $btn->SetFont($Slic3r::GUI::small_font);
  594. if (!$job->printing || !$job->paused) {
  595. $btn->Hide;
  596. }
  597. if ($Slic3r::GUI::have_button_icons) {
  598. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
  599. $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG));
  600. }
  601. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  602. EVT_BUTTON($self, $btn, sub {
  603. wxTheApp->CallAfter(sub {
  604. $self->on_resume_print->($job);
  605. });
  606. });
  607. }
  608. {
  609. my $btn = $self->{btn_abort} = Wx::Button->new($self, -1, "Abort", wxDefaultPosition, wxDefaultSize,
  610. $button_style);
  611. $btn->SetFont($Slic3r::GUI::small_font);
  612. if (!$job->printing) {
  613. $btn->Hide;
  614. }
  615. if ($Slic3r::GUI::have_button_icons) {
  616. $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG));
  617. $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
  618. }
  619. $buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
  620. EVT_BUTTON($self, $btn, sub {
  621. wxTheApp->CallAfter(sub {
  622. $self->on_abort_print->($job);
  623. });
  624. });
  625. }
  626. my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
  627. $sizer->Add($left_sizer, 1, wxEXPAND | wxALL, 6);
  628. $sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 6);
  629. $self->SetSizer($sizer);
  630. # set-up the timer that changes the job name color while printing
  631. if ($self->job->printing && !$self->job->paused) {
  632. my $timer_id = &Wx::NewId();
  633. $self->blink_timer(Wx::Timer->new($self, $timer_id));
  634. my $blink = 0; # closure
  635. my $colour = Wx::Colour->new(0, 190, 0);
  636. EVT_TIMER($self, $timer_id, sub {
  637. my ($self, $event) = @_;
  638. $self->{job_name_textctrl}->SetForegroundColour($blink ? Wx::wxBLACK : $colour);
  639. $blink = !$blink;
  640. });
  641. $self->blink_timer->Start(1000, wxTIMER_CONTINUOUS);
  642. }
  643. return $self;
  644. }
  645. sub enable_print {
  646. my ($self) = @_;
  647. if (!$self->job->printing) {
  648. $self->{btn_print}->Show;
  649. }
  650. $self->Layout;
  651. }
  652. sub Destroy {
  653. my ($self) = @_;
  654. # There's a gap between the time Perl destroys the wxPanel object and
  655. # the blink_timer member, so the wxTimer might still fire an event which
  656. # isn't handled properly, causing a crash. So we ensure that blink_timer
  657. # is stopped before we destroy the wxPanel.
  658. $self->blink_timer->Stop if $self->blink_timer;
  659. return $self->SUPER::Destroy;
  660. }
  661. 1;