mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Parse and write multi-material AMF files. Convert multiple STL files into a single multi-material AMF
This commit is contained in:
		
							parent
							
								
									aa98a9deb2
								
							
						
					
					
						commit
						b6bffacb9d
					
				
					 5 changed files with 97 additions and 33 deletions
				
			
		|  | @ -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; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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]] ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|      |      | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Alessandro Ranellucci
						Alessandro Ranellucci