From defe0a42790be49c89358a61080a17b3bc929414 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 28 May 2014 12:29:43 +0200 Subject: [PATCH] Move 2D canvas to a distinct class for better isolation --- lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater.pm | 270 +++------------------------------- lib/Slic3r/GUI/Plater/2D.pm | 284 ++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 249 deletions(-) create mode 100644 lib/Slic3r/GUI/Plater/2D.pm diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index f0a8adc5d..1e368d9f6 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -8,6 +8,7 @@ use FindBin; use Slic3r::GUI::AboutDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Plater; +use Slic3r::GUI::Plater::2D; use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectPreviewDialog; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a2750193e..e24e6014a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -4,11 +4,10 @@ use warnings; use utf8; use File::Basename qw(basename dirname); -use List::Util qw(max sum first); -use Slic3r::Geometry::Clipper qw(offset JT_ROUND); -use Slic3r::Geometry qw(X Y Z MIN MAX convex_hull scale unscale); +use List::Util qw(sum first); +use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale); use threads::shared qw(shared_clone); -use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :icon :id :listctrl :misc :panel :pen :sizer :toolbar :window); +use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window); use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE); use base 'Wx::Panel'; @@ -36,9 +35,6 @@ our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; our $EXPORT_FAILED_EVENT : shared = Wx::NewEventType; use constant CANVAS_SIZE => [335,335]; -use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8' - ? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap. - : 'Drag your objects here'; use constant FILAMENT_CHOOSERS_SPACING => 3; my $PreventListEvents = 0; @@ -54,19 +50,17 @@ sub new { $self->{print} = Slic3r::Print->new; $self->{objects} = []; - $self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, CANVAS_SIZE, wxTAB_TRAVERSAL); - $self->{canvas}->SetBackgroundColour(Wx::wxWHITE); - EVT_PAINT($self->{canvas}, \&repaint); - EVT_MOUSE_EVENTS($self->{canvas}, \&mouse_event); - - $self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID); - $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID); - $self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID); - $self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT); - $self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID); - $self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID); - $self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID); - $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID); + $self->{canvas} = Slic3r::GUI::Plater::2D->new($self, CANVAS_SIZE, $self->{objects}, $self->{model}, $self->{config}); + $self->{canvas}->on_select_object(sub { + my ($obj_idx) = @_; + $self->select_object($obj_idx); + }); + $self->{canvas}->on_double_click(sub { + $self->object_cut_dialog if $self->selected_object; + }); + $self->{canvas}->on_instance_moved(sub { + $self->update; + }); # toolbar for object manipulation if (!&Wx::wxMSW) { @@ -224,7 +218,7 @@ sub new { $self->on_export_failed; }); - $self->_update_bed_size; + $self->{canvas}->update_bed_size; $self->update; { @@ -597,8 +591,8 @@ sub arrange { # get the bounding box of the model area shown in the viewport my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ - Slic3r::Point->new(@{ $self->point_to_model_units([0,0]) }), - Slic3r::Point->new(@{ $self->point_to_model_units(CANVAS_SIZE) }), + Slic3r::Point->new(@{ $self->{canvas}->point_to_model_units([0,0]) }), + Slic3r::Point->new(@{ $self->{canvas}->point_to_model_units(CANVAS_SIZE) }), ]); eval { @@ -896,14 +890,6 @@ sub on_thumbnail_made { $self->{canvas}->Refresh; } -sub clean_instance_thumbnails { - my ($self) = @_; - - foreach my $object (@{ $self->{objects} }) { - @{ $object->instance_thumbnails } = (); - } -} - # this method gets called whenever print center is changed or the objects' bounding box changes # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { @@ -947,184 +933,14 @@ sub on_config_change { $self->Layout; } elsif ($self->{config}->has($opt_key)) { $self->{config}->set($opt_key, $value); - $self->_update_bed_size if $opt_key eq 'bed_size'; + if ($opt_key eq 'bed_size') { + $self->{canvas}->update_bed_size; + $self->update; + } $self->update if $opt_key eq 'print_center'; } } -sub _update_bed_size { - my $self = shift; - - # supposing the preview canvas is square, calculate the scaling factor - # to constrain print bed area inside preview - # when the canvas is not rendered yet, its GetSize() method returns 0,0 - # scaling_factor is expressed in pixel / mm - $self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size }); - $self->update; -} - -# this is called on the canvas -sub repaint { - my ($self, $event) = @_; - my $parent = $self->GetParent; - - my $dc = Wx::PaintDC->new($self); - my $size = $self->GetSize; - my @size = ($size->GetWidth, $size->GetHeight); - - # draw grid - $dc->SetPen($parent->{grid_pen}); - my $step = 10 * $parent->{scaling_factor}; - for (my $x = $step; $x <= $size[X]; $x += $step) { - $dc->DrawLine($x, 0, $x, $size[Y]); - } - for (my $y = $step; $y <= $size[Y]; $y += $step) { - $dc->DrawLine(0, $y, $size[X], $y); - } - - # draw print center - if (@{$parent->{objects}}) { - $dc->SetPen($parent->{print_center_pen}); - $dc->DrawLine($size[X]/2, 0, $size[X]/2, $size[Y]); - $dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2); - $dc->SetTextForeground(Wx::Colour->new(0,0,0)); - $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)); - $dc->DrawLabel("X = " . $parent->{config}->print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM); - $dc->DrawRotatedText("Y = " . $parent->{config}->print_center->[Y], 0, $size[Y]/2+15, 90); - } - - # draw frame - if (0) { - $dc->SetPen(wxBLACK_PEN); - $dc->SetBrush($parent->{transparent_brush}); - $dc->DrawRectangle(0, 0, @size); - } - - # draw text if plate is empty - if (!@{$parent->{objects}}) { - $dc->SetTextForeground(Wx::Colour->new(150,50,50)); - $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL)); - $dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); - } - - # draw thumbnails - $dc->SetPen(wxBLACK_PEN); - $parent->clean_instance_thumbnails; - for my $obj_idx (0 .. $#{$parent->{objects}}) { - my $object = $parent->{objects}[$obj_idx]; - my $model_object = $parent->{model}->objects->[$obj_idx]; - next unless defined $object->thumbnail; - for my $instance_idx (0 .. $#{$model_object->instances}) { - my $instance = $model_object->instances->[$instance_idx]; - next if !defined $object->transformed_thumbnail; - - my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates - $thumbnail->translate(map scale($_), @{$instance->offset}); - - $object->instance_thumbnails->[$instance_idx] = $thumbnail; - - if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) { - $dc->SetBrush($parent->{dragged_brush}); - } elsif ($object->selected) { - $dc->SetBrush($parent->{selected_brush}); - } else { - $dc->SetBrush($parent->{objects_brush}); - } - foreach my $expolygon (@$thumbnail) { - foreach my $points (@{$expolygon->pp}) { - $dc->DrawPolygon($parent->points_to_pixel($points, 1), 0, 0); - } - } - - if (0) { - # draw bounding box for debugging purposes - my $bb = $model_object->instance_bounding_box($instance_idx); - $bb->scale($parent->{scaling_factor}); - # no need to translate by instance offset because instance_bounding_box() does that - my $points = $bb->polygon->pp; - $dc->SetPen($parent->{clearance_pen}); - $dc->SetBrush($parent->{transparent_brush}); - $dc->DrawPolygon($parent->_y($points), 0, 0); - } - - # if sequential printing is enabled and we have more than one object, draw clearance area - if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{model}->objects}) > 1) { - my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($parent->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))}; - $dc->SetPen($parent->{clearance_pen}); - $dc->SetBrush($parent->{transparent_brush}); - $dc->DrawPolygon($parent->points_to_pixel($clearance, 1), 0, 0); - } - } - } - - # draw skirt - if (@{$parent->{objects}} && $parent->{config}->skirts) { - my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$parent->{objects}}; - if (@points >= 3) { - my ($convex_hull) = @{offset([convex_hull(\@points)], scale($parent->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))}; - $dc->SetPen($parent->{skirt_pen}); - $dc->SetBrush($parent->{transparent_brush}); - $dc->DrawPolygon($parent->points_to_pixel($convex_hull, 1), 0, 0); - } - } - - $event->Skip; -} - -sub mouse_event { - my ($self, $event) = @_; - my $parent = $self->GetParent; - - my $point = $event->GetPosition; - my $pos = $parent->point_to_model_units([ $point->x, $point->y ]); #]] - $pos = Slic3r::Point->new_scale(@$pos); - if ($event->ButtonDown(&Wx::wxMOUSE_BTN_LEFT)) { - $parent->select_object(undef); - for my $obj_idx (0 .. $#{$parent->{objects}}) { - my $object = $parent->{objects}->[$obj_idx]; - for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) { - my $thumbnail = $object->instance_thumbnails->[$instance_idx]; - if (defined first { $_->contour->contains_point($pos) } @$thumbnail) { - $parent->select_object($obj_idx); - my $instance = $parent->{model}->objects->[$obj_idx]->instances->[$instance_idx]; - my $instance_origin = [ map scale($_), @{$instance->offset} ]; - $self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units - $pos->x - $instance_origin->[X], - $pos->y - $instance_origin->[Y], #- - ]; - $self->{drag_object} = [ $obj_idx, $instance_idx ]; - } - } - } - $parent->Refresh; - } elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) { - $parent->update; - $parent->Refresh; - $self->{drag_start_pos} = undef; - $self->{drag_object} = undef; - $self->SetCursor(wxSTANDARD_CURSOR); - } elsif ($event->ButtonDClick) { - $parent->object_cut_dialog if $parent->selected_object; - } elsif ($event->Dragging) { - return if !$self->{drag_start_pos}; # concurrency problems - my ($obj_idx, $instance_idx) = @{ $self->{drag_object} }; - my $model_object = $parent->{model}->objects->[$obj_idx]; - $model_object->instances->[$instance_idx]->set_offset( - Slic3r::Pointf->new( - unscale($pos->[X] - $self->{drag_start_pos}[X]), - unscale($pos->[Y] - $self->{drag_start_pos}[Y]), - )); - $model_object->update_bounding_box; - $parent->Refresh; - } elsif ($event->Moving) { - my $cursor = wxSTANDARD_CURSOR; - if (defined first { $_->contour->contains_point($pos) } map @$_, map @{$_->instance_thumbnails}, @{ $parent->{objects} }) { - $cursor = Wx::Cursor->new(wxCURSOR_HAND); - } - $self->SetCursor($cursor); - } -} - sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; @@ -1314,50 +1130,6 @@ sub statusbar { return $self->skeinpanel->GetParent->{statusbar}; } -# coordinates of the model origin (0,0) in pixels -sub model_origin_to_pixel { - my ($self) = @_; - - return [ - CANVAS_SIZE->[X]/2 - ($self->{config}->print_center->[X] * $self->{scaling_factor}), - CANVAS_SIZE->[Y]/2 - ($self->{config}->print_center->[Y] * $self->{scaling_factor}), - ]; -} - -# convert a model coordinate into a pixel coordinate, assuming preview has square shape -sub point_to_pixel { - my ($self, $point) = @_; - - my $canvas_height = $self->{canvas}->GetSize->GetHeight; - my $zero = $self->model_origin_to_pixel; - return [ - $point->[X] * $self->{scaling_factor} + $zero->[X], - $canvas_height - ($point->[Y] * $self->{scaling_factor} + $zero->[Y]), - ]; -} - -sub points_to_pixel { - my ($self, $points, $unscale) = @_; - - my $result = []; - foreach my $point (@$points) { - $point = [ map unscale($_), @$point ] if $unscale; - push @$result, $self->point_to_pixel($point); - } - return $result; -} - -sub point_to_model_units { - my ($self, $point) = @_; - - my $canvas_height = $self->{canvas}->GetSize->GetHeight; - my $zero = $self->model_origin_to_pixel; - return [ - ($point->[X] - $zero->[X]) / $self->{scaling_factor}, - (($canvas_height - $point->[Y] - $zero->[Y]) / $self->{scaling_factor}), - ]; -} - package Slic3r::GUI::Plater::DropTarget; use Wx::DND; use base 'Wx::FileDropTarget'; diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm new file mode 100644 index 000000000..08765772b --- /dev/null +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -0,0 +1,284 @@ +package Slic3r::GUI::Plater::2D; +use strict; +use warnings; +use utf8; + +use List::Util qw(max first); +use Slic3r::Geometry qw(X Y scale unscale convex_hull); +use Slic3r::Geometry::Clipper qw(offset JT_ROUND); +use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); +use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT); +use base 'Wx::Panel'; + +use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8' + ? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap. + : 'Drag your objects here'; + +sub new { + my $class = shift; + my ($parent, $size, $objects, $model, $config) = @_; + + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL); + $self->SetBackgroundColour(Wx::wxWHITE); + + $self->{objects} = $objects; + $self->{model} = $model; + $self->{config} = $config; + $self->{on_select_object} = sub {}; + $self->{on_double_click} = sub {}; + $self->{on_instance_moved} = sub {}; + + $self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID); + $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID); + $self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID); + $self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT); + $self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID); + $self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID); + $self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID); + $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID); + + EVT_PAINT($self, \&repaint); + EVT_MOUSE_EVENTS($self, \&mouse_event); + + return $self; +} + +sub on_select_object { + my ($self, $cb) = @_; + $self->{on_select_object} = $cb; +} + +sub on_double_click { + my ($self, $cb) = @_; + $self->{on_double_click} = $cb; +} + +sub on_instance_moved { + my ($self, $cb) = @_; + $self->{on_instance_moved} = $cb; +} + +sub repaint { + my ($self, $event) = @_; + + my $dc = Wx::PaintDC->new($self); + my $size = $self->GetSize; + my @size = ($size->GetWidth, $size->GetHeight); + + # draw grid + $dc->SetPen($self->{grid_pen}); + my $step = 10 * $self->{scaling_factor}; + for (my $x = $step; $x <= $size[X]; $x += $step) { + $dc->DrawLine($x, 0, $x, $size[Y]); + } + for (my $y = $step; $y <= $size[Y]; $y += $step) { + $dc->DrawLine(0, $y, $size[X], $y); + } + + # draw print center + if (@{$self->{objects}}) { + $dc->SetPen($self->{print_center_pen}); + $dc->DrawLine($size[X]/2, 0, $size[X]/2, $size[Y]); + $dc->DrawLine(0, $size[Y]/2, $size[X], $size[Y]/2); + $dc->SetTextForeground(Wx::Colour->new(0,0,0)); + $dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)); + $dc->DrawLabel("X = " . $self->{config}->print_center->[X], Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM); + $dc->DrawRotatedText("Y = " . $self->{config}->print_center->[Y], 0, $size[Y]/2+15, 90); + } + + # draw frame + if (0) { + $dc->SetPen(wxBLACK_PEN); + $dc->SetBrush($self->{transparent_brush}); + $dc->DrawRectangle(0, 0, @size); + } + + # draw text if plate is empty + if (!@{$self->{objects}}) { + $dc->SetTextForeground(Wx::Colour->new(150,50,50)); + $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL)); + $dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); + } + + # draw thumbnails + $dc->SetPen(wxBLACK_PEN); + $self->clean_instance_thumbnails; + for my $obj_idx (0 .. $#{$self->{objects}}) { + my $object = $self->{objects}[$obj_idx]; + my $model_object = $self->{model}->objects->[$obj_idx]; + next unless defined $object->thumbnail; + for my $instance_idx (0 .. $#{$model_object->instances}) { + my $instance = $model_object->instances->[$instance_idx]; + next if !defined $object->transformed_thumbnail; + + my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates + $thumbnail->translate(map scale($_), @{$instance->offset}); + + $object->instance_thumbnails->[$instance_idx] = $thumbnail; + + if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) { + $dc->SetBrush($self->{dragged_brush}); + } elsif ($object->selected) { + $dc->SetBrush($self->{selected_brush}); + } else { + $dc->SetBrush($self->{objects_brush}); + } + foreach my $expolygon (@$thumbnail) { + foreach my $points (@{$expolygon->pp}) { + $dc->DrawPolygon($self->points_to_pixel($points, 1), 0, 0); + } + } + + if (0) { + # draw bounding box for debugging purposes + my $bb = $model_object->instance_bounding_box($instance_idx); + $bb->scale($self->{scaling_factor}); + # no need to translate by instance offset because instance_bounding_box() does that + my $points = $bb->polygon->pp; + $dc->SetPen($self->{clearance_pen}); + $dc->SetBrush($self->{transparent_brush}); + $dc->DrawPolygon($self->_y($points), 0, 0); + } + + # if sequential printing is enabled and we have more than one object, draw clearance area + if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) { + my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))}; + $dc->SetPen($self->{clearance_pen}); + $dc->SetBrush($self->{transparent_brush}); + $dc->DrawPolygon($self->points_to_pixel($clearance, 1), 0, 0); + } + } + } + + # draw skirt + if (@{$self->{objects}} && $self->{config}->skirts) { + my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}}; + if (@points >= 3) { + my ($convex_hull) = @{offset([convex_hull(\@points)], scale($self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))}; + $dc->SetPen($self->{skirt_pen}); + $dc->SetBrush($self->{transparent_brush}); + $dc->DrawPolygon($self->points_to_pixel($convex_hull, 1), 0, 0); + } + } + + $event->Skip; +} + +sub mouse_event { + my ($self, $event) = @_; + + my $point = $event->GetPosition; + my $pos = $self->point_to_model_units([ $point->x, $point->y ]); #]] + $pos = Slic3r::Point->new_scale(@$pos); + if ($event->ButtonDown(&Wx::wxMOUSE_BTN_LEFT)) { + $self->{on_select_object}->(undef); + for my $obj_idx (0 .. $#{$self->{objects}}) { + my $object = $self->{objects}->[$obj_idx]; + for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) { + my $thumbnail = $object->instance_thumbnails->[$instance_idx]; + if (defined first { $_->contour->contains_point($pos) } @$thumbnail) { + $self->{on_select_object}->($obj_idx); + my $instance = $self->{model}->objects->[$obj_idx]->instances->[$instance_idx]; + my $instance_origin = [ map scale($_), @{$instance->offset} ]; + $self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units + $pos->x - $instance_origin->[X], + $pos->y - $instance_origin->[Y], #- + ]; + $self->{drag_object} = [ $obj_idx, $instance_idx ]; + } + } + } + $self->Refresh; + } elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) { + $self->{on_instance_moved}->(); + $self->Refresh; + $self->{drag_start_pos} = undef; + $self->{drag_object} = undef; + $self->SetCursor(wxSTANDARD_CURSOR); + } elsif ($event->ButtonDClick) { + $self->{on_double_click}->(); + } elsif ($event->Dragging) { + return if !$self->{drag_start_pos}; # concurrency problems + my ($obj_idx, $instance_idx) = @{ $self->{drag_object} }; + my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->instances->[$instance_idx]->set_offset( + Slic3r::Pointf->new( + unscale($pos->[X] - $self->{drag_start_pos}[X]), + unscale($pos->[Y] - $self->{drag_start_pos}[Y]), + )); + $model_object->update_bounding_box; + $self->Refresh; + } elsif ($event->Moving) { + my $cursor = wxSTANDARD_CURSOR; + if (defined first { $_->contour->contains_point($pos) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) { + $cursor = Wx::Cursor->new(wxCURSOR_HAND); + } + $self->SetCursor($cursor); + } +} + +sub update_bed_size { + my $self = shift; + + # supposing the preview canvas is square, calculate the scaling factor + # to constrain print bed area inside preview + # when the canvas is not rendered yet, its GetSize() method returns 0,0 + # scaling_factor is expressed in pixel / mm + my $width = $self->GetSize->GetWidth; + $self->{scaling_factor} = $width / max(@{ $self->{config}->bed_size }) + if $width != 0; +} + +sub clean_instance_thumbnails { + my ($self) = @_; + + foreach my $object (@{ $self->{objects} }) { + @{ $object->instance_thumbnails } = (); + } +} + +# coordinates of the model origin (0,0) in pixels +sub model_origin_to_pixel { + my ($self) = @_; + + return [ + $self->GetSize->GetWidth/2 - ($self->{config}->print_center->[X] * $self->{scaling_factor}), + $self->GetSize->GetHeight/2 - ($self->{config}->print_center->[Y] * $self->{scaling_factor}), + ]; +} + +# convert a model coordinate into a pixel coordinate, assuming preview has square shape +sub point_to_pixel { + my ($self, $point) = @_; + + my $canvas_height = $self->GetSize->GetHeight; + my $zero = $self->model_origin_to_pixel; + return [ + $point->[X] * $self->{scaling_factor} + $zero->[X], + $canvas_height - ($point->[Y] * $self->{scaling_factor} + $zero->[Y]), + ]; +} + +sub points_to_pixel { + my ($self, $points, $unscale) = @_; + + my $result = []; + foreach my $point (@$points) { + $point = [ map unscale($_), @$point ] if $unscale; + push @$result, $self->point_to_pixel($point); + } + return $result; +} + +sub point_to_model_units { + my ($self, $point) = @_; + + my $canvas_height = $self->GetSize->GetHeight; + my $zero = $self->model_origin_to_pixel; + return [ + ($point->[X] - $zero->[X]) / $self->{scaling_factor}, + (($canvas_height - $point->[Y] - $zero->[Y]) / $self->{scaling_factor}), + ]; +} + +1;