From f90520ed0666533842ca2512d8dd52fb70327250 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 29 Aug 2012 16:49:38 +0200 Subject: [PATCH] Refactoring: new Slic3r::Model class to represent files --- MANIFEST | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/Format/AMF.pm | 65 +++++++++--------- lib/Slic3r/Format/AMF/Parser.pm | 2 +- lib/Slic3r/Format/OBJ.pm | 5 +- lib/Slic3r/Format/STL.pm | 13 ++-- lib/Slic3r/GUI/Plater.pm | 35 +++++----- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- lib/Slic3r/Model.pm | 112 ++++++++++++++++++++++++++++++++ lib/Slic3r/Print.pm | 49 +++++++++----- slic3r.pl | 4 +- utils/amf-to-stl.pl | 4 +- utils/split_stl.pl | 11 +++- utils/stl-to-amf.pl | 39 ++++++++--- 14 files changed, 255 insertions(+), 88 deletions(-) create mode 100644 lib/Slic3r/Model.pm diff --git a/MANIFEST b/MANIFEST index 19de724981..5a5a80df14 100644 --- a/MANIFEST +++ b/MANIFEST @@ -15,6 +15,7 @@ lib/Slic3r/Fill/Flowsnake.pm lib/Slic3r/Fill/HilbertCurve.pm lib/Slic3r/Fill/Honeycomb.pm lib/Slic3r/Fill/Line.pm +lib/Slic3r/Fill/Model.pm lib/Slic3r/Fill/OctagramSpiral.pm lib/Slic3r/Fill/PlanePath.pm lib/Slic3r/Fill/Rectilinear.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index dc97761213..19117f4b80 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -41,6 +41,7 @@ use Slic3r::GCode; use Slic3r::Geometry qw(PI); use Slic3r::Layer; use Slic3r::Line; +use Slic3r::Model; use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index ce388ec2ec..369503c78a 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -25,15 +25,21 @@ sub read_file { close $fh; - $_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_) - for values %$meshes_by_material; - - return $materials, $meshes_by_material; + my $model = Slic3r::Model->new; + my $object = $model->add_object(vertices => $vertices); + foreach my $material (keys %$meshes_by_material) { + push @{$model->materials}, $material; # TODO: we should not add duplicate materials + $object->add_volume( + material_id => $#{$model->materials}, + facets => $meshes_by_material->{$material}, + ); + } + return $model; } sub write_file { my $self = shift; - my ($file, $materials, $meshes_by_material) = @_; + my ($file, $model, %params) = @_; my %vertices_offset = (); @@ -42,20 +48,20 @@ sub write_file { printf $fh qq{\n}; printf $fh qq{\n}; printf $fh qq{ Slic3r %s\n}, $Slic3r::VERSION; - foreach my $material_id (keys %$materials) { - printf $fh qq{ \n}, $material_id; - for (keys %{$materials->{$material_id}}) { - printf $fh qq{ %s\n}, $_, $materials->{$material_id}{$_}; + for my $material_id (0 .. $#{ $model->materials }) { + my $material = $model->materials->[$material_id]; + printf $fh qq{ \n}, $material_id; + for (keys %$material) { + printf $fh qq{ %s\n}, $_, $material->{$_}; } printf $fh qq{ \n}; } - printf $fh qq{ \n}; - printf $fh qq{ \n}; - printf $fh qq{ \n}; - my $vertices_count = 0; - foreach my $mesh (values %$meshes_by_material) { - $vertices_offset{$mesh} = $vertices_count; - foreach my $vertex (@{$mesh->vertices}, ) { + for my $object_id (0 .. $#{ $model->objects }) { + my $object = $model->objects->[$object_id]; + printf $fh qq{ \n}, $object_id; + printf $fh qq{ \n}; + printf $fh qq{ \n}; + foreach my $vertex (@{$object->vertices}, ) { printf $fh qq{ \n}; printf $fh qq{ \n}; printf $fh qq{ %s\n}, $vertex->[X]; @@ -63,24 +69,21 @@ sub write_file { printf $fh qq{ %s\n}, $vertex->[Z]; printf $fh qq{ \n}; printf $fh qq{ \n}; - $vertices_count++; } - } - printf $fh qq{ \n}; - foreach my $material_id (sort keys %$meshes_by_material) { - my $mesh = $meshes_by_material->{$material_id}; - printf $fh qq{ \n}, - ($material_id eq '_') ? '' : " materialid=\"$material_id\""; - foreach my $facet (@{$mesh->facets}) { - printf $fh qq{ \n}; - printf $fh qq{ %d\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_ - for -3..-1; - printf $fh qq{ \n}; + printf $fh qq{ \n}; + foreach my $volume (@{ $object->volumes }) { + printf $fh qq{ \n}, + (!defined $volume->material_id) ? '' : (sprintf ' materialid="%s"', $volume->material_id); + foreach my $facet (@{$volume->facets}) { + printf $fh qq{ \n}; + printf $fh qq{ %d\n}, $_, $facet->[$_], $_ for -3..-1; + printf $fh qq{ \n}; + } + printf $fh qq{ \n}; } - printf $fh qq{ \n}; + printf $fh qq{ \n}; + printf $fh qq{ \n}; } - printf $fh qq{ \n}; - printf $fh qq{ \n}; printf $fh qq{\n}; close $fh; } diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 1d8d4dc396..7c100b7772 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -71,7 +71,7 @@ sub end_element { } elsif ($data->{LocalName} eq 'triangle') { push @{$self->{_volume}}, $self->{_triangle}; $self->{_triangle} = undef; - } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { + } elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { $self->{_vertex_idx} = undef; } elsif ($data->{LocalName} eq 'material') { $self->{_materials}{ $self->{_material_id} } = $self->{_material}; diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm index 210853025c..d663cfb5a7 100644 --- a/lib/Slic3r/Format/OBJ.pm +++ b/lib/Slic3r/Format/OBJ.pm @@ -17,7 +17,10 @@ sub read_file { } close $fh; - return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); + my $model = Slic3r::Model->new; + my $object = $model->add_object(vertices => $vertices); + my $volume = $object->add_volume(facets => $facets); + return $model; } 1; diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 9706fd00dc..5325a4ea6f 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -117,7 +117,10 @@ sub read_file { } } - return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); + my $model = Slic3r::Model->new; + my $object = $model->add_object(vertices => $vertices); + my $volume = $object->add_volume(facets => $facets); + return $model; } sub _read_ascii { @@ -161,13 +164,13 @@ sub _read_binary { sub write_file { my $self = shift; - my ($file, $mesh, $binary) = @_; + my ($file, $model, %params) = @_; open my $fh, '>', $file; - $binary - ? _write_binary($fh, $mesh) - : _write_ascii($fh, $mesh); + $params{binary} + ? _write_binary($fh, $model->mesh) + : _write_ascii($fh, $model->mesh); close $fh; } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 9c6a900da3..2efb7e018a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -296,7 +296,7 @@ sub load_file { my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0); $process_dialog->Pulse; local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - $self->{print}->add_object_from_file($input_file); + $self->{print}->add_objects_from_file($input_file); my $obj_idx = $#{$self->{print}->objects}; $process_dialog->Destroy; @@ -630,13 +630,11 @@ sub on_export_failed { sub export_stl { my $self = shift; - - my $print = $self->{print}; # select output file my $output_file = $main::opt{output}; { - $output_file = $print->expanded_output_filepath($output_file); + $output_file = $self->{print}->expanded_output_filepath($output_file); $output_file =~ s/\.gcode$/.stl/i; my $dlg = Wx::FileDialog->new($self, 'Save STL file as:', dirname($output_file), basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -648,21 +646,26 @@ sub export_stl { $dlg->Destroy; } - my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); - for my $obj_idx (0 .. $#{$print->objects}) { - for my $copy (@{$print->copies->[$obj_idx]}) { - my $cloned_mesh = $print->objects->[$obj_idx]->mesh->clone; - $cloned_mesh->move(@$copy); - my $vertices_offset = scalar @{$mesh->vertices}; - push @{$mesh->vertices}, @{$cloned_mesh->vertices}; - push @{$mesh->facets}, map [ $_->[0], map $vertices_offset + $_, @$_[-3..-1] ], @{$cloned_mesh->facets}; + Slic3r::Format::STL->write_file($output_file, $self->make_model, binary => 1); + $self->statusbar->SetStatusText("STL file exported to $output_file"); +} + +sub make_model { + my $self = shift; + + my $model = Slic3r::Model->new; + for my $obj_idx (0 .. $#{$self->{print}->objects}) { + my $mesh = $self->{print}->objects->[$obj_idx]->mesh->clone; + $mesh->scale(&Slic3r::SCALING_FACTOR); + my $object = $model->add_object(vertices => $mesh->vertices); + $object->add_volume(facets => $mesh->facets); + for my $copy (@{$self->{print}->copies->[$obj_idx]}) { + $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]); } } - $mesh->scale(&Slic3r::SCALING_FACTOR); - $mesh->align_to_origin; + # TODO: $model->align_to_origin; - Slic3r::Format::STL->write_file($output_file, $mesh, 1); - $self->statusbar->SetStatusText("STL file exported to $output_file"); + return $model; } sub make_thumbnail { diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 764f60a81f..babd1cfbc6 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -96,7 +96,7 @@ sub do_slice { Slic3r::GUI->save_settings; my $print = Slic3r::Print->new(config => $config); - $print->add_object_from_file($input_file); + $print->add_objects_from_file($input_file); $print->validate; # select output file diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm new file mode 100644 index 0000000000..48979da318 --- /dev/null +++ b/lib/Slic3r/Model.pm @@ -0,0 +1,112 @@ +package Slic3r::Model; +use Moo; + +use Slic3r::Geometry qw(X Y Z); + +has 'materials' => (is => 'ro', default => sub { [] }); +has 'objects' => (is => 'ro', default => sub { [] }); + +sub add_object { + my $self = shift; + + my $object = Slic3r::Model::Object->new(model => $self, @_); + push @{$self->objects}, $object; + return $object; +} + +# flattens everything to a single mesh +sub mesh { + my $self = shift; + + my $vertices = []; + my $facets = []; + foreach my $object (@{$self->objects}) { + my @instances = $object->instances ? @{$object->instances} : (undef); + foreach my $instance (@instances) { + my @vertices = @{$object->vertices}; + if ($instance) { + # save Z coordinates, as rotation and translation discard them + my @z = map $_->[Z], @vertices; + + if ($instance->rotation) { + # transform vertex coordinates + my $rad = Slic3r::Geometry::deg2rad($instance->rotation); + @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices); + } + @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices); + + # reapply Z coordinates + $vertices[$_][Z] = $z[$_] for 0 .. $#z; + } + + my $v_offset = @$vertices; + push @$vertices, @vertices; + foreach my $volume (@{$object->volumes}) { + push @$facets, map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{$volume->facets}; + } + } + } + + return Slic3r::TriangleMesh->new( + vertices => $vertices, + facets => $facets, + ); +} + +package Slic3r::Model::Material; +use Moo; + +has 'model' => (is => 'ro', weak_ref => 1, required => 1); +has 'attributes' => (is => 'rw', default => sub { {} }); + +package Slic3r::Model::Object; +use Moo; + +has 'model' => (is => 'ro', weak_ref => 1, required => 1); +has 'vertices' => (is => 'ro', default => sub { [] }); +has 'volumes' => (is => 'ro', default => sub { [] }); +has 'instances' => (is => 'rw'); + +sub add_volume { + my $self = shift; + + my $volume = Slic3r::Model::Volume->new(object => $self, @_); + push @{$self->volumes}, $volume; + return $volume; +} + +sub add_instance { + my $self = shift; + + $self->instances([]) if !defined $self->instances; + push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_); + return $self->instances->[-1]; +} + +package Slic3r::Model::Volume; +use Moo; + +has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'material_id' => (is => 'rw'); +has 'facets' => (is => 'rw', default => sub { [] }); + +sub mesh { + my $self = shift; + return Slic3r::TriangleMesh->new( + vertices => $self->object->vertices, + facets => $self->facets, + ); +} + +package Slic3r::Model::Instance; +use Moo; + +has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'rotation' => (is => 'rw', default => sub { 0 }); +has 'offset' => (is => 'rw'); + +1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index f015416de8..bfa7c2c36e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -79,28 +79,43 @@ sub _trigger_config { $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); } -sub add_object_from_file { +sub add_objects_from_file { my $self = shift; my ($input_file) = @_; - my $object; - if ($input_file =~ /\.stl$/i) { - my $mesh = Slic3r::Format::STL->read_file($input_file); - $mesh->check_manifoldness; - $object = $self->add_object_from_mesh($mesh); - } elsif ($input_file =~ /\.obj$/i) { - my $mesh = Slic3r::Format::OBJ->read_file($input_file); + my $model = $input_file =~ /\.stl$/i ? Slic3r::Format::STL->read_file($input_file) + : $input_file =~ /\.obj$/i ? Slic3r::Format::OBJ->read_file($input_file) + : $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->read_file($input_file) + : die "Input file must have .stl, .obj or .amf(.xml) extension\n"; + + my @print_objects = $self->add_model($model); + $_->input_file($input_file) for @print_objects; +} + +sub add_model { + my $self = shift; + my ($model) = @_; + + my @print_objects = (); + foreach my $object (@{ $model->objects }) { + my $mesh = $object->volumes->[0]->mesh; $mesh->check_manifoldness; - $object = $self->add_object_from_mesh($mesh); - } elsif ( $input_file =~ /\.amf(\.xml)?$/i) { - my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file); - $_->check_manifoldness for values %$meshes_by_material; - $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]); - } else { - die "Input file must have .stl, .obj or .amf(.xml) extension\n"; + + if ($object->instances) { + # we ignore the per-instance rotation currently and only + # consider the first one + $mesh->rotate($object->instances->[0]->rotation); + } + + push @print_objects, $self->add_object_from_mesh($mesh); + + if ($object->instances) { + # replace the default [0,0] instance with the custom ones + @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[X] ], @{$object->instances}; + } } - $object->input_file($input_file); - return $object; + + return @print_objects; } sub add_object_from_mesh { diff --git a/slic3r.pl b/slic3r.pl index cc0af0a602..234ce5c246 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -88,9 +88,9 @@ BEGIN while (my $input_file = shift @ARGV) { my $print = Slic3r::Print->new(config => $config); - $print->add_object_from_file($input_file); + $print->add_objects_from_file($input_file); if ($opt{merge}) { - $print->add_object_from_file($_) for splice @ARGV, 0; + $print->add_objects_from_file($_) for splice @ARGV, 0; } $print->duplicate; $print->arrange_objects if @{$print->objects} > 1; diff --git a/utils/amf-to-stl.pl b/utils/amf-to-stl.pl index 847e5e7498..b421dd33a0 100755 --- a/utils/amf-to-stl.pl +++ b/utils/amf-to-stl.pl @@ -25,12 +25,12 @@ BEGIN } { - my $mesh = Slic3r::Format::AMF->read_file($ARGV[0]); + my $model = Slic3r::Format::AMF->read_file($ARGV[0]); my $output_file = $ARGV[0]; $output_file =~ s/\.amf(?:\.xml)?$/\.stl/i; printf "Writing to %s\n", basename($output_file); - Slic3r::Format::STL->write_file($output_file, $mesh, !$opt{ascii}); + Slic3r::Format::STL->write_file($output_file, $model, binary => !$opt{ascii}); } diff --git a/utils/split_stl.pl b/utils/split_stl.pl index af98901163..42d2926bd0 100755 --- a/utils/split_stl.pl +++ b/utils/split_stl.pl @@ -25,15 +25,20 @@ BEGIN } { - my $mesh = Slic3r::Format::STL->read_file($ARGV[0]); + my $model = Slic3r::Format::STL->read_file($ARGV[0]); my $basename = $ARGV[0]; $basename =~ s/\.stl$//i; my $part_count = 0; - foreach my $new_mesh ($mesh->split_mesh) { + foreach my $new_mesh ($model->mesh->split_mesh) { + my $new_model = Slic3r::Model->new; + $new_model + ->add_object(vertices => $new_mesh->vertices) + ->add_volume(facets => $new_mesh->facets); + my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count; printf "Writing to %s\n", basename($output_file); - Slic3r::Format::STL->write_file($output_file, $new_mesh, !$opt{ascii}); + Slic3r::Format::STL->write_file($output_file, $new_model, binary => !$opt{ascii}); } } diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index f4805369b8..90aacc5352 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -18,29 +18,49 @@ BEGIN { my %options = ( 'help' => sub { usage() }, + 'distinct-materials' => \$opt{distinct_materials}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { - my @meshes = map Slic3r::Format::STL->read_file($_), @ARGV; + my @models = map Slic3r::Format::STL->read_file($_), @ARGV; my $output_file = $ARGV[0]; $output_file =~ s/\.stl$/.amf.xml/i; - my $materials = {}; - my $meshes_by_material = {}; - if (@meshes == 1) { - $meshes_by_material->{_} = $meshes[0]; + my $new_model = Slic3r::Model->new; + + if ($opt{distinct_materials} && @models > 1) { + my $new_object = $new_model->add_object; + for my $m (0 .. $#models) { + my $model = $models[$m]; + my $v_offset = @{$new_object->vertices}; + push @{$new_object->vertices}, @{$model->objects->[0]->vertices}; + my @new_facets = map { + my $f = [@$_]; + $f->[$_] += $v_offset for -3..-1; + $f; + } @{ $model->objects->[0]->volumes->[0]->facets }; + + push @{$new_model->materials}, { Name => basename($ARGV[$m]) }; + $new_object->add_volume( + material_id => $#{$new_model->materials}, + facets => [@new_facets], + ); + } } else { - for (0..$#meshes) { - $materials->{$_+1} = { Name => basename($ARGV[$_]) }; - $meshes_by_material->{$_+1} = $meshes[$_]; + foreach my $model (@models) { + $new_model->add_object( + vertices => $model->objects->[0]->vertices, + )->add_volume( + facets => $model->objects->[0]->volumes->[0]->facets, + ); } } printf "Writing to %s\n", basename($output_file); - Slic3r::Format::AMF->write_file($output_file, $materials, $meshes_by_material); + Slic3r::Format::AMF->write_file($output_file, $new_model); } @@ -51,6 +71,7 @@ sub usage { Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] --help Output this usage screen and exit + --distinct-materials Assign each STL file to a different material EOF exit ($exit_code || 0);