From b6bffacb9d117a67a7d8172bee104021c1062f5e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 Feb 2012 16:43:45 +0100 Subject: [PATCH] Parse and write multi-material AMF files. Convert multiple STL files into a single multi-material AMF --- lib/Slic3r/AMF.pm | 64 +++++++++++++++++++++++++++------------- lib/Slic3r/AMF/Parser.pm | 31 ++++++++++++++++++- lib/Slic3r/STL.pm | 4 +-- lib/Slic3r/Skein.pm | 14 +++++---- utils/stl-to-amf.pl | 17 +++++++++-- 5 files changed, 97 insertions(+), 33 deletions(-) diff --git a/lib/Slic3r/AMF.pm b/lib/Slic3r/AMF.pm index 853b2813..c31aa605 100644 --- a/lib/Slic3r/AMF.pm +++ b/lib/Slic3r/AMF.pm @@ -14,53 +14,75 @@ sub read_file { open my $fh, '<', $file or die "Failed to open $file\n"; my $vertices = []; - my $facets = []; + my $materials = {}; + my $meshes_by_material = {}; XML::SAX::ExpatXS ->new(Handler => Slic3r::AMF::Parser->new( - _vertices => $vertices, - _facets => $facets, + _vertices => $vertices, + _materials => $materials, + _meshes_by_material => $meshes_by_material, )) ->parse_file($fh); close $fh; - return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); + $_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_) + for values %$meshes_by_material; + + return $materials, $meshes_by_material; } sub write_file { my $self = shift; - my ($file, $mesh) = @_; + my ($file, $materials, $meshes_by_material) = @_; + + my %vertices_offset = (); open my $fh, '>', $file; binmode $fh, ':utf8'; - 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}{$_}; + } + printf $fh qq{ \n}; + } printf $fh qq{ \n}; printf $fh qq{ \n}; printf $fh qq{ \n}; - foreach my $vertex (@{$mesh->vertices}) { - printf $fh qq{ \n}; - printf $fh qq{ \n}; - printf $fh qq{ %s\n}, $vertex->[X]; - printf $fh qq{ %s\n}, $vertex->[Y]; - printf $fh qq{ %s\n}, $vertex->[Z]; - 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}, ) { + printf $fh qq{ \n}; + printf $fh qq{ \n}; + printf $fh qq{ %s\n}, $vertex->[X]; + printf $fh qq{ %s\n}, $vertex->[Y]; + printf $fh qq{ %s\n}, $vertex->[Z]; + printf $fh qq{ \n}; + printf $fh qq{ \n}; + $vertices_count++; + } } printf $fh qq{ \n}; - printf $fh qq{ \n}; - foreach my $facet (@{$mesh->facets}) { - printf $fh qq{ \n}; - printf $fh qq{ %d\n}, $_, $facet->[$_], $_ for 1..3; - 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 1..3; + 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/AMF/Parser.pm b/lib/Slic3r/AMF/Parser.pm index 1ad43761..2440acba 100644 --- a/lib/Slic3r/AMF/Parser.pm +++ b/lib/Slic3r/AMF/Parser.pm @@ -3,6 +3,8 @@ use strict; use warnings; use XML::SAX::ExpatXS; +use XXX; + use base 'XML::SAX::Base'; my %xyz_index = (x => 0, y => 1, z => 2); #= @@ -21,10 +23,19 @@ sub start_element { $self->{_vertex} = ["", "", ""]; } elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') { $self->{_coordinate} = $data->{LocalName}; + } elsif ($data->{LocalName} eq 'volume') { + $self->{_volume_materialid} = $self->_get_attribute($data, 'materialid'); + $self->{_volume} = []; } elsif ($data->{LocalName} eq 'triangle') { $self->{_triangle} = [[], "", "", ""]; # empty normal } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { $self->{_vertex_idx} = $1; + } elsif ($data->{LocalName} eq 'material') { + $self->{_material_id} = $self->_get_attribute($data, 'id') || '_'; + $self->{_material} = {}; + } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { + $self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); + $self->{_material}{ $self->{_material_metadata_type} } = ""; } push @{$self->{_tree}}, $data->{LocalName}; @@ -38,6 +49,8 @@ sub characters { $self->{_vertex}[ $xyz_index{$self->{_coordinate}} ] .= $data->{Data}; } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; + } elsif ($self->{_material_metadata_type}) { + $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data}; } } @@ -52,13 +65,29 @@ sub end_element { $self->{_vertex} = undef; } elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) { $self->{_coordinate} = undef; + } elsif ($data->{LocalName} eq 'volume') { + $self->{_meshes_by_material}{ $self->{_volume_materialid} } ||= []; + push @{ $self->{_meshes_by_material}{ $self->{_volume_materialid} } }, @{$self->{_volume}}; + $self->{_volume} = undef; } elsif ($data->{LocalName} eq 'triangle') { - push @{$self->{_facets}}, $self->{_triangle}; + push @{$self->{_volume}}, $self->{_triangle}; $self->{_triangle} = undef; } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { $self->{_vertex_idx} = undef; + } elsif ($data->{LocalName} eq 'material') { + $self->{_materials}{ $self->{_material_id} } = $self->{_material}; + $self->{_material_id} = undef; + $self->{_material} = undef; + } elsif ($data->{LocalName} eq 'metadata' && $self->{_material}) { + $self->{_material_metadata_type} = undef; } +} + +sub _get_attribute { + my $self = shift; + my ($data, $name) = @_; + return +(map $_->{Value}, grep $_->{Name} eq $name, values %{$data->{Attributes}})[0]; } 1; diff --git a/lib/Slic3r/STL.pm b/lib/Slic3r/STL.pm index 0c7830c3..cbaa22b4 100644 --- a/lib/Slic3r/STL.pm +++ b/lib/Slic3r/STL.pm @@ -126,7 +126,7 @@ sub _read_ascii { my $facet; seek $fh, 0, 0; - while (<$fh>) { + while (my $_ = <$fh>) { s/\R+$//; if (!$facet) { /^\s*facet\s+normal\s+$point_re/ or next; @@ -153,7 +153,7 @@ sub _read_binary { binmode $fh; seek $fh, 80 + 4, 0; - while (read $fh, $_, 4*4*3+2) { + while (read $fh, my $_, 4*4*3+2) { my @v = unpack '(f<3)4'; push @$facets, [ [@v[0..2]], [@v[3..5]], [@v[6..8]], [@v[9..11]] ]; } diff --git a/lib/Slic3r/Skein.pm b/lib/Slic3r/Skein.pm index 331190c8..1c19ad4a 100644 --- a/lib/Slic3r/Skein.pm +++ b/lib/Slic3r/Skein.pm @@ -25,14 +25,16 @@ sub go { # each layer has surfaces with holes $self->status_cb->(10, "Processing triangulated mesh"); my $print; - { - my $mesh = $self->input_file =~ /\.stl$/i - ? Slic3r::STL->read_file($self->input_file) - : $self->input_file =~ /\.amf(\.xml)?$/i - ? Slic3r::AMF->read_file($self->input_file) - : die "Input file must have .stl or .amf(.xml) extension\n"; + if ($self->input_file =~ /\.stl$/i) { + my $mesh = Slic3r::STL->read_file($self->input_file); $mesh->check_manifoldness; $print = Slic3r::Print->new_from_mesh($mesh); + } elsif ( $self->input_file =~ /\.amf(\.xml)?$/i) { + my ($materials, $meshes_by_material) = Slic3r::AMF->read_file($self->input_file); + $_->check_manifoldness for values %$meshes_by_material; + $print = Slic3r::Print->new_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]); + } else { + die "Input file must have .stl or .amf(.xml) extension\n"; } # make perimeters diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index 28941723..32aed21b 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -24,12 +24,23 @@ my %opt = (); } { - my $mesh = Slic3r::STL->read_file($ARGV[0]); + my @meshes = map Slic3r::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]; + } else { + for (0..$#meshes) { + $materials->{$_+1} = { Name => basename($ARGV[$_]) }; + $meshes_by_material->{$_+1} = $meshes[$_]; + } + } + printf "Writing to %s\n", basename($output_file); - Slic3r::AMF->write_file($output_file, $mesh); + Slic3r::AMF->write_file($output_file, $materials, $meshes_by_material); } @@ -37,7 +48,7 @@ sub usage { my ($exit_code) = @_; print <<"EOF"; -Usage: amf-to-stl.pl [ OPTIONS ] file.stl +Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] --help Output this usage screen and exit