mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07: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,33 +14,49 @@ 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;
 | 
				
			||||||
 | 
					    foreach my $mesh (values %$meshes_by_material) {
 | 
				
			||||||
 | 
					        $vertices_offset{$mesh} = $vertices_count;
 | 
				
			||||||
 | 
					        foreach my $vertex (@{$mesh->vertices}, ) {
 | 
				
			||||||
            printf $fh qq{        <vertex>\n};
 | 
					            printf $fh qq{        <vertex>\n};
 | 
				
			||||||
            printf $fh qq{          <coordinates>\n};
 | 
					            printf $fh qq{          <coordinates>\n};
 | 
				
			||||||
            printf $fh qq{            <x>%s</x>\n}, $vertex->[X];
 | 
					            printf $fh qq{            <x>%s</x>\n}, $vertex->[X];
 | 
				
			||||||
| 
						 | 
					@ -48,19 +64,25 @@ sub write_file {
 | 
				
			||||||
            printf $fh qq{            <z>%s</z>\n}, $vertex->[Z];
 | 
					            printf $fh qq{            <z>%s</z>\n}, $vertex->[Z];
 | 
				
			||||||
            printf $fh qq{          </coordinates>\n};
 | 
					            printf $fh qq{          </coordinates>\n};
 | 
				
			||||||
            printf $fh qq{        </vertex>\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) {
 | 
				
			||||||
 | 
					        my $mesh = $meshes_by_material->{$material_id};
 | 
				
			||||||
 | 
					        printf $fh qq{      <volume%s>\n},
 | 
				
			||||||
 | 
					            ($material_id eq '_') ? '' : " materialid=\"$material_id\"";
 | 
				
			||||||
        foreach my $facet (@{$mesh->facets}) {
 | 
					        foreach my $facet (@{$mesh->facets}) {
 | 
				
			||||||
            printf $fh qq{        <triangle>\n};
 | 
					            printf $fh qq{        <triangle>\n};
 | 
				
			||||||
        printf $fh qq{          <v%d>%d</v%d>\n}, $_, $facet->[$_], $_ for 1..3;
 | 
					            printf $fh qq{          <v%d>%d</v%d>\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_
 | 
				
			||||||
 | 
					                for 1..3;
 | 
				
			||||||
            printf $fh qq{        </triangle>\n};
 | 
					            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