diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 55824104e..f0a8adc5d 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -9,6 +9,7 @@ use Slic3r::GUI::AboutDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::ObjectPartsPanel; +use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectPreviewDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index ed6e4a21f..87f6b4779 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -77,7 +77,9 @@ sub BUILD { $self->sizer->Add($grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5); foreach my $line (@{$self->lines}) { - if ($line->{widget}) { + if ($line->{sizer}) { + $self->sizer->Add($line->{sizer}, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); + } elsif ($line->{widget}) { my $window = $line->{widget}->GetWindow($self->parent); $self->sizer->Add($window, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15); } else { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2fbb438fd..35d19bee7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -86,7 +86,7 @@ sub new { $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; - $self->{htoolbar}->AddTool(TB_VIEW, "View", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_VIEW, "View/Cut…", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), ''); } else { my %tbar_buttons = ( @@ -101,7 +101,7 @@ sub new { rotate => "Rotate…", changescale => "Scale…", split => "Split", - view => "View", + view => "View/Cut…", settings => "Settings…", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); @@ -173,7 +173,7 @@ sub new { 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_preview_dialog }); + EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_cut_dialog }); EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); } else { EVT_BUTTON($self, $self->{btn_add}, \&add); @@ -187,7 +187,7 @@ sub new { 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_preview_dialog }); + EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); } @@ -1149,7 +1149,7 @@ sub list_item_activated { $self->object_preview_dialog($obj_idx); } -sub object_preview_dialog { +sub object_cut_dialog { my $self = shift; my ($obj_idx) = @_; @@ -1162,11 +1162,16 @@ sub object_preview_dialog { return; } - my $dlg = Slic3r::GUI::Plater::ObjectPreviewDialog->new($self, - object => $self->{objects}[$obj_idx], - model_object => $self->{model}->objects->[$obj_idx], + my $dlg = Slic3r::GUI::Plater::ObjectCutDialog->new($self, + object => $self->{objects}[$obj_idx], + model_object => $self->{model}->objects->[$obj_idx], ); $dlg->ShowModal; + + if (my @new_objects = $dlg->NewModelObjects) { + $self->remove($obj_idx); + $self->load_model_objects(@new_objects); + } } sub object_settings_dialog { diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm new file mode 100644 index 000000000..b16bf4280 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -0,0 +1,126 @@ +package Slic3r::GUI::Plater::ObjectCutDialog; +use strict; +use warnings; +use utf8; + +use Wx qw(: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->{new_model_objects} = []; + + # cut options + $self->{cut_options} = { + z => 0, + keep_upper => 1, + keep_lower => 1, + }; + my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL); + { + $self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize); + $cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10); + } + my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Cut', + options => [ + { + opt_key => 'z', + type => 'f', + label => 'Z', + default => $self->{cut_options}{z}, + }, + { + opt_key => 'keep_upper', + type => 'bool', + label => 'Upper part', + default => $self->{cut_options}{keep_upper}, + }, + { + opt_key => 'keep_lower', + type => 'bool', + label => 'Lower part', + default => $self->{cut_options}{keep_lower}, + }, + ], + lines => [ + { + label => 'Z', + options => [qw(z)], + }, + { + label => 'Keep', + options => [qw(keep_upper keep_lower)], + }, + { + sizer => $cut_button_sizer, + }, + ], + on_change => sub { + my ($opt_key, $value) = @_; + + $self->{cut_options}{$opt_key} = $value; + if ($opt_key eq 'z') { + if ($self->{canvas}) { + $self->{canvas}->SetCuttingPlane($value); + $self->{canvas}->Render; + } + } + }, + label_width => 50, + ); + + # 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::PreviewCanvas->new($self, $self->{model_object}); + $canvas->SetSize([500,500]); + $canvas->SetMinSize($canvas->GetSize); + } + + $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); + + # needed to actually free memory + EVT_CLOSE($self, sub { + $self->EndModal(wxID_OK); + $self->Destroy; + }); + + EVT_BUTTON($self, $self->{btn_cut}, sub { $self->perform_cut }); + + return $self; +} + +sub perform_cut { + my ($self) = @_; + + my ($upper_object, $lower_object) = $self->{model_object}->cut($self->{cut_options}{z}); + $self->{new_model_objects} = []; + push @{$self->{new_model_objects}}, $upper_object + if $self->{cut_options}{keep_upper} && defined $upper_object; + push @{$self->{new_model_objects}}, $lower_object + if $self->{cut_options}{keep_lower} && defined $lower_object; +} + +sub NewModelObjects { + my ($self) = @_; + return @{ $self->{new_model_objects} }; +} + +1; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index a2a0a6c90..29a5509fc 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -14,7 +14,9 @@ use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(quat dirty init mview_init object_bounding_box object_shift volumes initpos - sphi stheta) ); + sphi stheta + cutting_plane_z + ) ); use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; @@ -114,6 +116,11 @@ sub load_object { } } +sub SetCuttingPlane { + my ($self, $z) = @_; + $self->cutting_plane_z($z); +} + # Given an axis and angle, compute quaternion. sub axis_to_quat { my ($ax, $phi) = @_; @@ -440,6 +447,23 @@ sub Render { glVertex3f($axis_len, $y, $ground_z); } glEnd(); + + # draw cutting plane + if (defined $self->cutting_plane_z) { + my $plane_z = $z0 + $self->cutting_plane_z; + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glColor4f(1, 0.8, 0.8, 0.5); + glVertex3f(-$axis_len, -$axis_len, $plane_z); + glVertex3f($axis_len, -$axis_len, $plane_z); + glVertex3f($axis_len, $axis_len, $plane_z); + glVertex3f(-$axis_len, $axis_len, $plane_z); + glEnd(); + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + } } glPopMatrix(); diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index b80bc5ede..6180cc0ae 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -562,6 +562,53 @@ sub print_info { } } +sub cut { + my ($self, $z) = @_; + + # clone this one + my $upper = Slic3r::Model::Object->new( + input_file => $self->input_file, + model => $self->model, + config => $self->config->clone, + layer_height_ranges => $self->layer_height_ranges, + ); + my $lower = Slic3r::Model::Object->new( + input_file => $self->input_file, + model => $self->model, + config => $self->config->clone, + layer_height_ranges => $self->layer_height_ranges, + ); + $upper->add_instance(offset => Slic3r::Point->new(0,0)); + $lower->add_instance(offset => Slic3r::Point->new(0,0)); + + foreach my $volume (@{$self->volumes}) { + if ($volume->modifier) { + # don't cut modifiers + $upper->add_volume($volume); + $lower->add_volume($volume); + } else { + my $upper_mesh = Slic3r::TriangleMesh->new; + my $lower_mesh = Slic3r::TriangleMesh->new; + $volume->mesh->cut($z, $upper_mesh, $lower_mesh); + $upper_mesh->repair; + $lower_mesh->repair; + + $upper->add_volume( + material_id => $volume->material_id, + mesh => $upper_mesh, + modifier => $volume->modifier, + ); + $lower->add_volume( + material_id => $volume->material_id, + mesh => $lower_mesh, + modifier => $volume->modifier, + ); + } + } + + return ($upper, $lower); +} + package Slic3r::Model::Volume; use Moo;