From 026e0c06e4c0d1e418416c66aa9236cccaa7d27c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 19:52:32 +0200 Subject: [PATCH] Ability to customize how materials are mapped to extruders. #1020 --- lib/Slic3r/Format/AMF/Parser.pm | 4 +- lib/Slic3r/GUI/Plater.pm | 45 ++++++++----- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 66 +++++++++++++++++++ lib/Slic3r/Model.pm | 23 +++++++ lib/Slic3r/Print.pm | 28 ++++---- lib/Slic3r/Print/Object.pm | 2 +- 6 files changed, 134 insertions(+), 34 deletions(-) diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 22e4204b3..19fc60e65 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -27,14 +27,14 @@ sub start_element { $self->{_coordinate} = $data->{LocalName}; } elsif ($data->{LocalName} eq 'volume') { $self->{_volume} = $self->{_object}->add_volume( - material_id => $self->_get_attribute($data, 'materialid') || undef, + material_id => $self->_get_attribute($data, 'materialid') // undef, ); } elsif ($data->{LocalName} eq 'triangle') { $self->{_triangle} = ["", "", ""]; } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { - my $material_id = $self->_get_attribute($data, 'id') || '_'; + my $material_id = $self->_get_attribute($data, 'id') // '_'; $self->{_material} = $self->{_model}->set_material($material_id); } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { $self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a5124fa8d..950aa9d11 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -389,8 +389,9 @@ sub load_file { name => $basename, input_file => $input_file, input_file_object_id => $i, - model_object => $model->objects->[$i], - mesh_stats => $model->objects->[$i]->mesh_stats, # so that we can free model_object + model => $model, + model_object_idx => $i, + mesh_stats => $model->objects->[$i]->mesh_stats, # so that we can free model instances => [ $model->objects->[$i]->instances ? (map $_->offset, @{$model->objects->[$i]->instances}) @@ -589,7 +590,8 @@ sub split_object { name => basename($current_object->input_file), input_file => $current_object->input_file, input_file_object_id => undef, - model_object => $model_object, + model => $new_model, + model_object_idx => $#{$new_model->objects}, instances => [ map $bb->min_point, 1..$current_copies_num ], ); push @{ $self->{objects} }, $object; @@ -629,6 +631,8 @@ sub export_gcode { $self->statusbar->StartBusy; + $_->free_model_object for @{$self->{objects}}; + # It looks like declaring a local $SIG{__WARN__} prevents the ugly # "Attempt to free unreferenced scalar" warning... local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); @@ -788,6 +792,7 @@ sub make_model { input_file => $plater_object->input_file, config => $plater_object->config, layer_height_ranges => $plater_object->layer_height_ranges, + material_mapping => $plater_object->material_mapping, ); foreach my $volume (@{$model_object->volumes}) { $new_model_object->add_volume( @@ -834,7 +839,6 @@ sub on_thumbnail_made { my $self = shift; my ($obj_idx) = @_; - $self->{objects}[$obj_idx]->free_model_object; $self->recenter; $self->{canvas}->Refresh; } @@ -1221,7 +1225,8 @@ use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad); has 'name' => (is => 'rw', required => 1); has 'input_file' => (is => 'rw', required => 1); has 'input_file_object_id' => (is => 'rw'); # undef means keep model object -has 'model_object' => (is => 'rw', required => 1, trigger => 1); +has 'model' => (is => 'rw', required => 1, trigger => \&_trigger_model_object); +has 'model_object_idx' => (is => 'rw', required => 1, trigger => \&_trigger_model_object); has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling has 'convex_hull' => (is => 'rw'); # 2D convex hull of original object (aligned to origin) with no rotation or scaling has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail); @@ -1232,6 +1237,7 @@ has 'transformed_thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw', trigger => \&_transform_thumbnail); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] +has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => extruder_idx } has 'mesh_stats' => (is => 'rw'); # statistics @@ -1242,16 +1248,17 @@ has 'is_manifold' => (is => 'rw'); sub _trigger_model_object { my $self = shift; - if ($self->model_object) { - $self->model_object->align_to_origin; - $self->bounding_box($self->model_object->bounding_box); + if ($self->model && defined $self->model_object_idx) { + my $model_object = $self->model->objects->[$self->model_object_idx]; + $model_object->align_to_origin; + $self->bounding_box($model_object->bounding_box); - my $mesh = $self->model_object->mesh; + my $mesh = $model_object->mesh; $self->convex_hull(Slic3r::Polygon->new(@{Math::ConvexHull::MonotoneChain::convex_hull($mesh->used_vertices)})); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); - $self->materials($self->model_object->materials_count); + $self->materials($model_object->materials_count); } } @@ -1290,18 +1297,21 @@ sub free_model_object { # only delete mesh from memory if we can retrieve it from the original file return unless $self->input_file && defined $self->input_file_object_id; - $self->model_object(undef); + $self->model(undef); + $self->model_object_idx(undef); } sub get_model_object { my $self = shift; - my $object = $self->model_object; - if (!$object) { - my $model = Slic3r::Model->read_from_file($self->input_file); - $object = $model->objects->[$self->input_file_object_id]; + if ($self->model) { + return $self->model->objects->[$self->model_object_idx]; } - return $object; + + return Slic3r::Model + ->read_from_file($self->input_file) + ->objects + ->[$self->input_file_object_id]; } sub instances_count { @@ -1312,7 +1322,7 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin + my $mesh = $self->get_model_object->mesh; # $self->model_object is already aligned to origin my $thumbnail = Slic3r::ExPolygon::Collection->new; if (@{$mesh->facets} <= 5000) { $thumbnail->append(@{ $mesh->horizontal_projection }); @@ -1331,7 +1341,6 @@ sub make_thumbnail { $thumbnail->scale(&Slic3r::SCALING_FACTOR); $self->thumbnail($thumbnail); # ignored in multi-threaded environments - $self->free_model_object; return $thumbnail; } diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index fb7b68209..32b9c38f2 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -16,6 +16,7 @@ sub new { $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); $self->{tabpanel}->AddPage($self->{settings} = Slic3r::GUI::Plater::ObjectDialog::SettingsTab->new($self->{tabpanel}, object => $self->{object}), "Settings"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers"); + $self->{tabpanel}->AddPage($self->{materials} = Slic3r::GUI::Plater::ObjectDialog::MaterialsTab->new($self->{tabpanel}, object => $self->{object}), "Materials"); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); EVT_BUTTON($self, wxID_OK, sub { @@ -25,6 +26,7 @@ sub new { # notify tabs $self->{layers}->Closing; + $self->{materials}->Closing; $self->EndModal(wxID_OK); }); @@ -254,4 +256,68 @@ sub _get_ranges { return sort { $a->[0] <=> $b->[0] } @ranges; } +package Slic3r::GUI::Plater::ObjectDialog::MaterialsTab; +use Wx qw(:dialog :id :misc :sizer :systemsettings :button :icon); +use Wx::Grid; +use Wx::Event qw(EVT_BUTTON); +use base 'Wx::Panel'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); + $self->{object} = $params{object}; + + $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); + + # descriptive text + { + my $label = Wx::StaticText->new($self, -1, "In this section you can assign object materials to your extruders.", + wxDefaultPosition, [-1, 25]); + $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + $self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + } + + # get unique materials used in this object + $self->{materials} = [ $self->{object}->get_model_object->unique_materials ]; + + # build an OptionsGroup + $self->{mapping} = { + (map { $self->{materials}[$_] => $_+1 } 0..$#{ $self->{materials} }), # defaults + %{$self->{object}->material_mapping}, + }; + my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Extruders', + label_width => 300, + options => [ + map { + my $i = $_; + my $material_id = $self->{materials}[$i]; + { + opt_key => "material_extruder_$_", + type => 'i', + label => $self->{object}->get_model_object->model->get_material_name($material_id), + min => 1, + default => $self->{mapping}{$material_id}, + on_change => sub { $self->{mapping}{$material_id} = $_[0] }, + } + } 0..$#{ $self->{materials} } + ], + ); + $self->{sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); + + $self->SetSizer($self->{sizer}); + $self->{sizer}->SetSizeHints($self); + + return $self; +} + +sub Closing { + my $self = shift; + + # save mappings into the plater object + $self->{object}->material_mapping($self->{mapping}); +} + 1; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 0c5d03fa2..5418a0997 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -285,6 +285,20 @@ sub print_info { $_->print_info for @{$self->objects}; } +sub get_material_name { + my $self = shift; + my ($material_id) = @_; + + my $name; + if (exists $self->materials->{$material_id}) { + $name //= $self->materials->{$material_id}->attributes->{$_} for qw(Name name); + } elsif ($material_id eq '_') { + $name = 'Default material'; + } + $name //= $material_id; + return $name; +} + package Slic3r::Model::Region; use Moo; @@ -306,6 +320,7 @@ has 'volumes' => (is => 'ro', default => sub { [] }); has 'instances' => (is => 'rw'); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] +has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => extruder_idx } has 'mesh_stats' => (is => 'rw'); has '_bounding_box' => (is => 'rw'); @@ -430,6 +445,14 @@ sub materials_count { return scalar keys %materials; } +sub unique_materials { + my $self = shift; + + my %materials = (); + $materials{ $_->material_id // '_' } = 1 for @{$self->volumes}; + return sort keys %materials; +} + sub facets_count { my $self = shift; return sum(map $_->facets_count, @{$self->volumes}); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7552101a1..390823727 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -91,18 +91,6 @@ sub add_model { my $self = shift; my ($model) = @_; - # append/merge materials and preserve a mapping between the original material ID - # and our numeric material index - my %materials = (); - { - my @material_ids = sort keys %{$model->materials}; - @material_ids = (0) if !@material_ids; - for (my $i = $self->regions_count; $i < @material_ids; $i++) { - push @{$self->regions}, Slic3r::Print::Region->new; - $materials{$material_ids[$i]} = $#{$self->regions}; - } - } - # optimization: if avoid_crossing_perimeters is enabled, split # this mesh into distinct objects so that we reduce the complexity # of the graphs @@ -112,6 +100,7 @@ sub add_model { # -- thing before the split. ###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; + my %unmapped_materials = (); foreach my $object (@{ $model->objects }) { # we align object to origin before applying transformations my @align = $object->align_to_origin; @@ -119,13 +108,26 @@ sub add_model { # extract meshes by material my @meshes = (); # by region_id foreach my $volume (@{$object->volumes}) { - my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0; + my $region_id; + if (defined $volume->material_id) { + if ($object->material_mapping) { + $region_id = $object->material_mapping->{$volume->material_id} - 1 + if defined $object->material_mapping->{$volume->material_id}; + } + $region_id //= $unmapped_materials{$volume->material_id}; + if (!defined $region_id) { + $region_id = $unmapped_materials{$volume->material_id} = scalar(keys %unmapped_materials); + } + } + $region_id //= 0; + my $mesh = $volume->mesh->clone; # should the object contain multiple volumes of the same material, merge them $meshes[$region_id] = $meshes[$region_id] ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh) : $mesh; } + $self->regions->[$_] //= Slic3r::Print::Region->new for 0..$#meshes; foreach my $mesh (grep $_, @meshes) { $mesh->check_manifoldness; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 9b0d48509..3803382d4 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -143,7 +143,7 @@ sub slice { # process facets for my $region_id (0 .. $#{$self->meshes}) { - my $mesh = $self->meshes->[$region_id]; # ignore undef meshes + my $mesh = $self->meshes->[$region_id] // next; # ignore undef meshes my %lines = (); # layer_id => [ lines ] my $apply_lines = sub {