Rotating face to match plane (#4424)

* Prototype for selecting Face

* Fixed the speed issue - only allocates the array once

* Selecting faces works.

* Add UI elements to run rotate to bed function

(and a bad icon)

* Cleaned up a bit

* Optimized regular frame times (to a decent state) and added TODO for first frame time

* Add rotate face dialog

* Change how coloring for face selection works

* Cleanup according to comments

* Added grouped undos

* Easy fix for variant of #4420

* Added plane selection

* Edited UI labels to be more consistent

* Add a workable rotate face icon
This commit is contained in:
Benjamin Landers 2018-05-31 15:50:40 -07:00 committed by Joseph Lenox
parent f3b590911d
commit 9856947d45
7 changed files with 303 additions and 17 deletions

View File

@ -39,6 +39,7 @@ use Slic3r::GUI::Plater::3D;
use Slic3r::GUI::Plater::3DPreview; use Slic3r::GUI::Plater::3DPreview;
use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectPartsPanel;
use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectCutDialog;
use Slic3r::GUI::Plater::ObjectRotateFaceDialog;
use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog;
use Slic3r::GUI::Plater::LambdaObjectDialog; use Slic3r::GUI::Plater::LambdaObjectDialog;
use Slic3r::GUI::Plater::OverrideSettingsPanel; use Slic3r::GUI::Plater::OverrideSettingsPanel;

View File

@ -14,6 +14,7 @@ use Wx::GLCanvas qw(:all);
__PACKAGE__->mk_accessors( qw(_quat _dirty init __PACKAGE__->mk_accessors( qw(_quat _dirty init
enable_picking enable_picking
enable_face_select
enable_moving enable_moving
on_viewport_changed on_viewport_changed
on_hover on_hover
@ -517,13 +518,16 @@ sub set_bed_shape {
sub deselect_volumes { sub deselect_volumes {
my ($self) = @_; my ($self) = @_;
$_->selected(0) for @{$self->volumes}; $_->selected(0) for @{$self->volumes};
$_->selected_face(-1) for @{$self->volumes};
} }
sub select_volume { sub select_volume {
my ($self, $volume_idx) = @_; my ($self, $volume_idx) = @_;
$self->volumes->[$volume_idx]->selected(1) if ($volume_idx != -1) {
if $volume_idx != -1; $self->volumes->[$volume_idx]->selected(1);
$self->volumes->[$volume_idx]->selected_face($self->volumes->[$volume_idx]->hover_face);
}
} }
sub SetCuttingPlane { sub SetCuttingPlane {
@ -827,6 +831,7 @@ sub Render {
my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
$self->_hover_volume_idx(undef); $self->_hover_volume_idx(undef);
$_->hover(0) for @{$self->volumes}; $_->hover(0) for @{$self->volumes};
$_->hover_face(-1) for @{$self->volumes};
if ($volume_idx <= $#{$self->volumes}) { if ($volume_idx <= $#{$self->volumes}) {
$self->_hover_volume_idx($volume_idx); $self->_hover_volume_idx($volume_idx);
@ -837,6 +842,16 @@ sub Render {
} }
$self->on_hover->($volume_idx) if $self->on_hover; $self->on_hover->($volume_idx) if $self->on_hover;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
glFinish();
if($self->enable_face_select){
$self->draw_volumes(2, $volume_idx);
my $color = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
my $face_idx = $color->[0] + $color->[1]*256 + $color->[2]*256*256;
$self->volumes->[$volume_idx]->hover_face($face_idx);
}
} }
} }
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -1043,7 +1058,7 @@ sub draw_center_of_rotation {
} }
sub draw_volumes { sub draw_volumes {
my ($self, $fakecolor) = @_; my ($self, $fakecolor, $volume_to_render) = @_;
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -1051,22 +1066,28 @@ sub draw_volumes {
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_NORMAL_ARRAY);
foreach my $volume_idx (0..$#{$self->volumes}) { my $upper = $volume_to_render // $#{$self->volumes};
my $lower = $volume_to_render // 0;
foreach my $volume_idx ($lower..$upper) {
my $volume = $self->volumes->[$volume_idx]; my $volume = $self->volumes->[$volume_idx];
glPushMatrix(); glPushMatrix();
glTranslatef(@{$volume->origin}); glTranslatef(@{$volume->origin});
my @baseColor;
if ($fakecolor) { if ($fakecolor) {
my $r = ($volume_idx & 0x000000FF) >> 0; my $r = ($volume_idx & 0x000000FF) >> 0;
my $g = ($volume_idx & 0x0000FF00) >> 8; my $g = ($volume_idx & 0x0000FF00) >> 8;
my $b = ($volume_idx & 0x00FF0000) >> 16; my $b = ($volume_idx & 0x00FF0000) >> 16;
glColor4f($r/255.0, $g/255.0, $b/255.0, 1); @baseColor = ($r/255.0, $g/255.0, $b/255.0, 1);
} elsif ($self->enable_face_select){
@baseColor = @{ $volume->color };
} elsif ($volume->selected) { } elsif ($volume->selected) {
glColor4f( @SELECTED_COLOR , $volume->color->[3]); @baseColor = @SELECTED_COLOR;
push(@baseColor, $volume->color->[3]);
} elsif ($volume->hover) { } elsif ($volume->hover) {
glColor4f( @HOVER_COLOR, $volume->color->[3]); @baseColor = @HOVER_COLOR;
push(@baseColor, $volume->color->[3]);
} else { } else {
glColor4f(@{ $volume->color }); @baseColor = @{ $volume->color };
} }
my @sorted_z = (); my @sorted_z = ();
@ -1106,10 +1127,56 @@ sub draw_volumes {
} }
$min_offset //= 0; $min_offset //= 0;
$max_offset //= $volume->tverts->size; $max_offset //= $volume->tverts->size;
if($fakecolor && $fakecolor == 2){
glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr); our @_cached_colors;
glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr); our $_cached_ptr;
glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3); my $pLen = @_cached_colors;
my $toAdd = max($max_offset/3*4 - $pLen, 0);
if($toAdd){
# TODO: move this into CPP to reduce memory consumption and decrease time when new models are added
my @colors = (@baseColor)x($toAdd);
for my $i (0 .. ($#colors/12)){
my $r = (($i+($pLen/12)) & 0x000000FF) >> 0;
my $g = (($i+($pLen/12)) & 0x0000FF00) >> 8;
my $b = (($i+($pLen/12)) & 0x00FF0000) >> 16;
$colors[$i*4*3 + 0] = $r / 255.0;
$colors[$i*4*3 + 1] = $g / 255.0;
$colors[$i*4*3 + 2] = $b / 255.0;
$colors[$i*4*3 + 3] = 1.0;
$colors[$i*4*3 + 4] = $r / 255.0;
$colors[$i*4*3 + 5] = $g / 255.0;
$colors[$i*4*3 + 6] = $b / 255.0;
$colors[$i*4*3 + 7] = 1.0;
$colors[$i*4*3 + 8] = $r / 255.0;
$colors[$i*4*3 + 9] = $g / 255.0;
$colors[$i*4*3 + 10] = $b / 255.0;
$colors[$i*4*3 + 11] = 1.0;
}
push(@_cached_colors, @colors);
$_cached_ptr = OpenGL::Array->new_list(GL_FLOAT,@_cached_colors);
}
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer_c(4, GL_FLOAT, 0, $_cached_ptr->ptr());
glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
glDisableClientState(GL_COLOR_ARRAY);
} else {
glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
if ( (not $fakecolor) && $volume->selected && $volume->selected_face != -1 && $volume->selected_face <= $max_offset/3){
my $i = $volume->selected_face;
glColor4f(@SELECTED_COLOR,$volume->color->[3]);
glDrawArrays(GL_TRIANGLES, $i*3, 3);
}
if ( (not $fakecolor) && $volume->hover && $volume->hover_face != -1 && $volume->hover_face <= $max_offset/3){
my $i = $volume->hover_face;
glColor4f(@HOVER_COLOR,$volume->color->[3]);
glDrawArrays(GL_TRIANGLES, $i*3, 3);
}
glColor4f(@baseColor);
glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
}
} }
glPopMatrix(); glPopMatrix();
@ -1144,6 +1211,34 @@ sub draw_volumes {
glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
} }
sub calculate_normal {
my $self = shift;
my ($volume_idx) = @_;
return undef if $volume_idx == -1;
my $volume = $self->volumes->[$volume_idx];
my $max_offset = $volume->tverts->size; # For now just assume this TODO: add the other checks
if ($volume->selected && $volume->selected_face != -1 && $volume->selected_face <= $max_offset/3){
my $i = $volume->selected_face;
my $p1 = $volume->tverts->get_point($i*3);
my $p2 = $volume->tverts->get_point($i*3+1);
my $p3 = $volume->tverts->get_point($i*3+2);
my $v1 = $p1->vector_to($p2);
my $v2 = $p1->vector_to($p3);
# Calculate the cross product
my $x = $v1->y() * $v2->z() - $v1->z() * $v2->y();
my $y = $v1->z() * $v2->x() - $v1->x() * $v2->z();
my $z = $v1->x() * $v2->y() - $v1->y() * $v2->x();
# Normalize it
my $d = sqrt($x*$x + $y*$y + $z*$z);
return Slic3r::Pointf3->new($x/$d,$y/$d,$z/$d);
}
return undef;
}
package Slic3r::GUI::3DScene::Volume; package Slic3r::GUI::3DScene::Volume;
use Moo; use Moo;
@ -1153,7 +1248,9 @@ has 'color' => (is => 'ro', required => 1);
has 'select_group_id' => (is => 'rw', default => sub { -1 }); has 'select_group_id' => (is => 'rw', default => sub { -1 });
has 'drag_group_id' => (is => 'rw', default => sub { -1 }); has 'drag_group_id' => (is => 'rw', default => sub { -1 });
has 'selected' => (is => 'rw', default => sub { 0 }); has 'selected' => (is => 'rw', default => sub { 0 });
has 'selected_face' => (is => 'rw', default => sub { -1 });
has 'hover' => (is => 'rw', default => sub { 0 }); has 'hover' => (is => 'rw', default => sub { 0 });
has 'hover_face' => (is => 'rw', default => sub { -1 });
has 'range' => (is => 'rw'); has 'range' => (is => 'rw');
# geometric data # geometric data

View File

@ -23,6 +23,7 @@ use utf8;
use File::Basename qw(basename dirname); use File::Basename qw(basename dirname);
use List::Util qw(sum first max none any); use List::Util qw(sum first max none any);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg);
use Math::Trig qw(acos);
use LWP::UserAgent; use LWP::UserAgent;
use threads::shared qw(shared_clone); use threads::shared qw(shared_clone);
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc
@ -43,6 +44,7 @@ use constant TB_MORE => &Wx::NewId;
use constant TB_FEWER => &Wx::NewId; use constant TB_FEWER => &Wx::NewId;
use constant TB_45CW => &Wx::NewId; use constant TB_45CW => &Wx::NewId;
use constant TB_45CCW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId;
use constant TB_ROTFACE => &Wx::NewId;
use constant TB_SCALE => &Wx::NewId; use constant TB_SCALE => &Wx::NewId;
use constant TB_SPLIT => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId;
use constant TB_CUT => &Wx::NewId; use constant TB_CUT => &Wx::NewId;
@ -191,6 +193,7 @@ sub new {
$self->{htoolbar}->AddSeparator; $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_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_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ROTFACE, "Rotate face", Wx::Bitmap->new($Slic3r::var->("rotate_face.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_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}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), '');
@ -207,6 +210,7 @@ sub new {
decrease => "", decrease => "",
rotate45ccw => "", rotate45ccw => "",
rotate45cw => "", rotate45cw => "",
rotateFace => "",
changescale => "Scale…", changescale => "Scale…",
split => "Split", split => "Split",
cut => "Cut…", cut => "Cut…",
@ -214,7 +218,7 @@ sub new {
settings => "Settings…", settings => "Settings…",
); );
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut layers settings)) { for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw rotateFace changescale split cut layers settings)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"}); $self->{btoolbar}->Add($self->{"btn_$_"});
} }
@ -245,6 +249,7 @@ sub new {
decrease delete.png decrease delete.png
rotate45cw arrow_rotate_clockwise.png rotate45cw arrow_rotate_clockwise.png
rotate45ccw arrow_rotate_anticlockwise.png rotate45ccw arrow_rotate_anticlockwise.png
rotateFace rotate_face.png
changescale arrow_out.png changescale arrow_out.png
split shape_ungroup.png split shape_ungroup.png
cut package.png cut package.png
@ -282,6 +287,7 @@ sub new {
EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); EVT_TOOL($self, TB_FEWER, sub { $self->decrease; });
EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) });
EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) });
EVT_TOOL($self, TB_ROTFACE, sub { $_[0]->rotate_face });
EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); }); EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
@ -296,6 +302,7 @@ sub new {
EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; }); EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; });
EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); 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_rotate45ccw}, sub { $_[0]->rotate(45) });
EVT_BUTTON($self, $self->{btn_rotateFace}, sub { $_[0]->rotate_face });
EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); }); EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
@ -1023,7 +1030,14 @@ sub undo {
my $obj_idx = $self->get_object_index($identifier); my $obj_idx = $self->get_object_index($identifier);
$self->remove($obj_idx, 'true'); $self->remove($obj_idx, 'true');
} }
} } elsif ($type eq "GROUP"){
my @ops = @{$operation->{attributes}};
push @{$self->{undo_stack}}, @ops;
foreach my $op (@ops) {
$self->undo;
pop @{$self->{redo_stack}};
}
}
} }
sub redo { sub redo {
@ -1115,6 +1129,13 @@ sub redo {
$self->{objects}->[-$objects_count]->identifier($start_identifier++); $self->{objects}->[-$objects_count]->identifier($start_identifier++);
$objects_count--; $objects_count--;
} }
} elsif ($type eq "GROUP"){
my @ops = @{$operation->{attributes}};
foreach my $op (@ops) {
push @{$self->{redo_stack}}, $op;
$self->redo;
pop @{$self->{undo_stack}};
}
} }
} }
@ -1503,6 +1524,44 @@ sub center_selected_object_on_bed {
$self->refresh_canvases; $self->refresh_canvases;
} }
sub rotate_face {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
# Get the selected normal
if (!$Slic3r::GUI::have_OpenGL) {
Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions).");
return;
}
my $dlg = Slic3r::GUI::Plater::ObjectRotateFaceDialog->new($self,
object => $self->{objects}[$obj_idx],
model_object => $self->{model}->objects->[$obj_idx],
);
return unless $dlg->ShowModal == wxID_OK;
my $normal = $dlg->SelectedNormal;
return if !defined $normal;
my $axis = $dlg->SelectedAxis;
return if !defined $axis;
# Actual math to rotate
my $angleToXZ = atan2($normal->y(),$normal->x());
my $angleToZ = acos(-$normal->z());
$self->rotate(-rad2deg($angleToXZ),Z);
$self->rotate(rad2deg($angleToZ),Y);
if($axis == Z){
$self->add_undo_operation("GROUP", $object->identifier, splice(@{$self->{undo_stack}},-2));
} else {
if($axis == X){
$self->rotate(90,Y);
} else {
$self->rotate(90,X);
}
$self->add_undo_operation("GROUP", $object->identifier, splice(@{$self->{undo_stack}},-3));
}
}
sub rotate { sub rotate {
my $self = shift; my $self = shift;
my ($angle, $axis, $dont_push) = @_; my ($angle, $axis, $dont_push) = @_;
@ -2582,9 +2641,12 @@ sub make_thumbnail {
my ($obj_idx) = @_; my ($obj_idx) = @_;
my $plater_object = $self->{objects}[$obj_idx]; my $plater_object = $self->{objects}[$obj_idx];
return if($plater_object->remaking_thumbnail);
$plater_object->remaking_thumbnail(1);
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new); $plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
my $cb = sub { my $cb = sub {
$plater_object->make_thumbnail($self->{model}, $obj_idx); $plater_object->make_thumbnail($self->{model}, $obj_idx);
$plater_object->remaking_thumbnail(0);
if ($Slic3r::have_threads) { if ($Slic3r::have_threads) {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx ]))); Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx ])));
@ -2877,11 +2939,11 @@ sub selection_changed {
my $method = $have_sel ? 'Enable' : 'Disable'; my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method $self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut layers settings); for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw rotateFace changescale split cut layers settings);
if ($self->{htoolbar}) { if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_sel) $self->{htoolbar}->EnableTool($_, $have_sel)
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_LAYERS, TB_SETTINGS); for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTFACE, TB_SCALE, TB_SPLIT, TB_CUT, TB_LAYERS, TB_SETTINGS);
} }
if ($self->{object_info_size}) { # have we already loaded the info pane? if ($self->{object_info_size}) { # have we already loaded the info pane?
@ -3033,6 +3095,9 @@ sub object_menu {
wxTheApp->append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub { wxTheApp->append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub {
$self->rotate(+45); $self->rotate(+45);
}, undef, 'arrow_rotate_anticlockwise.png'); }, undef, 'arrow_rotate_anticlockwise.png');
wxTheApp->append_menu_item($menu, "Rotate Face to Plane", 'Rotates the selected object to have the selected face parallel with a plane', sub {
$self->rotate_face;
}, undef, 'rotate_face.png');
{ {
my $rotateMenu = Wx::Menu->new; my $rotateMenu = Wx::Menu->new;
@ -3194,6 +3259,7 @@ has 'input_file' => (is => 'rw');
has 'input_file_obj_idx' => (is => 'rw'); has 'input_file_obj_idx' => (is => 'rw');
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
has 'transformed_thumbnail' => (is => 'rw'); has 'transformed_thumbnail' => (is => 'rw');
has 'remaking_thumbnail' => (is => 'rw', default => sub { 0 });
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 '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' => (is => 'rw', default => sub { 0 });
has 'selected_instance' => (is => 'rw', default => sub { -1 }); has 'selected_instance' => (is => 'rw', default => sub { -1 });

View File

@ -0,0 +1,118 @@
# Rotate an object such that a face is aligned with a specified plane.
# This dialog gets opened with the "Rotate face" button above the platter.
package Slic3r::GUI::Plater::ObjectRotateFaceDialog;
use strict;
use warnings;
use utf8;
use POSIX qw(ceil);
use Scalar::Util qw(looks_like_number);
use Slic3r::Geometry qw(PI X Y Z);
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{model_object_idx} = $params{model_object_idx};
$self->{model_object} = $params{model_object};
$self->{normal} = undef;
# Note whether the window was already closed, so a pending update is not executed.
$self->{already_closed} = 0;
$self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1);
# Options
$self->{options} = {
axis => Z,
};
my $optgroup;
$optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Rotate to Align Face with Plane',
on_change => sub {
my ($opt_id) = @_;
if ($self->{options}{$opt_id} != $optgroup->get_value($opt_id)){
$self->{options}{$opt_id} = $optgroup->get_value($opt_id);
}
},
label_width => 120,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'axis',
type => 'select',
label => 'Plane',
labels => ['YZ','XZ','XY'],
values => [X,Y,Z],
default => $self->{options}{axis},
));
{
my $button_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{btn_rot} = Wx::Button->new($self, -1, "Rotate to Plane", wxDefaultPosition, wxDefaultSize);
$self->{btn_rot}->SetDefault;
$button_sizer->Add($self->{btn_rot}, 0, wxALIGN_RIGHT | wxALL, 10);
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
sizer => $button_sizer,
));
}
# left pane with tree
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
$left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
# right pane with preview canvas
my $canvas;
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
$canvas->load_object($self->{model_object}, undef, [0]);
$canvas->set_auto_bed_shape;
$canvas->SetSize([500,500]);
$canvas->enable_picking(1);
$canvas->enable_face_select(1);
$canvas->SetMinSize($canvas->GetSize);
$canvas->zoom_to_volumes;
$canvas->on_select(sub {
my ($volume_idx) = @_;
$self->{normal} = $canvas->calculate_normal($volume_idx);
});
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
$self->SetSizer($self->{sizer});
$self->SetMinSize($self->GetSize);
$self->{sizer}->SetSizeHints($self);
EVT_BUTTON($self, $self->{btn_rot}, sub {
$self->{already_closed} = 1;
$self->EndModal(wxID_OK);
$self->Destroy();
});
EVT_CLOSE($self, sub {
# Note that the window was already closed, so a pending update will not be executed.
$self->{already_closed} = 1;
$self->EndModal(wxID_CANCEL);
$self->Destroy();
});
return $self;
}
sub SelectedNormal {
my ($self) = @_;
return $self->{normal};
}
sub SelectedAxis {
my ($self) = @_;
return $self->{options}->{axis};
}
1;

BIN
var/rotate_face.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

View File

@ -41,6 +41,9 @@ class GLVertexArray {
this->norms.push_back(z); this->norms.push_back(z);
}; };
void load_mesh(const TriangleMesh &mesh); void load_mesh(const TriangleMesh &mesh);
Pointf3 get_point(int i){
return Pointf3(this->verts.at(i*3),this->verts.at(i*3+1),this->verts.at(i*3+2));
}
}; };
class _3DScene class _3DScene

View File

@ -14,6 +14,7 @@
%code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->verts.front(); %}; %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->verts.front(); %};
void* norms_ptr() const void* norms_ptr() const
%code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->norms.front(); %}; %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->norms.front(); %};
Clone< Pointf3 > get_point(int i);
}; };
%package{Slic3r::GUI::_3DScene}; %package{Slic3r::GUI::_3DScene};