From 9856947d45c957bdc66cc1ec7c5c92cd263f7ba4 Mon Sep 17 00:00:00 2001 From: Benjamin Landers Date: Thu, 31 May 2018 15:50:40 -0700 Subject: [PATCH] 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 --- lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/3DScene.pm | 123 ++++++++++++++++-- lib/Slic3r/GUI/Plater.pm | 74 ++++++++++- .../GUI/Plater/ObjectRotateFaceDialog.pm | 118 +++++++++++++++++ var/rotate_face.png | Bin 0 -> 643 bytes xs/src/slic3r/GUI/3DScene.hpp | 3 + xs/xsp/GUI_3DScene.xsp | 1 + 7 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm create mode 100644 var/rotate_face.png diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 6d2e5e3a8..1bbffc92e 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -39,6 +39,7 @@ use Slic3r::GUI::Plater::3D; use Slic3r::GUI::Plater::3DPreview; use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; +use Slic3r::GUI::Plater::ObjectRotateFaceDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::LambdaObjectDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index fd7dadb44..8835ff94b 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -14,6 +14,7 @@ use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(_quat _dirty init enable_picking + enable_face_select enable_moving on_viewport_changed on_hover @@ -517,13 +518,16 @@ sub set_bed_shape { sub deselect_volumes { my ($self) = @_; $_->selected(0) for @{$self->volumes}; + $_->selected_face(-1) for @{$self->volumes}; } sub select_volume { 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 { @@ -827,6 +831,7 @@ sub Render { my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256; $self->_hover_volume_idx(undef); $_->hover(0) for @{$self->volumes}; + $_->hover_face(-1) for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { $self->_hover_volume_idx($volume_idx); @@ -837,6 +842,16 @@ sub Render { } $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); @@ -1043,7 +1058,7 @@ sub draw_center_of_rotation { } sub draw_volumes { - my ($self, $fakecolor) = @_; + my ($self, $fakecolor, $volume_to_render) = @_; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -1051,22 +1066,28 @@ sub draw_volumes { glEnableClientState(GL_VERTEX_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]; glPushMatrix(); glTranslatef(@{$volume->origin}); - + my @baseColor; if ($fakecolor) { my $r = ($volume_idx & 0x000000FF) >> 0; my $g = ($volume_idx & 0x0000FF00) >> 8; 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) { - glColor4f( @SELECTED_COLOR , $volume->color->[3]); + @baseColor = @SELECTED_COLOR; + push(@baseColor, $volume->color->[3]); } elsif ($volume->hover) { - glColor4f( @HOVER_COLOR, $volume->color->[3]); + @baseColor = @HOVER_COLOR; + push(@baseColor, $volume->color->[3]); } else { - glColor4f(@{ $volume->color }); + @baseColor = @{ $volume->color }; } my @sorted_z = (); @@ -1106,10 +1127,56 @@ sub draw_volumes { } $min_offset //= 0; $max_offset //= $volume->tverts->size; - - 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); + if($fakecolor && $fakecolor == 2){ + our @_cached_colors; + our $_cached_ptr; + 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(); @@ -1144,6 +1211,34 @@ sub draw_volumes { 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; use Moo; @@ -1153,7 +1248,9 @@ has 'color' => (is => 'ro', required => 1); has 'select_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_face' => (is => 'rw', default => sub { -1 }); has 'hover' => (is => 'rw', default => sub { 0 }); +has 'hover_face' => (is => 'rw', default => sub { -1 }); has 'range' => (is => 'rw'); # geometric data diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8b2c26d0d..0190286d1 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -23,6 +23,7 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max none any); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg); +use Math::Trig qw(acos); use LWP::UserAgent; use threads::shared qw(shared_clone); 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_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; +use constant TB_ROTFACE => &Wx::NewId; use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; use constant TB_CUT => &Wx::NewId; @@ -191,6 +193,7 @@ 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_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_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), ''); @@ -207,6 +210,7 @@ sub new { decrease => "", rotate45ccw => "", rotate45cw => "", + rotateFace => "", changescale => "Scale…", split => "Split", cut => "Cut…", @@ -214,7 +218,7 @@ sub new { settings => "Settings…", ); $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->{btoolbar}->Add($self->{"btn_$_"}); } @@ -245,6 +249,7 @@ sub new { decrease delete.png rotate45cw arrow_rotate_clockwise.png rotate45ccw arrow_rotate_anticlockwise.png + rotateFace rotate_face.png changescale arrow_out.png split shape_ungroup.png cut package.png @@ -282,6 +287,7 @@ sub new { EVT_TOOL($self, TB_FEWER, sub { $self->decrease; }); EVT_TOOL($self, TB_45CW, 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_SPLIT, sub { $self->split_object; }); 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_rotate45cw}, 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_split}, sub { $self->split_object; }); 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); $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 { @@ -1115,6 +1129,13 @@ sub redo { $self->{objects}->[-$objects_count]->identifier($start_identifier++); $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; } +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 { my $self = shift; my ($angle, $axis, $dont_push) = @_; @@ -2582,9 +2641,12 @@ sub make_thumbnail { my ($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); my $cb = sub { $plater_object->make_thumbnail($self->{model}, $obj_idx); + $plater_object->remaking_thumbnail(0); if ($Slic3r::have_threads) { 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'; $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}) { $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? @@ -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 { $self->rotate(+45); }, 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; @@ -3194,6 +3259,7 @@ has 'input_file' => (is => 'rw'); has 'input_file_obj_idx' => (is => 'rw'); has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms 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 'selected' => (is => 'rw', default => sub { 0 }); has 'selected_instance' => (is => 'rw', default => sub { -1 }); diff --git a/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm b/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm new file mode 100644 index 000000000..e39ab5978 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm @@ -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; diff --git a/var/rotate_face.png b/var/rotate_face.png new file mode 100644 index 0000000000000000000000000000000000000000..ec7652ae2049451f88e76f1a68d50fcd38325fc3 GIT binary patch literal 643 zcmV-}0(||6P)_Iy#qD>GH7{NdpK3bNwOsIv}F0d$QkwnxYs!av6Z8aZU&bjA)_kX_ge{m?Ij521! zYtjGMCYnRUN2?8C7%%YxBY1;hi+!430T~Z8*dvvf^&u8Dh#@JjE8luJy4FzX{>eD( zk>H!)YXMIXPMtRqSeZ3O$w=Tp}R&1tPhgRpNvr*f{|GSd>7Ka!j}pm zu&Y?}3ku-0TZmpvO#71Jqhi9Th|c8eB%?t_kP+M$Oyy3#@Qcu0D+EH|*8Eh# z9}(gAd<|7A=&Pn;f^h~q*@Vg1UN?3(GL=*a#71*?$MY?@%`vu|&DbC}Zi-n~3iZIE z0(tjP2M4G7(BW_Z$vz*2g98A&!9lw(9(4RD`i+Dc+_ZyZbSp_tDg|QW%sT}}E zBYH|aRMF&nExFTQjkq*(uG)+0c;1wuIjJ0=(w%)hs-pa{y@@69{6;)pPsRi5-Q7ND z>m01()83CH(+7&`TgI^rZyQ;gWDXRH6Ynorms.push_back(z); }; 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 diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 4ff101937..c117976c2 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -14,6 +14,7 @@ %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->verts.front(); %}; void* norms_ptr() const %code%{ RETVAL = THIS->verts.empty() ? 0 : &THIS->norms.front(); %}; + Clone< Pointf3 > get_point(int i); }; %package{Slic3r::GUI::_3DScene};