diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 11f2ce5f2..058facbb4 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -4,6 +4,7 @@ use warnings; use utf8; use List::Util qw(min); +use Slic3r::Geometry qw(X Y Z); use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog :font :icon wxTheApp); use Wx::Event qw(EVT_CLOSE EVT_MENU); @@ -192,9 +193,19 @@ sub _init_menubar { $self->_append_menu_item($self->{object_menu}, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub { $plater->rotate(+45); }); - $self->_append_menu_item($self->{object_menu}, "Rotate…", 'Rotate the selected object by an arbitrary angle around Z axis', sub { - $plater->rotate(undef); + + my $rotateMenu = Wx::Menu->new; + $self->{object_menu}->AppendSubMenu($rotateMenu, "Rotate…", 'Rotate the selected object by an arbitrary angle'); + $self->_append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub { + $plater->rotate(undef, X); }); + $self->_append_menu_item($rotateMenu, "Around Y axis…", 'Rotate the selected object by an arbitrary angle around Y axis', sub { + $plater->rotate(undef, Y); + }); + $self->_append_menu_item($rotateMenu, "Around Z axis…", 'Rotate the selected object by an arbitrary angle around Z axis', sub { + $plater->rotate(undef, Z); + }); + $self->_append_menu_item($self->{object_menu}, "Scale…", 'Scale the selected object by an arbitrary factor', sub { $plater->changescale; }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 98961acdc..82497ef1f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -5,7 +5,7 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first); -use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale); +use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); use threads::shared qw(shared_clone); use Thread::Semaphore; use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc @@ -25,7 +25,6 @@ use constant TB_MORE => &Wx::NewId; use constant TB_FEWER => &Wx::NewId; use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; -use constant TB_ROTATE => &Wx::NewId; use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; use constant TB_VIEW => &Wx::NewId; @@ -96,7 +95,6 @@ sub new { $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_ROTATE, "Rotate…", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; @@ -160,7 +158,6 @@ sub new { decrease delete.png rotate45cw arrow_rotate_clockwise.png rotate45ccw arrow_rotate_anticlockwise.png - rotate arrow_rotate_clockwise.png changescale arrow_out.png split shape_ungroup.png view package.png @@ -187,7 +184,6 @@ sub new { EVT_TOOL($self, TB_FEWER, \&decrease); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); - EVT_TOOL($self, TB_ROTATE, sub { $_[0]->rotate(undef) }); EVT_TOOL($self, TB_SCALE, \&changescale); EVT_TOOL($self, TB_SPLIT, \&split_object); EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_cut_dialog }); @@ -202,7 +198,6 @@ sub new { EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) }); EVT_BUTTON($self, $self->{btn_changescale}, \&changescale); - EVT_BUTTON($self, $self->{btn_rotate}, sub { $_[0]->rotate(undef) }); EVT_BUTTON($self, $self->{btn_split}, \&split_object); EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); @@ -555,9 +550,13 @@ sub decrease { sub rotate { my $self = shift; - my ($angle) = @_; + my ($angle, $axis) = @_; + + $axis //= Z; my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + my $model_object = $self->{model}->objects->[$obj_idx]; my $model_instance = $model_object->instances->[0]; @@ -565,14 +564,27 @@ sub rotate { return if !$object->thumbnail; if (!defined $angle) { - $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $model_instance->rotation, -364, 364, $self); + my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; + $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate around $axis_name axis", $model_instance->rotation, -364, 364, $self); return if !$angle || $angle == -1; $angle = 0 - $angle; # rotate clockwise (be consistent with button icon) } { - my $new_angle = $model_instance->rotation + $angle; - $_->set_rotation($new_angle) for @{ $model_object->instances }; + if ($axis == Z) { + my $new_angle = $model_instance->rotation + $angle; + $_->set_rotation($new_angle) for @{ $model_object->instances }; + $object->transform_thumbnail($self->{model}, $obj_idx); + } else { + # rotation around X and Y needs to be performed on mesh + # so we first apply any Z rotation + if ($model_object->instances->[0]->rotation != 0) { + $model_object->rotate(deg2rad($model_object->instances->[0]->rotation), Z); + $_->set_rotation(0) for @{ $model_object->instances }; + } + $model_object->rotate(deg2rad($angle), $axis); + $self->make_thumbnail($obj_idx); + } $model_object->update_bounding_box; # update print and start background processing @@ -580,7 +592,6 @@ sub rotate { $self->{print}->add_model_object($model_object, $obj_idx); $self->schedule_background_process; - $object->transform_thumbnail($self->{model}, $obj_idx); } $self->selection_changed; # refresh info (size etc.) $self->update; @@ -1210,11 +1221,11 @@ sub selection_changed { my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw rotate changescale split view settings); + for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split view settings); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT, TB_VIEW, TB_SETTINGS); + for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_VIEW, TB_SETTINGS); } if ($self->{object_info_size}) { # have we already loaded the info pane? @@ -1338,8 +1349,10 @@ has 'selected' => (is => 'rw', default => sub { 0 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; - my $mesh = $model->objects->[$obj_idx]->raw_mesh; + # make method idempotent + $self->thumbnail->clear; + my $mesh = $model->objects->[$obj_idx]->raw_mesh; if ($mesh->facets_count <= 5000) { # remove polygons with area <= 1mm my $area_threshold = Slic3r::Geometry::scale 1; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index b647debc2..a2d740f91 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -3,7 +3,7 @@ use strict; use warnings; use utf8; -use Slic3r::Geometry qw(PI); +use Slic3r::Geometry qw(PI X); use Wx qw(:dialog :id :misc :sizer wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE EVT_BUTTON); use base 'Wx::Dialog'; @@ -133,7 +133,7 @@ sub perform_cut { if ($self->{cut_options}{keep_lower} && defined $lower_object) { push @{$self->{new_model_objects}}, $lower_object; if ($self->{cut_options}{rotate_lower}) { - $lower_object->rotate_x(PI); + $lower_object->rotate(PI, X); } } diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index e5cd02406..50393b0d5 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -463,13 +463,19 @@ sub translate { $self->_bounding_box->translate(@shift) if defined $self->_bounding_box; } -sub rotate_x { - my ($self, $angle) = @_; +sub rotate { + my ($self, $angle, $axis) = @_; # we accept angle in radians but mesh currently uses degrees $angle = rad2deg($angle); - $_->mesh->rotate_x($angle) for @{$self->volumes}; + if ($axis == X) { + $_->mesh->rotate_x($angle) for @{$self->volumes}; + } elsif ($axis == Y) { + $_->mesh->rotate_y($angle) for @{$self->volumes}; + } elsif ($axis == Z) { + $_->mesh->rotate_z($angle) for @{$self->volumes}; + } $self->invalidate_bounding_box; }