123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- package Slic3r::GUI::Plater::SplineControl;
- use strict;
- use warnings;
- use utf8;
- use List::Util qw(min max first);
- use Slic3r::Geometry qw(X Y scale unscale);
- use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
- use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
- use base 'Wx::Panel';
- sub new {
- my $class = shift;
- my ($parent, $size, $object) = @_;
-
- my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
-
- $self->{object} = $object;
- $self->{is_valid} = 0;
-
- # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
- $self->SetBackgroundColour(Wx::wxWHITE);
- $self->{line_pen} = Wx::Pen->new(Wx::Colour->new(50,50,50), 1, wxSOLID);
- $self->{original_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
- $self->{interactive_pen} = Wx::Pen->new(Wx::Colour->new(255,0,0), 1, wxSOLID);
- $self->{resulting_pen} = Wx::Pen->new(Wx::Colour->new(5,120,160), 1, wxSOLID);
- $self->{user_drawn_background} = $^O ne 'darwin';
- # scale plot data to actual canvas, documentation in set_size_parameters
- $self->{scaling_factor_x} = 1;
- $self->{scaling_factor_y} = 1;
- $self->{min_layer_height} = 0.1;
- $self->{max_layer_height} = 0.4;
- $self->{mousover_layer_height} = undef; # display layer height at mousover position
- $self->{object_height} = 1.0;
-
- # initialize values
- $self->update;
-
- EVT_PAINT($self, \&repaint);
- EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
- EVT_MOUSE_EVENTS($self, \&mouse_event);
- EVT_SIZE($self, sub {
- $self->_update_canvas_size;
- $self->Refresh;
- });
- return $self;
- }
- sub repaint {
- my ($self, $event) = @_;
-
- my $dc = Wx::AutoBufferedPaintDC->new($self);
- my $size = $self->GetSize;
- my @size = ($size->GetWidth, $size->GetHeight);
-
- if ($self->{user_drawn_background}) {
- # On all systems the AutoBufferedPaintDC() achieves double buffering.
- # On MacOS the background is erased, on Windows the background is not erased
- # and on Linux/GTK the background is erased to gray color.
- # Fill DC with the background on Windows & Linux/GTK.
- my $brush_background = Wx::Brush->new(Wx::wxWHITE, wxSOLID);
- $dc->SetPen(wxWHITE_PEN);
- $dc->SetBrush($brush_background);
- my $rect = $self->GetUpdateRegion()->GetBox();
- $dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
- }
-
- # draw scale (min and max indicator at the bottom)
- $dc->SetTextForeground(Wx::Colour->new(0,0,0));
- $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
- $dc->DrawLabel(sprintf('%.4g', $self->{min_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_LEFT | wxALIGN_BOTTOM);
- $dc->DrawLabel(sprintf('%.2g', $self->{max_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_RIGHT | wxALIGN_BOTTOM);
- if($self->{mousover_layer_height}){
- $dc->DrawLabel(sprintf('%4.2fmm', $self->{mousover_layer_height}), Wx::Rect->new(0, 0, $size[0], $size[1]), wxALIGN_RIGHT | wxALIGN_TOP);
- }
- if($self->{is_valid}) {
- # draw original spline as reference
- if($self->{original_height_spline}) {
- #draw spline
- $self->_draw_layer_height_spline($dc, $self->{original_height_spline}, $self->{original_pen});
- }
- # draw interactive (currently modified by the user) layers as lines and spline
- if($self->{interactive_height_spline}) {
- # draw layer lines
- my @interpolated_layers = @{$self->{interactive_height_spline}->getInterpolatedLayers};
- $self->_draw_layers_as_lines($dc, $self->{interactive_pen}, \@interpolated_layers);
- #draw spline
- $self->_draw_layer_height_spline($dc, $self->{interactive_height_spline}, $self->{interactive_pen});
- }
- # draw resulting layers as lines
- unless($self->{interactive_heights}) {
- $self->_draw_layers_as_lines($dc, $self->{resulting_pen}, $self->{interpolated_layers});
- }
- # Always draw current BSpline, gives a reference during a modification
- $self->_draw_layer_height_spline($dc, $self->{object}->layer_height_spline, $self->{line_pen});
- }
- $event->Skip;
- }
- # Set basic parameters for this control.
- # min/max_layer_height are required to define the x-range, object_height is used to scale the y-range.
- # Must be called if object selection changes.
- sub set_size_parameters {
- my ($self, $min_layer_height, $max_layer_height, $object_height) = @_;
-
- $self->{min_layer_height} = $min_layer_height;
- $self->{max_layer_height} = $max_layer_height;
- $self->{object_height} = $object_height;
-
- $self->_update_canvas_size;
- $self->Refresh;
- }
- # Layers have been modified externally, re-initialize this control with new values
- sub update {
- my $self = shift;
- if(($self->{object}->layer_height_spline->layersUpdated || !$self->{heights}) && $self->{object}->layer_height_spline->hasData) {
- $self->{original_height_spline} = $self->{object}->layer_height_spline->clone; # make a copy to display the unmodified original spline
- $self->{original_layers} = $self->{object}->layer_height_spline->getOriginalLayers;
- $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers; # Initialize to current values
-
- # initialize height vector
- $self->{heights} = ();
- $self->{interactive_heights} = ();
- foreach my $z (@{$self->{original_layers}}) {
- push (@{$self->{heights}}, $self->{object}->layer_height_spline->getLayerHeightAt($z));
- }
- $self->{is_valid} = 1;
- }
- $self->Refresh;
- }
- # Callback to notify parent element if layers have changed and reslicing should be triggered
- sub on_layer_update {
- my ($self, $cb) = @_;
- $self->{on_layer_update} = $cb;
- }
- # Callback to tell parent element at which z-position the mouse currently hovers to update indicator in 3D-view
- sub on_z_indicator {
- my ($self, $cb) = @_;
- $self->{on_z_indicator} = $cb;
- }
- sub mouse_event {
- my ($self, $event) = @_;
-
- my $pos = $event->GetPosition;
- my @obj_pos = $self->pixel_to_point($pos);
-
- if ($event->ButtonDown) {
- if ($event->LeftDown) {
- # start dragging
- $self->{left_drag_start_pos} = $pos;
- $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
- }
- if ($event->RightDown) {
- # start dragging
- $self->{right_drag_start_pos} = $pos;
- $self->{interactive_height_spline} = $self->{object}->layer_height_spline->clone;
- }
- } elsif ($event->LeftUp) {
- if($self->{left_drag_start_pos}) {
- $self->_modification_done;
- }
- $self->{left_drag_start_pos} = undef;
- } elsif ($event->RightUp) {
- if($self->{right_drag_start_pos}) {
- $self->_modification_done;
- }
- $self->{right_drag_start_pos} = undef;
- } elsif ($event->Dragging) {
- if($self->{left_drag_start_pos}) {
-
- my @start_pos = $self->pixel_to_point($self->{left_drag_start_pos});
- my $range = abs($start_pos[1] - $obj_pos[1]);
- # compute updated interactive layer heights
- $self->_interactive_quadratic_curve($start_pos[1], $obj_pos[0], $range);
- unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
- die "Unable to update interactive interpolated layers!\n";
- }
- $self->Refresh;
- } elsif($self->{right_drag_start_pos}) {
- my @start_pos = $self->pixel_to_point($self->{right_drag_start_pos});
- my $range = $obj_pos[1] - $start_pos[1];
- # compute updated interactive layer heights
- $self->_interactive_linear_curve($start_pos[1], $obj_pos[0], $range);
- unless($self->{interactive_height_spline}->updateLayerHeights($self->{interactive_heights})) {
- die "Unable to update interactive interpolated layers!\n";
- }
- $self->Refresh;
- }
- } elsif ($event->Moving) {
- if($self->{on_z_indicator}) {
- $self->{on_z_indicator}->($obj_pos[1]);
- $self->{mousover_layer_height} = $self->{object}->layer_height_spline->getLayerHeightAt($obj_pos[1]);
- $self->Refresh;
- $self->Update;
- }
- } elsif ($event->Leaving) {
- if($self->{on_z_indicator} && !$self->{left_drag_start_pos}) {
- $self->{on_z_indicator}->(undef);
- }
- $self->{mousover_layer_height} = undef;
- $self->Refresh;
- $self->Update;
- }
- }
- # Push modified heights to the spline object and update after user modification
- sub _modification_done {
- my $self = shift;
-
- if($self->{interactive_heights}) {
- $self->{heights} = $self->{interactive_heights};
- $self->{interactive_heights} = ();
- # update spline database
- unless($self->{object}->layer_height_spline->updateLayerHeights($self->{heights})) {
- die "Unable to update interpolated layers!\n";
- }
- $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers;
- }
- $self->Refresh;
- $self->{on_layer_update}->(@{$self->{interpolated_layers}});
- $self->{interactive_height_spline} = undef;
- }
- # Internal function to cache scaling factors
- sub _update_canvas_size {
- my $self = shift;
-
- # when the canvas is not rendered yet, its GetSize() method returns 0,0
- my $canvas_size = $self->GetSize;
- my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight);
- return if $canvas_w == 0;
- my $padding = $self->{canvas_padding} = 10; # border size in pixels
- my @size = ($canvas_w - 2*$padding, $canvas_h - 2*$padding);
- $self->{canvas_size} = [@size];
-
- $self->{scaling_factor_x} = $size[0]/($self->{max_layer_height} - $self->{min_layer_height});
- $self->{scaling_factor_y} = $size[1]/$self->{object_height};
- }
- # calculate new set of layers with quadaratic modifier for interactive display
- sub _interactive_quadratic_curve {
- my ($self, $mod_z, $target_layer_height, $range) = @_;
-
- $self->{interactive_heights} = (); # reset interactive curve
-
- # iterate over original points provided by spline
- my $last_z = 0;
- foreach my $i (0..@{$self->{heights}}-1 ) {
- my $z = $self->{original_layers}[$i];
- my $layer_h = $self->{heights}[$i];
- my $quadratic_factor = $self->_quadratic_factor($mod_z, $range, $z);
- my $diff = $target_layer_height - $layer_h;
- $layer_h += $diff * $quadratic_factor;
- push (@{$self->{interactive_heights}}, $layer_h);
- }
- }
- # calculate new set of layers with linear modifier for interactive display
- sub _interactive_linear_curve {
- my ($self, $mod_z, $target_layer_height, $range) = @_;
- $self->{interactive_heights} = (); # reset interactive curve
- my $from;
- my $to;
- if($range >= 0) {
- $from = $mod_z;
- $to = $mod_z + $range;
- }else{
- $from = $mod_z + $range;
- $to = $mod_z;
- }
- # iterate over original points provided by spline
- foreach my $i (0..@{$self->{heights}}-1 ) {
- if(($self->{original_layers}[$i]) >= $from && ($self->{original_layers}[$i]< $to)) {
- push (@{$self->{interactive_heights}}, $target_layer_height);
- }else{
- push (@{$self->{interactive_heights}}, $self->{heights}[$i]);
- }
- }
- }
- sub _quadratic_factor {
- my ($self, $fixpoint, $range, $value) = @_;
-
- # avoid division by zero
- $range = 0.00001 if $range <= 0;
-
- my $dist = abs($fixpoint - $value);
- my $x = $dist/$range; # normalize
- my $result = 1-($x*$x);
-
- return max(0, $result);
- }
- # Draw a set of layers as lines
- sub _draw_layers_as_lines {
- my ($self, $dc, $pen, $layers) = @_;
-
- $dc->SetPen($pen);
- my $last_z = 0.0;
- foreach my $z (@$layers) {
- my $layer_h = $z - $last_z;
- my $pl = $self->point_to_pixel(0, $z);
- my $pr = $self->point_to_pixel($layer_h, $z);
- $dc->DrawLine($pl->x, $pl->y, $pr->x, $pr->y);
- $last_z = $z;
- }
- }
- # Draw the resulting spline from a LayerHeightSpline object over the full canvas height
- sub _draw_layer_height_spline {
- my ($self, $dc, $layer_height_spline, $pen) = @_;
-
- my @size = @{$self->{canvas_size}};
-
- $dc->SetPen($pen);
- my @points = ();
- foreach my $pixel (0..$size[1]) {
- my @z = $self->pixel_to_point(Wx::Point->new(0, $pixel));
- my $h = $layer_height_spline->getLayerHeightAt($z[1]);
- my $p = $self->point_to_pixel($h, $z[1]);
- push (@points, $p);
- }
- $dc->DrawLines(\@points);
- }
- # Takes a 2-tupel [layer_height (x), height(y)] and converts it
- # into a Wx::Point in scaled canvas coordinates
- sub point_to_pixel {
- my ($self, @point) = @_;
-
- my @size = @{$self->{canvas_size}};
-
- my $x = ($point[0] - $self->{min_layer_height})*$self->{scaling_factor_x} + $self->{canvas_padding};
- my $y = $size[1] - $point[1]*$self->{scaling_factor_y} + $self->{canvas_padding}; # invert y-axis
- 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
- }
- # Takes a Wx::Point in scaled canvas coordinates and converts it
- # into a 2-tupel [layer_height (x), height(y)]
- sub pixel_to_point {
- my ($self, $point) = @_;
-
- my @size = @{$self->{canvas_size}};
-
- my $x = ($point->x-$self->{canvas_padding})/$self->{scaling_factor_x} + $self->{min_layer_height};
- my $y = ($size[1] - $point->y)/$self->{scaling_factor_y}; # invert y-axis
-
- 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
- }
- 1;
|