Parse and write multi-material AMF files. Convert multiple STL files into a single multi-material AMF

This commit is contained in:
Alessandro Ranellucci 2012-02-20 16:43:45 +01:00
parent aa98a9deb2
commit b6bffacb9d
5 changed files with 97 additions and 33 deletions

View File

@ -14,53 +14,75 @@ sub read_file {
open my $fh, '<', $file or die "Failed to open $file\n"; open my $fh, '<', $file or die "Failed to open $file\n";
my $vertices = []; my $vertices = [];
my $facets = []; my $materials = {};
my $meshes_by_material = {};
XML::SAX::ExpatXS XML::SAX::ExpatXS
->new(Handler => Slic3r::AMF::Parser->new( ->new(Handler => Slic3r::AMF::Parser->new(
_vertices => $vertices, _vertices => $vertices,
_facets => $facets, _materials => $materials,
_meshes_by_material => $meshes_by_material,
)) ))
->parse_file($fh); ->parse_file($fh);
close $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 { sub write_file {
my $self = shift; my $self = shift;
my ($file, $mesh) = @_; my ($file, $materials, $meshes_by_material) = @_;
my %vertices_offset = ();
open my $fh, '>', $file; open my $fh, '>', $file;
binmode $fh, ':utf8'; binmode $fh, ':utf8';
printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n}; printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
printf $fh qq{<amf unit="millimeter">\n}; printf $fh qq{<amf unit="millimeter">\n};
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION; printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
foreach my $material_id (keys %$materials) {
printf $fh qq{ <material id="%s">\n}, $material_id;
for (keys %{$materials->{$material_id}}) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $materials->{$material_id}{$_};
}
printf $fh qq{ </material>\n};
}
printf $fh qq{ <object id="0">\n}; printf $fh qq{ <object id="0">\n};
printf $fh qq{ <mesh>\n}; printf $fh qq{ <mesh>\n};
printf $fh qq{ <vertices>\n}; printf $fh qq{ <vertices>\n};
foreach my $vertex (@{$mesh->vertices}) { my $vertices_count = 0;
printf $fh qq{ <vertex>\n}; foreach my $mesh (values %$meshes_by_material) {
printf $fh qq{ <coordinates>\n}; $vertices_offset{$mesh} = $vertices_count;
printf $fh qq{ <x>%s</x>\n}, $vertex->[X]; foreach my $vertex (@{$mesh->vertices}, ) {
printf $fh qq{ <y>%s</y>\n}, $vertex->[Y]; printf $fh qq{ <vertex>\n};
printf $fh qq{ <z>%s</z>\n}, $vertex->[Z]; printf $fh qq{ <coordinates>\n};
printf $fh qq{ </coordinates>\n}; printf $fh qq{ <x>%s</x>\n}, $vertex->[X];
printf $fh qq{ </vertex>\n}; printf $fh qq{ <y>%s</y>\n}, $vertex->[Y];
printf $fh qq{ <z>%s</z>\n}, $vertex->[Z];
printf $fh qq{ </coordinates>\n};
printf $fh qq{ </vertex>\n};
$vertices_count++;
}
} }
printf $fh qq{ </vertices>\n}; printf $fh qq{ </vertices>\n};
printf $fh qq{ <volume>\n}; foreach my $material_id (sort keys %$meshes_by_material) {
foreach my $facet (@{$mesh->facets}) { my $mesh = $meshes_by_material->{$material_id};
printf $fh qq{ <triangle>\n}; printf $fh qq{ <volume%s>\n},
printf $fh qq{ <v%d>%d</v%d>\n}, $_, $facet->[$_], $_ for 1..3; ($material_id eq '_') ? '' : " materialid=\"$material_id\"";
printf $fh qq{ </triangle>\n}; foreach my $facet (@{$mesh->facets}) {
printf $fh qq{ <triangle>\n};
printf $fh qq{ <v%d>%d</v%d>\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_
for 1..3;
printf $fh qq{ </triangle>\n};
}
printf $fh qq{ </volume>\n};
} }
printf $fh qq{ </volume>\n};
printf $fh qq{ </mesh>\n}; printf $fh qq{ </mesh>\n};
printf $fh qq{ </object>\n}; printf $fh qq{ </object>\n};
printf $fh qq{</amf>\n}; printf $fh qq{</amf>\n};
close $fh; close $fh;
} }

View File

@ -3,6 +3,8 @@ use strict;
use warnings; use warnings;
use XML::SAX::ExpatXS; use XML::SAX::ExpatXS;
use XXX;
use base 'XML::SAX::Base'; use base 'XML::SAX::Base';
my %xyz_index = (x => 0, y => 1, z => 2); #= my %xyz_index = (x => 0, y => 1, z => 2); #=
@ -21,10 +23,19 @@ sub start_element {
$self->{_vertex} = ["", "", ""]; $self->{_vertex} = ["", "", ""];
} elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') { } elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') {
$self->{_coordinate} = $data->{LocalName}; $self->{_coordinate} = $data->{LocalName};
} elsif ($data->{LocalName} eq 'volume') {
$self->{_volume_materialid} = $self->_get_attribute($data, 'materialid');
$self->{_volume} = [];
} elsif ($data->{LocalName} eq 'triangle') { } elsif ($data->{LocalName} eq 'triangle') {
$self->{_triangle} = [[], "", "", ""]; # empty normal $self->{_triangle} = [[], "", "", ""]; # empty normal
} elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') {
$self->{_vertex_idx} = $1; $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}; push @{$self->{_tree}}, $data->{LocalName};
@ -38,6 +49,8 @@ sub characters {
$self->{_vertex}[ $xyz_index{$self->{_coordinate}} ] .= $data->{Data}; $self->{_vertex}[ $xyz_index{$self->{_coordinate}} ] .= $data->{Data};
} elsif ($self->{_triangle} && defined $self->{_vertex_idx}) { } elsif ($self->{_triangle} && defined $self->{_vertex_idx}) {
$self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; $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; $self->{_vertex} = undef;
} elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) { } elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) {
$self->{_coordinate} = undef; $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') { } elsif ($data->{LocalName} eq 'triangle') {
push @{$self->{_facets}}, $self->{_triangle}; push @{$self->{_volume}}, $self->{_triangle};
$self->{_triangle} = undef; $self->{_triangle} = undef;
} elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) {
$self->{_vertex_idx} = undef; $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; 1;

View File

@ -126,7 +126,7 @@ sub _read_ascii {
my $facet; my $facet;
seek $fh, 0, 0; seek $fh, 0, 0;
while (<$fh>) { while (my $_ = <$fh>) {
s/\R+$//; s/\R+$//;
if (!$facet) { if (!$facet) {
/^\s*facet\s+normal\s+$point_re/ or next; /^\s*facet\s+normal\s+$point_re/ or next;
@ -153,7 +153,7 @@ sub _read_binary {
binmode $fh; binmode $fh;
seek $fh, 80 + 4, 0; 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'; my @v = unpack '(f<3)4';
push @$facets, [ [@v[0..2]], [@v[3..5]], [@v[6..8]], [@v[9..11]] ]; push @$facets, [ [@v[0..2]], [@v[3..5]], [@v[6..8]], [@v[9..11]] ];
} }

View File

@ -25,14 +25,16 @@ sub go {
# each layer has surfaces with holes # each layer has surfaces with holes
$self->status_cb->(10, "Processing triangulated mesh"); $self->status_cb->(10, "Processing triangulated mesh");
my $print; my $print;
{ if ($self->input_file =~ /\.stl$/i) {
my $mesh = $self->input_file =~ /\.stl$/i my $mesh = Slic3r::STL->read_file($self->input_file);
? 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";
$mesh->check_manifoldness; $mesh->check_manifoldness;
$print = Slic3r::Print->new_from_mesh($mesh); $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 # make perimeters

View File

@ -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]; my $output_file = $ARGV[0];
$output_file =~ s/\.stl$/.amf.xml/i; $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); 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) = @_; my ($exit_code) = @_;
print <<"EOF"; 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 --help Output this usage screen and exit