ObjectSettingsDialog.pm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # This dialog opens up when double clicked on an object line in the list at the right side of the platter.
  2. # One may load additional STLs and additional modifier STLs,
  3. # one may change the properties of the print per each modifier mesh or a Z-span.
  4. package Slic3r::GUI::Plater::ObjectSettingsDialog;
  5. use strict;
  6. use warnings;
  7. use utf8;
  8. use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL wxTheApp);
  9. use Wx::Event qw(EVT_BUTTON EVT_MENU);
  10. use base 'Wx::Dialog';
  11. sub new {
  12. my $class = shift;
  13. my ($parent, %params) = @_;
  14. my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [1000,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
  15. $self->{$_} = $params{$_} for keys %params;
  16. $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
  17. $self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
  18. $self->{tabpanel}->AddPage($self->{adaptive_layers} = Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab->new( $self->{tabpanel},
  19. plater => $parent,
  20. model_object => $params{model_object},
  21. obj_idx => $params{obj_idx}
  22. ), "Adaptive Layers");
  23. $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layer height table");
  24. my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
  25. EVT_BUTTON($self, wxID_OK, sub {
  26. # validate user input
  27. return if !$self->{parts}->CanClose;
  28. return if !$self->{layers}->CanClose;
  29. # notify tabs
  30. $self->{layers}->Closing;
  31. # save window size
  32. wxTheApp->save_window_pos($self, "object_settings");
  33. $self->EndModal(wxID_OK);
  34. $self->Destroy;
  35. });
  36. my $sizer = Wx::BoxSizer->new(wxVERTICAL);
  37. $sizer->Add($self->{tabpanel}, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
  38. $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
  39. $self->SetSizer($sizer);
  40. $self->SetMinSize($self->GetSize);
  41. wxTheApp->restore_window_pos($self, "object_settings");
  42. return $self;
  43. }
  44. sub PartsChanged {
  45. my ($self) = @_;
  46. return $self->{parts}->PartsChanged;
  47. }
  48. sub PartSettingsChanged {
  49. my ($self) = @_;
  50. return $self->{parts}->PartSettingsChanged || $self->{layers}->LayersChanged;
  51. }
  52. package Slic3r::GUI::Plater::ObjectDialog::BaseTab;
  53. use base 'Wx::Panel';
  54. sub model_object {
  55. my ($self) = @_;
  56. return $self->GetParent->GetParent->{model_object};
  57. }
  58. package Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab;
  59. use Slic3r::Geometry qw(X Y Z scale unscale);
  60. use Slic3r::Print::State ':steps';
  61. use List::Util qw(min max sum first);
  62. use Wx qw(wxTheApp :dialog :id :misc :sizer :systemsettings :statictext wxTAB_TRAVERSAL);
  63. use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
  64. sub new {
  65. my $class = shift;
  66. my ($parent, %params) = @_;
  67. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
  68. my $model_object = $self->{model_object} = $params{model_object};
  69. my $obj_idx = $self->{obj_idx} = $params{obj_idx};
  70. my $plater = $self->{plater} = $params{plater};
  71. my $object = $self->{object} = $self->{plater}->{print}->get_object($self->{obj_idx});
  72. # store last raft height to correctly draw z-indicator plane during a running background job where the printObject is not valid
  73. $self->{last_raft_height} = 0;
  74. # Initialize 3D toolpaths preview
  75. if ($Slic3r::GUI::have_OpenGL) {
  76. $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self, $plater->{print});
  77. $self->{preview3D}->canvas->set_auto_bed_shape;
  78. $self->{preview3D}->canvas->SetSize([500,500]);
  79. $self->{preview3D}->canvas->SetMinSize($self->{preview3D}->canvas->GetSize);
  80. # object already processed?
  81. wxTheApp->CallAfter(sub {
  82. if (!$plater->{processed}) {
  83. $self->_trigger_slicing(0); # trigger processing without invalidating STEP_SLICE to keep current height distribution
  84. }else{
  85. $self->{preview3D}->reload_print($obj_idx);
  86. $self->{preview3D}->canvas->zoom_to_volumes;
  87. $self->{preview_zoomed} = 1;
  88. }
  89. });
  90. }
  91. $self->{splineControl} = Slic3r::GUI::Plater::SplineControl->new($self, Wx::Size->new(150, 200), $object);
  92. my $optgroup;
  93. $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
  94. parent => $self,
  95. title => 'Adaptive quality %',
  96. on_change => sub {
  97. my ($opt_id) = @_;
  98. # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
  99. # genates tens of events for a single value change.
  100. # Only trigger the recalculation if the value changes
  101. # or a live preview was activated and the mesh cut is not valid yet.
  102. if ($self->{adaptive_quality} != $optgroup->get_value($opt_id)) {
  103. $self->{adaptive_quality} = $optgroup->get_value($opt_id);
  104. $self->{model_object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id));
  105. $self->{object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id));
  106. $self->{object}->invalidate_step(STEP_LAYERS);
  107. # trigger re-slicing
  108. $self->_trigger_slicing;
  109. }
  110. },
  111. label_width => 0,
  112. );
  113. $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
  114. opt_id => 'adaptive_slicing_quality',
  115. type => 'slider',
  116. label => '',
  117. default => $object->config->get('adaptive_slicing_quality'),
  118. min => 0,
  119. max => 100,
  120. full_width => 1,
  121. ));
  122. $optgroup->get_field('adaptive_slicing_quality')->set_scale(1);
  123. $self->{adaptive_quality} = $object->config->get('adaptive_slicing_quality');
  124. # init quality slider
  125. if(!$object->config->get('adaptive_slicing')) {
  126. # disable slider
  127. $optgroup->get_field('adaptive_slicing_quality')->disable;
  128. }
  129. my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
  130. $right_sizer->Add($self->{splineControl}, 1, wxEXPAND | wxALL, 0);
  131. $right_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 0);
  132. $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
  133. $self->{sizer}->Add($self->{preview3D}, 3, wxEXPAND | wxTOP | wxBOTTOM, 0) if $self->{preview3D};
  134. $self->{sizer}->Add($right_sizer, 1, wxEXPAND | wxTOP | wxBOTTOM, 10);
  135. $self->SetSizerAndFit($self->{sizer});
  136. # init spline control values
  137. # determine min and max layer height from perimeter extruder capabilities.
  138. my %extruders;
  139. for my $region_id (0 .. ($object->region_count - 1)) {
  140. foreach (qw(perimeter_extruder infill_extruder solid_infill_extruder)) {
  141. my $extruder_id = $self->{plater}->{print}->get_region($region_id)->config->get($_)-1;
  142. $extruders{$extruder_id} = $extruder_id;
  143. }
  144. }
  145. my $min_height = max(map {$self->{plater}->{print}->config->get_at('min_layer_height', $_)} (values %extruders));
  146. my $max_height = min(map {$self->{plater}->{print}->config->get_at('max_layer_height', $_)} (values %extruders));
  147. $self->{splineControl}->set_size_parameters($min_height, $max_height, unscale($object->size->z));
  148. $self->{splineControl}->on_layer_update(sub {
  149. # trigger re-slicing
  150. $self->_trigger_slicing;
  151. });
  152. $self->{splineControl}->on_z_indicator(sub {
  153. my ($z) = @_;
  154. if($z) { # compensate raft height
  155. $z += $self->{last_raft_height};
  156. }
  157. $self->{preview3D}->canvas->SetCuttingPlane(Z, $z, []);
  158. $self->{preview3D}->canvas->Render;
  159. });
  160. return $self;
  161. }
  162. # This is called by the plater after processing to update the preview and spline
  163. sub reload_preview {
  164. my ($self) = @_;
  165. $self->{splineControl}->update;
  166. $self->{preview3D}->reload_print($self->{obj_idx});
  167. my $object = $self->{plater}->{print}->get_object($self->{obj_idx});
  168. if($object->layer_count-1 > 0) {
  169. my $first_layer = $self->{object}->get_layer(0);
  170. $self->{last_raft_height} = max(0, $first_layer->print_z - $first_layer->height);
  171. $self->{preview3D}->set_z(unscale($self->{object}->size->z));
  172. if(!$self->{preview_zoomed}) {
  173. $self->{preview3D}->canvas->set_auto_bed_shape;
  174. $self->{preview3D}->canvas->zoom_to_volumes;
  175. $self->{preview_zoomed} = 1;
  176. }
  177. }
  178. }
  179. # Trigger background slicing at the plater
  180. sub _trigger_slicing {
  181. my ($self, $invalidate) = @_;
  182. $invalidate //= 1;
  183. my $object = $self->{plater}->{print}->get_object($self->{obj_idx});
  184. $self->{model_object}->set_layer_height_spline($self->{object}->layer_height_spline); # push modified spline object to model_object
  185. #$self->{plater}->pause_background_process;
  186. $self->{plater}->stop_background_process;
  187. if (!$Slic3r::GUI::Settings->{_}{background_processing}) {
  188. $self->{plater}->statusbar->SetCancelCallback(sub {
  189. $self->{plater}->stop_background_process;
  190. $self->{plater}->statusbar->SetStatusText("Slicing cancelled");
  191. $self->{plater}->preview_notebook->SetSelection(0);
  192. });
  193. $object->invalidate_step(STEP_SLICE) if($invalidate);
  194. $self->{plater}->start_background_process;
  195. }else{
  196. $object->invalidate_step(STEP_SLICE) if($invalidate);
  197. $self->{plater}->schedule_background_process;
  198. }
  199. }
  200. package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
  201. use Wx qw(:dialog :id :misc :sizer :systemsettings);
  202. use Wx::Grid;
  203. use Wx::Event qw(EVT_GRID_CELL_CHANGED);
  204. use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
  205. sub new {
  206. my $class = shift;
  207. my ($parent, %params) = @_;
  208. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
  209. my $sizer = Wx::BoxSizer->new(wxVERTICAL);
  210. {
  211. my $label = Wx::StaticText->new($self, -1, "You can use this section to override the layer height for parts of this object. The values from this table will override the default layer height and adaptive layer heights, but not the interactively modified height curve.",
  212. wxDefaultPosition, wxDefaultSize);
  213. $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
  214. $label->Wrap(800);
  215. $sizer->Add($label, 0, wxEXPAND | wxALL, 10);
  216. }
  217. my $grid = $self->{grid} = Wx::Grid->new($self, -1, wxDefaultPosition, wxDefaultSize);
  218. $sizer->Add($grid, 1, wxEXPAND | wxALL, 10);
  219. $grid->CreateGrid(0, 3);
  220. $grid->DisableDragRowSize;
  221. $grid->HideRowLabels if &Wx::wxVERSION_STRING !~ / 2\.8\./;
  222. $grid->SetColLabelValue(0, "Min Z (mm)");
  223. $grid->SetColLabelValue(1, "Max Z (mm)");
  224. $grid->SetColLabelValue(2, "Layer height (mm)");
  225. $grid->SetColSize($_, -1) for 0..2;
  226. $grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
  227. # load data
  228. foreach my $range (@{ $self->model_object->layer_height_ranges }) {
  229. $grid->AppendRows(1);
  230. my $i = $grid->GetNumberRows-1;
  231. $grid->SetCellValue($i, $_, $range->[$_]) for 0..2;
  232. }
  233. $grid->AppendRows(1); # append one empty row
  234. EVT_GRID_CELL_CHANGED($grid, sub {
  235. my ($grid, $event) = @_;
  236. # remove any non-numeric character
  237. my $value = $grid->GetCellValue($event->GetRow, $event->GetCol);
  238. $value =~ s/,/./g;
  239. $value =~ s/[^0-9.]//g;
  240. $grid->SetCellValue($event->GetRow, $event->GetCol, $value);
  241. # if there's no empty row, let's append one
  242. for my $i (0 .. $grid->GetNumberRows) {
  243. if ($i == $grid->GetNumberRows) {
  244. # if we're here then we found no empty row
  245. $grid->AppendRows(1);
  246. last;
  247. }
  248. if (!grep $grid->GetCellValue($i, $_), 0..2) {
  249. # exit loop if this row is empty
  250. last;
  251. }
  252. }
  253. $self->{layers_changed} = 1;
  254. });
  255. $self->SetSizer($sizer);
  256. $sizer->SetSizeHints($self);
  257. return $self;
  258. }
  259. sub CanClose {
  260. my $self = shift;
  261. # validate ranges before allowing user to dismiss the dialog
  262. foreach my $range ($self->_get_ranges) {
  263. my ($min, $max, $height) = @$range;
  264. if ($max <= $min) {
  265. Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
  266. return 0;
  267. }
  268. if ($min < 0 || $max < 0) {
  269. Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
  270. return 0;
  271. }
  272. if ($height < 0) {
  273. Slic3r::GUI::show_error($self, "Invalid layer height $height.");
  274. return 0;
  275. }
  276. # TODO: check for overlapping ranges
  277. }
  278. return 1;
  279. }
  280. sub Closing {
  281. my $self = shift;
  282. # save ranges into the plater object
  283. $self->model_object->set_layer_height_ranges([ $self->_get_ranges ]);
  284. }
  285. sub _get_ranges {
  286. my $self = shift;
  287. my @ranges = ();
  288. for my $i (0 .. $self->{grid}->GetNumberRows-1) {
  289. my ($min, $max, $height) = map $self->{grid}->GetCellValue($i, $_), 0..2;
  290. next if $min eq '' || $max eq '' || $height eq '';
  291. push @ranges, [ $min, $max, $height ];
  292. }
  293. return sort { $a->[0] <=> $b->[0] } @ranges;
  294. }
  295. sub LayersChanged {
  296. my ($self) = @_;
  297. return $self->{layers_changed};
  298. }
  299. 1;