mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-10-05 02:16:31 +08:00
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:
parent
f3b590911d
commit
9856947d45
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 });
|
||||
|
118
lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm
Normal file
118
lib/Slic3r/GUI/Plater/ObjectRotateFaceDialog.pm
Normal 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
BIN
var/rotate_face.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 643 B |
@ -41,6 +41,9 @@ class GLVertexArray {
|
||||
this->norms.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
|
||||
|
@ -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};
|
||||
|
Loading…
x
Reference in New Issue
Block a user