SplineControl.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. package Slic3r::GUI::Plater::SplineControl;
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use List::Util qw(min max first);
  6. use Slic3r::Geometry qw(X Y scale unscale);
  7. use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
  8. use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
  9. use base 'Wx::Panel';
  10. # Color Scheme
  11. use Slic3r::GUI::ColorScheme;
  12. sub new {
  13. my $class = shift;
  14. my ($parent, $size, $object) = @_;
  15. if ( ( defined $Slic3r::GUI::Settings->{_}{colorscheme} ) && ( Slic3r::GUI::ColorScheme->can($Slic3r::GUI::Settings->{_}{colorscheme}) ) ) {
  16. my $myGetSchemeName = \&{"Slic3r::GUI::ColorScheme::$Slic3r::GUI::Settings->{_}{colorscheme}"};
  17. $myGetSchemeName->();
  18. } else {
  19. Slic3r::GUI::ColorScheme->getDefault();
  20. }
  21. my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
  22. $self->{object} = $object;
  23. $self->{is_valid} = 0;
  24. # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
  25. $self->SetBackgroundColour(Wx::Colour->new(@BACKGROUND255));
  26. $self->{line_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_L_PEN), 1, wxSOLID);
  27. $self->{original_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_O_PEN), 1, wxSOLID);
  28. $self->{interactive_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_I_PEN), 1, wxSOLID);
  29. $self->{resulting_pen} = Wx::Pen->new(Wx::Colour->new(@SPLINE_R_PEN), 1, wxSOLID);
  30. $self->{user_drawn_background} = $^O ne 'darwin';
  31. # scale plot data to actual canvas, documentation in set_size_parameters
  32. $self->{scaling_factor_x} = 1;
  33. $self->{scaling_factor_y} = 1;
  34. $self->{min_layer_height} = 0.1;
  35. $self->{max_layer_height} = 0.4;
  36. $self->{mousover_layer_height} = undef; # display layer height at mousover position
  37. $self->{object_height} = 1.0;
  38. # initialize values
  39. $self->update;
  40. EVT_PAINT($self, \&repaint);
  41. EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
  42. EVT_MOUSE_EVENTS($self, \&mouse_event);
  43. EVT_SIZE($self, sub {
  44. $self->_update_canvas_size;
  45. $self->Refresh;
  46. });
  47. return $self;
  48. }
  49. sub repaint {
  50. my ($self, $event) = @_;
  51. my $dc = Wx::AutoBufferedPaintDC->new($self);
  52. my $size = $self->GetSize;
  53. my @size = ($size->GetWidth, $size->GetHeight);
  54. if ($self->{user_drawn_background}) {
  55. # On all systems the AutoBufferedPaintDC() achieves double buffering.
  56. # On MacOS the background is erased, on Windows the background is not erased
  57. # and on Linux/GTK the background is erased to gray color.
  58. # Fill DC with the background on Windows & Linux/GTK.
  59. my $brush_background = Wx::Brush->new(Wx::Colour->new(@BACKGROUND255), wxSOLID);
  60. my $pen_background = Wx::Pen->new(Wx::Colour->new(@BACKGROUND255), 1, wxSOLID);
  61. $dc->SetPen($pen_background);
  62. $dc->SetBrush($brush_background);
  63. my $rect = $self->GetUpdateRegion()->GetBox();
  64. $dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
  65. }
  66. # draw scale (min and max indicator at the bottom)
  67. $dc->SetTextForeground(Wx::Colour->new(0,0,0));
  68. $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
  69. $dc->DrawLabel(sprintf('%.4g', $self->{min_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_LEFT | wxALIGN_BOTTOM);
  70. $dc->DrawLabel(sprintf('%.2g', $self->{max_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_RIGHT | wxALIGN_BOTTOM);
  71. if($self->{mousover_layer_height}){
  72. $dc->DrawLabel(sprintf('%4.2fmm', $self->{mousover_layer_height}), Wx::Rect->new(0, 0, $size[0], $size[1]), wxALIGN_RIGHT | wxALIGN_TOP);
  73. }
  74. if($self->{is_valid}) {
  75. # draw original spline as reference
  76. if($self->{original_height_spline}) {
  77. #draw spline
  78. $self->_draw_layer_height_spline($dc, $self->{original_height_spline}, $self->{original_pen});
  79. }
  80. # draw interactive (currently modified by the user) layers as lines and spline
  81. if($self->{interactive_height_spline}) {
  82. # draw layer lines
  83. my @interpolated_layers = @{$self->{interactive_height_spline}->getInterpolatedLayers};
  84. $self->_draw_layers_as_lines($dc, $self->{interactive_pen}, \@interpolated_layers);
  85. #draw spline
  86. $self->_draw_layer_height_spline($dc, $self->{interactive_height_spline}, $self->{interactive_pen});
  87. }
  88. # draw resulting layers as lines
  89. unless($self->{interactive_heights}) {
  90. $self->_draw_layers_as_lines($dc, $self->{resulting_pen}, $self->{interpolated_layers});
  91. }
  92. # Always draw current BSpline, gives a reference during a modification
  93. $self->_draw_layer_height_spline($dc, $self->{object}->layer_height_spline, $self->{line_pen});
  94. }
  95. $event->Skip;
  96. }
  97. # Set basic parameters for this control.
  98. # min/max_layer_height are required to define the x-range, object_height is used to scale the y-range.
  99. # Must be called if object selection changes.
  100. sub set_size_parameters {
  101. my ($self, $min_layer_height, $max_layer_height, $object_height) = @_;
  102. $self->{min_layer_height} = $min_layer_height;
  103. $self->{max_layer_height} = $max_layer_height;
  104. $self->{object_height} = $object_height;
  105. $self->_update_canvas_size;
  106. $self->Refresh;
  107. }
  108. # Layers have been modified externally, re-initialize this control with new values
  109. sub update {
  110. my $self = shift;
  111. if(($self->{object}->layer_height_spline->layersUpdated || !$self->{heights}) && $self->{object}->layer_height_spline->hasData) {
  112. $self->{original_height_spline} = $self->{object}->layer_height_spline->clone; # make a copy to display the unmodified original spline
  113. $self->{original_layers} = $self->{object}->layer_height_spline->getOriginalLayers;
  114. $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers; # Initialize to current values
  115. # initialize height vector
  116. $self->{heights} = ();
  117. $self->{interactive_heights} = ();
  118. foreach my $z (@{$self->{original_layers}}) {
  119. push (@{$self->{heights}}, $self->{object}->layer_height_spline->getLayerHeightAt($z));
  120. }
  121. $self->{is_valid} = 1;
  122. }
  123. $self->Refresh;
  124. }
  125. # Callback to notify parent element if layers have changed and reslicing should be triggered
  126. sub on_layer_update {
  127. my ($self, $cb) = @_;
  128. $self->{on_layer_update} = $cb;
  129. }
  130. # Callback to tell parent element at which z-position the mouse currently hovers to update indicator in 3D-view
  131. sub on_z_indicator {
  132. my ($self, $cb) = @_;
  133. $self->{on_z_indicator} = $cb;
  134. }
  135. sub mouse_event {
  136. my ($self, $event) = @_;
  137. my $pos = $event->GetPosition;
  138. my @obj_pos = $self->pixel_to_point($pos);
  139. if ($event->ButtonDown) {
  140. if ($event->LeftDown) {
  141. # start dragging
  142. $self->{left_drag_start_pos} = $pos;
  143. $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
  144. }
  145. if ($event->RightDown) {
  146. # start dragging
  147. $self->{right_drag_start_pos} = $pos;
  148. $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
  149. }
  150. } elsif ($event->LeftUp) {
  151. if($self->{left_drag_start_pos}) {
  152. $self->_modification_done;
  153. }
  154. $self->{left_drag_start_pos} = undef;
  155. } elsif ($event->RightUp) {
  156. if($self->{right_drag_start_pos}) {
  157. $self->_modification_done;
  158. }
  159. $self->{right_drag_start_pos} = undef;
  160. } elsif ($event->Dragging) {
  161. if($self->{left_drag_start_pos}) {
  162. my @start_pos = $self->pixel_to_point($self->{left_drag_start_pos});
  163. my $range = abs($start_pos[1] - $obj_pos[1]);
  164. # compute updated interactive layer heights
  165. $self->_interactive_quadratic_curve($start_pos[1], $obj_pos[0], $range);
  166. unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
  167. die "Unable to update interactive interpolated layers!\n";
  168. }
  169. $self->Refresh;
  170. } elsif($self->{right_drag_start_pos}) {
  171. my @start_pos = $self->pixel_to_point($self->{right_drag_start_pos});
  172. my $range = $obj_pos[1] - $start_pos[1];
  173. # compute updated interactive layer heights
  174. $self->_interactive_linear_curve($start_pos[1], $obj_pos[0], $range);
  175. unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
  176. die "Unable to update interactive interpolated layers!\n";
  177. }
  178. $self->Refresh;
  179. }
  180. } elsif ($event->Moving) {
  181. if($self->{on_z_indicator}) {
  182. $self->{on_z_indicator}->($obj_pos[1]);
  183. $self->{mousover_layer_height} = $self->{object}->layer_height_spline->getLayerHeightAt($obj_pos[1]);
  184. $self->Refresh;
  185. $self->Update;
  186. }
  187. } elsif ($event->Leaving) {
  188. if($self->{on_z_indicator} && !$self->{left_drag_start_pos}) {
  189. $self->{on_z_indicator}->(undef);
  190. }
  191. $self->{mousover_layer_height} = undef;
  192. $self->Refresh;
  193. $self->Update;
  194. }
  195. }
  196. # Push modified heights to the spline object and update after user modification
  197. sub _modification_done {
  198. my $self = shift;
  199. if($self->{interactive_heights}) {
  200. $self->{heights} = $self->{interactive_heights};
  201. $self->{interactive_heights} = ();
  202. # update spline database
  203. unless($self->{object}->layer_height_spline->updateLayerHeights($self->{heights})) {
  204. die "Unable to update interpolated layers!\n";
  205. }
  206. $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers;
  207. }
  208. $self->Refresh;
  209. $self->{on_layer_update}->(@{$self->{interpolated_layers}});
  210. $self->{interactive_height_spline} = undef;
  211. }
  212. # Internal function to cache scaling factors
  213. sub _update_canvas_size {
  214. my $self = shift;
  215. # when the canvas is not rendered yet, its GetSize() method returns 0,0
  216. my $canvas_size = $self->GetSize;
  217. my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight);
  218. return if $canvas_w == 0;
  219. my $padding = $self->{canvas_padding} = 10; # border size in pixels
  220. my @size = ($canvas_w - 2*$padding, $canvas_h - 2*$padding);
  221. $self->{canvas_size} = [@size];
  222. if ($self->{max_layer_height} > $self->{min_layer_height}) {
  223. $self->{scaling_factor_x} = $size[0]/($self->{max_layer_height} - $self->{min_layer_height});
  224. } else {
  225. $self->{scaling_factor_x} = 1.0;
  226. }
  227. $self->{scaling_factor_y} = $size[1]/$self->{object_height};
  228. }
  229. # calculate new set of layers with quadaratic modifier for interactive display
  230. sub _interactive_quadratic_curve {
  231. my ($self, $mod_z, $target_layer_height, $range) = @_;
  232. $self->{interactive_heights} = (); # reset interactive curve
  233. # iterate over original points provided by spline
  234. my $last_z = 0;
  235. foreach my $i (0..@{$self->{heights}}-1 ) {
  236. my $z = $self->{original_layers}[$i];
  237. my $layer_h = $self->{heights}[$i];
  238. my $quadratic_factor = $self->_quadratic_factor($mod_z, $range, $z);
  239. my $diff = $target_layer_height - $layer_h;
  240. $layer_h += $diff * $quadratic_factor;
  241. push (@{$self->{interactive_heights}}, $layer_h);
  242. }
  243. }
  244. # calculate new set of layers with linear modifier for interactive display
  245. sub _interactive_linear_curve {
  246. my ($self, $mod_z, $target_layer_height, $range) = @_;
  247. $self->{interactive_heights} = (); # reset interactive curve
  248. my $from;
  249. my $to;
  250. if($range >= 0) {
  251. $from = $mod_z;
  252. $to = $mod_z + $range;
  253. }else{
  254. $from = $mod_z + $range;
  255. $to = $mod_z;
  256. }
  257. # iterate over original points provided by spline
  258. foreach my $i (0..@{$self->{heights}}-1 ) {
  259. if(($self->{original_layers}[$i]) >= $from && ($self->{original_layers}[$i]< $to)) {
  260. push (@{$self->{interactive_heights}}, $target_layer_height);
  261. }else{
  262. push (@{$self->{interactive_heights}}, $self->{heights}[$i]);
  263. }
  264. }
  265. }
  266. sub _quadratic_factor {
  267. my ($self, $fixpoint, $range, $value) = @_;
  268. # avoid division by zero
  269. $range = 0.00001 if $range <= 0;
  270. my $dist = abs($fixpoint - $value);
  271. my $x = $dist/$range; # normalize
  272. my $result = 1-($x*$x);
  273. return max(0, $result);
  274. }
  275. # Draw a set of layers as lines
  276. sub _draw_layers_as_lines {
  277. my ($self, $dc, $pen, $layers) = @_;
  278. $dc->SetPen($pen);
  279. my $last_z = 0.0;
  280. foreach my $z (@$layers) {
  281. my $layer_h = $z - $last_z;
  282. # only draw layers up to object height. The spline might contain one more layer due to
  283. # print_z / slice_z effects
  284. if($z le $self->{object_height}) {
  285. my $pl = $self->point_to_pixel(0, $z);
  286. my $pr = $self->point_to_pixel($layer_h, $z);
  287. $dc->DrawLine($pl->x, $pl->y, $pr->x, $pr->y);
  288. }
  289. $last_z = $z;
  290. }
  291. }
  292. # Draw the resulting spline from a LayerHeightSpline object over the full canvas height
  293. sub _draw_layer_height_spline {
  294. my ($self, $dc, $layer_height_spline, $pen) = @_;
  295. my @size = @{$self->{canvas_size}};
  296. $dc->SetPen($pen);
  297. my @points = ();
  298. foreach my $pixel (0..$size[1]) {
  299. my @z = $self->pixel_to_point(Wx::Point->new(0, $pixel));
  300. my $h = $layer_height_spline->getLayerHeightAt($z[1]);
  301. my $p = $self->point_to_pixel($h, $z[1]);
  302. push (@points, $p);
  303. }
  304. $dc->DrawLines(\@points);
  305. }
  306. # Takes a 2-tupel [layer_height (x), height(y)] and converts it
  307. # into a Wx::Point in scaled canvas coordinates
  308. sub point_to_pixel {
  309. my ($self, @point) = @_;
  310. my @size = @{$self->{canvas_size}};
  311. my $x = ($point[0] - $self->{min_layer_height})*$self->{scaling_factor_x} + $self->{canvas_padding};
  312. my $y = $size[1] - $point[1]*$self->{scaling_factor_y} + $self->{canvas_padding}; # invert y-axis
  313. return Wx::Point->new(min(max($x, $self->{canvas_padding}), $size[0]+$self->{canvas_padding}), min(max($y, $self->{canvas_padding}), $size[1]+$self->{canvas_padding})); # limit to canvas size
  314. }
  315. # Takes a Wx::Point in scaled canvas coordinates and converts it
  316. # into a 2-tupel [layer_height (x), height(y)]
  317. sub pixel_to_point {
  318. my ($self, $point) = @_;
  319. my @size = @{$self->{canvas_size}};
  320. my $x = ($point->x-$self->{canvas_padding})/$self->{scaling_factor_x} + $self->{min_layer_height};
  321. my $y = ($size[1] - $point->y)/$self->{scaling_factor_y}; # invert y-axis
  322. return (min(max($x, $self->{min_layer_height}), $self->{max_layer_height}), min(max($y, 0), $self->{object_height})); # limit to object size and layer constraints
  323. }
  324. 1;