diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 20a99304..8efdca8f 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -18,7 +18,8 @@ use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1"; -use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow); +use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow + :filedialog); use Wx::Event qw(EVT_CLOSE EVT_MENU EVT_IDLE); use base 'Wx::App'; @@ -349,6 +350,25 @@ sub output_path { : $dir; } +sub open_model { + my ($self) = @_; + + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} + || $Slic3r::GUI::Settings->{recent}{config_directory} + || ''; + + my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", + &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + return; + } + my @input_files = $dialog->GetPaths; + $dialog->Destroy; + + return @input_files; +} + sub CallAfter { my $class = shift; my ($cb) = @_; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8fd9ed5b..00f4d8eb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -361,14 +361,7 @@ sub filament_presets { sub add { my $self = shift; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Choose one or more files (STL/OBJ/AMF):', $dir, "", &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - if ($dialog->ShowModal != wxID_OK) { - $dialog->Destroy; - return; - } - my @input_files = $dialog->GetPaths; - $dialog->Destroy; + my @input_files = Slic3r::GUI::open_model($self); $self->load_file($_) for @input_files; } diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 9ec29d3b..ef4582b3 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -3,7 +3,8 @@ use strict; use warnings; use utf8; -use Wx qw(:misc :sizer :treectrl wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG); +use File::Basename qw(basename); +use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG); use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED); use base 'Wx::Panel'; @@ -29,33 +30,22 @@ sub new { $self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG)); $self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package_green.png", wxBITMAP_TYPE_PNG)); - my $rootId = $tree->AddRoot(""); - my %nodes = (); # material_id => nodeId - foreach my $volume_id (0..$#{$object->volumes}) { - my $volume = $object->volumes->[$volume_id]; - my $material_id = $volume->material_id; - $material_id //= '_'; - - if (!exists $nodes{$material_id}) { - my $material_name = $material_id eq '' - ? 'default' - : $object->model->get_material_name($material_id); - $nodes{$material_id} = $tree->AppendItem($rootId, "Material: $material_name", ICON_MATERIAL); - } - my $name = $volume->modifier ? 'Modifier mesh' : 'Solid mesh'; - my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; - my $itemId = $tree->AppendItem($nodes{$material_id}, $name, $icon); - $tree->SetPlData($itemId, { - type => 'volume', - volume_id => $volume_id, - }); - } - $tree->ExpandAll; + $tree->AddRoot(""); + $self->reload_tree; } + $self->{btn_load} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + $self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + # left pane with tree my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); $left_sizer->Add($tree, 0, wxEXPAND | wxALL, 10); + $left_sizer->Add($self->{btn_load}, 0); + $left_sizer->Add($self->{btn_delete}, 0); + if ($Slic3r::GUI::have_button_icons) { + $self->{btn_load}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG)); + $self->{btn_delete}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG)); + } # right pane with preview canvas my $canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object}); @@ -75,23 +65,94 @@ sub new { }); EVT_TREE_SEL_CHANGED($self, $tree, sub { my ($self, $event) = @_; - - # deselect all meshes - $_->{selected} = 0 for @{$canvas->volumes}; - - my $nodeId = $tree->GetSelection; - if ($nodeId->IsOk) { - my $itemData = $tree->GetPlData($nodeId); - if ($itemData && $itemData->{type} eq 'volume') { - $canvas->volumes->[ $itemData->{volume_id} ]{selected} = 1; - } - } - - $canvas->Render; + $self->selection_changed; }); + EVT_BUTTON($self, $self->{btn_load}, \&on_btn_load); + $self->selection_changed; return $self; } +sub reload_tree { + my ($self) = @_; + + my $object = $self->{model_object}; + my $tree = $self->{tree}; + my $rootId = $tree->GetRootItem; + + $tree->DeleteChildren($rootId); + + my %nodes = (); # material_id => nodeId + foreach my $volume_id (0..$#{$object->volumes}) { + my $volume = $object->volumes->[$volume_id]; + my $material_id = $volume->material_id; + $material_id //= '_'; + + if (!exists $nodes{$material_id}) { + my $material_name = $material_id eq '_' + ? 'default' + : $object->model->get_material_name($material_id); + $nodes{$material_id} = $tree->AppendItem($rootId, "Material: $material_name", ICON_MATERIAL); + } + my $name = $volume->modifier ? 'Modifier mesh' : 'Solid mesh'; + my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; + my $itemId = $tree->AppendItem($nodes{$material_id}, $name, $icon); + $tree->SetPlData($itemId, { + type => 'volume', + volume_id => $volume_id, + }); + } + $tree->ExpandAll; +} + +sub selection_changed { + my ($self) = @_; + + # deselect all meshes + $_->{selected} = 0 for @{$self->{canvas}->volumes}; + + # disable buttons + $self->{btn_delete}->Disable; + + my $nodeId = $self->{tree}->GetSelection; + if ($nodeId->IsOk) { + my $itemData = $self->{tree}->GetPlData($nodeId); + if ($itemData && $itemData->{type} eq 'volume') { + $self->{canvas}->volumes->[ $itemData->{volume_id} ]{selected} = 1; + $self->{btn_delete}->Enable; + } + } + + $self->{canvas}->Render; +} + +sub on_btn_load { + my ($self) = @_; + + my @input_files = Slic3r::GUI::open_model($self); + foreach my $input_file (@input_files) { + my $model = eval { Slic3r::Model->read_from_file($input_file) }; + if ($@) { + Slic3r::GUI::show_error($self, $@); + next; + } + + foreach my $object (@{$model->objects}) { + foreach my $volume (@{$object->volumes}) { + my $new_volume = $self->{model_object}->add_volume($volume); + if (!defined $new_volume->material_id) { + my $material_name = basename($input_file); + $material_name =~ s/\.(stl|obj)$//i; + $self->{model_object}->model->set_material($material_name); + $new_volume->material_id($material_name); + } + } + } + } + + $self->reload_tree; + $self->{canvas}->load_object($self->{model_object}); +} + 1; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 7635d33c..64d7fb01 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -29,40 +29,7 @@ sub new { $self->sphi(45); $self->stheta(-45); - my $bb = $object->raw_mesh->bounding_box; - my $center = $bb->center; - $self->object_shift(Slic3r::Pointf3->new(-$center->x, -$center->y, -$bb->z_min)); #,, - $bb->translate(@{ $self->object_shift }); - $self->object_bounding_box($bb); - - # group mesh(es) by material - my @materials = (); - $self->volumes([]); - foreach my $volume (@{$object->volumes}) { - my $mesh = $volume->mesh->clone; - $mesh->translate(@{ $self->object_shift }); - - my $material_id = $volume->material_id // '_'; - my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials; - if (!defined $color_idx) { - push @materials, $material_id; - $color_idx = $#materials; - } - push @{$self->volumes}, my $v = { - color => COLORS->[ $color_idx % scalar(@{&COLORS}) ], - }; - - { - my $vertices = $mesh->vertices; - my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets}; - $v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts); - } - - { - my @norms = map { @$_, @$_, @$_ } @{$mesh->normals}; - $v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms); - } - } + $self->load_object($object); EVT_PAINT($self, sub { my $dc = Wx::PaintDC->new($self); @@ -102,6 +69,45 @@ sub new { return $self; } +sub load_object { + my ($self, $object) = @_; + + my $bb = $object->raw_mesh->bounding_box; + my $center = $bb->center; + $self->object_shift(Slic3r::Pointf3->new(-$center->x, -$center->y, -$bb->z_min)); #,, + $bb->translate(@{ $self->object_shift }); + $self->object_bounding_box($bb); + + # group mesh(es) by material + my @materials = (); + $self->volumes([]); + foreach my $volume (@{$object->volumes}) { + my $mesh = $volume->mesh->clone; + $mesh->translate(@{ $self->object_shift }); + + my $material_id = $volume->material_id // '_'; + my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials; + if (!defined $color_idx) { + push @materials, $material_id; + $color_idx = $#materials; + } + push @{$self->volumes}, my $v = { + color => COLORS->[ $color_idx % scalar(@{&COLORS}) ], + }; + + { + my $vertices = $mesh->vertices; + my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets}; + $v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts); + } + + { + my @norms = map { @$_, @$_, @$_ } @{$mesh->normals}; + $v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms); + } + } +} + # Given an axis and angle, compute quaternion. sub axis_to_quat { my ($ax, $phi) = @_; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 10184a07..6e86ed00 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -47,20 +47,7 @@ sub add_object { ); foreach my $volume (@{$object->volumes}) { - $new_object->add_volume( - material_id => $volume->material_id, - mesh => $volume->mesh->clone, - modifier => $volume->modifier, - ); - - if (defined $volume->material_id) { - # merge material attributes (should we rename materials in case of duplicates?) - my %attributes = %{ $object->model->materials->{$volume->material_id}->attributes }; - if (exists $self->materials->{$volume->material_id}) { - %attributes = (%attributes, %{ $self->materials->{$volume->material_id}->attributes }); - } - $self->set_material($volume->material_id, {%attributes}); - } + $new_object->add_volume($volume); } $new_object->add_instance( @@ -325,14 +312,43 @@ has '_bounding_box' => (is => 'rw'); sub add_volume { my $self = shift; - my %args = @_; - push @{$self->volumes}, my $volume = Slic3r::Model::Volume->new( - object => $self, - %args, - ); + my $new_volume; + if (@_ == 1) { + # we have a Model::Volume + my ($volume) = @_; + + $new_volume = Slic3r::Model::Volume->new( + object => $self, + material_id => $volume->material_id, + mesh => $volume->mesh->clone, + modifier => $volume->modifier, + ); + + if (defined $volume->material_id) { + # merge material attributes (should we rename materials in case of duplicates?) + if (my $material = $volume->object->model->materials->{$volume->material_id}) { + my %attributes = %{ $material->attributes }; + if (exists $self->model->materials->{$volume->material_id}) { + %attributes = (%attributes, %{ $self->model->materials->{$volume->material_id}->attributes }); + } + $self->model->set_material($volume->material_id, {%attributes}); + } + } + } else { + my %args = @_; + $new_volume = Slic3r::Model::Volume->new( + object => $self, + %args, + ); + } + + push @{$self->volumes}, $new_volume; + + # invalidate cached bounding box $self->_bounding_box(undef); - return $volume; + + return $new_volume; } sub add_instance { @@ -411,18 +427,17 @@ sub center_around_origin { # center this object around the origin my $bb = $self->raw_mesh->bounding_box; - # first align to origin on XYZ + # first align to origin on XY my @shift = ( -$bb->x_min, -$bb->y_min, - -$bb->z_min, + 0, ); # then center it on XY my $size = $bb->size; $shift[X] -= $size->x/2; $shift[Y] -= $size->y/2; #// - $shift[Z] -= $size->z/2; $self->translate(@shift);