diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 9f9aec454..c8cd20c5c 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -91,6 +91,7 @@ our $Settings = { color_toolpaths_by => 'role', tabbed_preset_editors => 1, show_host => 0, + nudge_val => 1 }, }; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8cd91bca6..2c118d434 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2727,7 +2727,10 @@ sub selection_changed { my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; - + + # Remove selection in 2d Plater. + $self->{canvas}->{selected_instance} = undef; + if (my $menu = $self->GetFrame->{plater_select_menu}) { $_->Check(0) for $menu->GetMenuItems; if ($have_sel) { @@ -2796,10 +2799,13 @@ sub selection_changed { sub select_object { my ($self, $obj_idx) = @_; - + $_->selected(0) for @{ $self->{objects} }; + $_->selected_instance(-1) for @{ $self->{objects} }; + if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); + $self->{objects}->[$obj_idx]->selected_instance(0); } $self->selection_changed(1); } @@ -3053,6 +3059,7 @@ has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled m has 'transformed_thumbnail' => (is => 'rw'); has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units has 'selected' => (is => 'rw', default => sub { 0 }); +has 'selected_instance' => (is => 'rw', default => sub { -1 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index 8fb8908e1..3081ba351 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -10,7 +10,7 @@ use List::Util qw(min max first); use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl); 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 Wx::Event qw(EVT_MOUSE_EVENTS EVT_KEY_DOWN EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE); use base 'Wx::Panel'; use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8' @@ -34,7 +34,8 @@ sub new { $self->{on_instances_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->{instance_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID); + $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,166,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); @@ -43,7 +44,9 @@ sub new { $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID); $self->{user_drawn_background} = $^O ne 'darwin'; - + + $self->{selected_instance} = undef; + EVT_PAINT($self, \&repaint); EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background}; EVT_MOUSE_EVENTS($self, \&mouse_event); @@ -51,6 +54,22 @@ sub new { $self->update_bed_size; $self->Refresh; }); + EVT_KEY_DOWN($self, sub { + my ($s, $event) = @_; + + my $key = $event->GetKeyCode; + if ($key == 65 || $key == 314) { + $self->nudge_instance('left'); + } elsif ($key == 87 || $key == 315) { + $self->nudge_instance('up'); + } elsif ($key == 68 || $key == 316) { + $self->nudge_instance('right'); + } elsif ($key == 83 || $key == 317) { + $self->nudge_instance('down'); + } else { + $event->Skip; + } + }); return $self; } @@ -77,7 +96,10 @@ sub on_instances_moved { sub repaint { my ($self, $event) = @_; - + + # Focus is needed in order to catch keyboard events. + $self->SetFocus; + my $dc = Wx::AutoBufferedPaintDC->new($self); my $size = $self->GetSize; my @size = ($size->GetWidth, $size->GetHeight); @@ -149,6 +171,8 @@ sub repaint { 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 && $object->selected_instance == $instance_idx) { + $dc->SetBrush($self->{instance_brush}); } elsif ($object->selected) { $dc->SetBrush($self->{selected_brush}); } else { @@ -201,7 +225,11 @@ sub mouse_event { my $pos = $event->GetPosition; my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]] if ($event->ButtonDown) { + # On Linux, Focus is needed in order to move selected instance using keyboard arrows. + $self->SetFocus; + $self->{on_select_object}->(undef); + $self->{selected_instance} = undef; # traverse objects and instances in reverse order, so that if they're overlapping # we get the one that gets drawn last, thus on top (as user expects that to move) OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) { @@ -220,6 +248,8 @@ sub mouse_event { $point->y - $instance_origin->[Y], #- ]; $self->{drag_object} = [ $obj_idx, $instance_idx ]; + $self->{objects}->[$obj_idx]->selected_instance($instance_idx); + $self->{selected_instance} = $self->{drag_object}; } elsif ($event->RightDown) { $self->{on_right_click}->($pos); } @@ -236,7 +266,7 @@ sub mouse_event { $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); } elsif ($event->LeftDClick) { - $self->{on_double_click}->(); + $self->{on_double_click}->(); } elsif ($event->Dragging) { return if !$self->{drag_start_pos}; # concurrency problems my ($obj_idx, $instance_idx) = @{ $self->{drag_object} }; @@ -257,6 +287,55 @@ sub mouse_event { } } +sub nudge_instance{ + my ($self, $direction) = @_; + + # Get the selected instance of an object. + if (!defined $self->{selected_instance}) { + # Check if an object is selected. + for my $obj_idx (0 .. $#{$self->{objects}}) { + if ($self->{objects}->[$obj_idx]->selected) { + if ($self->{objects}->[$obj_idx]->selected_instance != -1) { + $self->{selected_instance} = [$obj_idx, $self->{objects}->[$obj_idx]->selected_instance]; + } + } + } + } + return if not defined ($self->{selected_instance}); + my ($obj_idx, $instance_idx) = @{ $self->{selected_instance} }; + my $object = $self->{model}->objects->[$obj_idx]; + my $instance = $object->instances->[$instance_idx]; + + # Get the nudge values. + my $x_nudge = 0; + my $y_nudge = 0; + + $self->{nudge_value} = ($Slic3r::GUI::Settings->{_}{nudge_val} < 0.1 ? 0.1 : $Slic3r::GUI::Settings->{_}{nudge_val}) / &Slic3r::SCALING_FACTOR; + + if ($direction eq 'right'){ + $x_nudge = $self->{nudge_value}; + } elsif ($direction eq 'left'){ + $x_nudge = -1 * $self->{nudge_value}; + } elsif ($direction eq 'up'){ + $y_nudge = $self->{nudge_value}; + } elsif ($direction eq 'down'){ + $y_nudge = -$self->{nudge_value}; + } + my $point = Slic3r::Pointf->new($x_nudge, $y_nudge); + my $instance_origin = [ map scale($_), @{$instance->offset} ]; + $point = [ map scale($_), @{$point} ]; + + $instance->set_offset( + Slic3r::Pointf->new( + unscale( $instance_origin->[X] + $x_nudge), + unscale( $instance_origin->[Y] + $y_nudge), + )); + + $object->update_bounding_box; + $self->Refresh; + $self->{on_instances_moved}->(); +} + sub update_bed_size { my $self = shift; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index e6ce149f9..26a0b2fbc 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -85,6 +85,13 @@ sub new { tooltip => 'Shows/Hides the Controller Tab. Requires a restart of Slic3r.', default => $Slic3r::GUI::Settings->{_}{show_host}, )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'nudge_val', + type => 's', + label => '2D plater nudge value', + tooltip => 'In 2D plater, Move objects using keyboard by nudge value of', + default => $Slic3r::GUI::Settings->{_}{nudge_val}, + )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);