From f29d4553191d96ea10ec39cefbb36190e7e0b4ce Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Wed, 29 Aug 2012 18:23:34 +0200
Subject: [PATCH] Refactor the AMF parser so that it popolates a Model object
 directly. Also read constellations

---
 lib/Slic3r/Format/AMF.pm        | 24 +++----------
 lib/Slic3r/Format/AMF/Parser.pm | 63 ++++++++++++++++++++++++++-------
 lib/Slic3r/Model.pm             |  2 +-
 utils/stl-to-amf.pl             |  5 +--
 4 files changed, 59 insertions(+), 35 deletions(-)

diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm
index ce3fbb474..552f1fdbd 100644
--- a/lib/Slic3r/Format/AMF.pm
+++ b/lib/Slic3r/Format/AMF.pm
@@ -12,28 +12,12 @@ sub read_file {
     
     open my $fh, '<', $file or die "Failed to open $file\n";
     
-    my $vertices = [];
-    my $materials = {};
-    my $meshes_by_material = {};
+    my $model = Slic3r::Model->new;
     XML::SAX::PurePerl
-        ->new(Handler => Slic3r::Format::AMF::Parser->new(
-            _vertices           => $vertices,
-            _materials          => $materials,
-            _meshes_by_material => $meshes_by_material,
-         ))
+        ->new(Handler => Slic3r::Format::AMF::Parser->new(_model => $model))
         ->parse_file($fh);
-    
     close $fh;
     
-    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;
 }
 
@@ -48,8 +32,8 @@ sub write_file {
     printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
     printf $fh qq{<amf unit="millimeter">\n};
     printf $fh qq{  <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
-    for my $material_id (0 .. $#{ $model->materials }) {
-        my $material = $model->materials->[$material_id];
+    for my $material_id (sort keys %{ $model->materials }) {
+        my $material = $model->materials->{$material_id};
         printf $fh qq{  <material id="%d">\n}, $material_id;
         for (keys %$material) {
              printf $fh qq{    <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_};
diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm
index 7c100b777..93b35765e 100644
--- a/lib/Slic3r/Format/AMF/Parser.pm
+++ b/lib/Slic3r/Format/AMF/Parser.pm
@@ -11,6 +11,8 @@ my %xyz_index = (x => 0, y => 1, z => 2); #=
 sub new {
     my $self = shift->SUPER::new(@_);
     $self->{_tree} = [];
+    $self->{_objects_map} = {};  # this hash maps AMF object IDs to object indexes in $model->objects
+    $self->{_instances} = {};   # apply these lazily to make sure all objects have been parsed
     $self;
 }
 
@@ -18,23 +20,35 @@ sub start_element {
     my $self = shift;
     my $data = shift;
     
-    if ($data->{LocalName} eq 'vertex') {
+    if ($data->{LocalName} eq 'object') {
+        $self->{_object} = $self->{_model}->add_object;
+        $self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects };
+    } elsif ($data->{LocalName} eq 'vertex') {
         $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} = [];
+        $self->{_volume} = $self->{_object}->add_volume(
+            material_id => $self->_get_attribute($data, 'materialid') || undef,
+        );
     } elsif ($data->{LocalName} eq 'triangle') {
         $self->{_triangle} = ["", "", ""];
     } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') {
         $self->{_vertex_idx} = $1-1;
     } elsif ($data->{LocalName} eq 'material') {
-        $self->{_material_id} = $self->_get_attribute($data, 'id') || '_';
-        $self->{_material} = {};
+        my $material_id = $self->_get_attribute($data, 'id') || '_';
+        $self->{_material} = $self->{_model}->materials->{ $material_id } = {};
     } 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} } = "";
+    } elsif ($data->{LocalName} eq 'constellation') {
+        $self->{_constellation} = 1; # we merge all constellations as we don't support more than one
+    } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) {
+        my $object_id = $self->_get_attribute($data, 'objectid');
+        $self->{_instances}{$object_id} ||= [];
+        push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {};
+    } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
+        $self->{_instance_property} = $data->{LocalName};
     }
     
     push @{$self->{_tree}}, $data->{LocalName};
@@ -50,6 +64,8 @@ sub characters {
         $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data};
     } elsif ($self->{_material_metadata_type}) {
         $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data};
+    } elsif ($self->{_instance_property}) {
+        $self->{_instance}{ $self->{_instance_property} } .= $data->{Data};
     }
 }
 
@@ -59,26 +75,49 @@ sub end_element {
     
     pop @{$self->{_tree}};
     
-    if ($data->{LocalName} eq 'vertex') {
-        push @{$self->{_vertices}}, $self->{_vertex};
+    if ($data->{LocalName} eq 'object') {
+        $self->{_object} = undef;
+    } elsif ($data->{LocalName} eq 'vertex') {
+        push @{$self->{_object}->vertices}, $self->{_vertex};
         $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->{_volume}}, $self->{_triangle};
+        push @{$self->{_volume}->facets}, $self->{_triangle};
         $self->{_triangle} = undef;
     } 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};
-        $self->{_material_id} = undef;
         $self->{_material} = undef;
     } elsif ($data->{LocalName} eq 'metadata' && $self->{_material}) {
         $self->{_material_metadata_type} = undef;
+    } elsif ($data->{LocalName} eq 'constellation') {
+        $self->{_constellation} = undef;
+    } elsif ($data->{LocalName} eq 'instance') {
+        $self->{_instance} = undef;
+    } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
+        $self->{_instance_property} = undef;
+    }
+}
+
+sub end_document {
+    my $self = shift;
+    
+    foreach my $object_id (keys %{ $self->{_instances} }) {
+        my $new_object_id = $self->{_objects_map}{$object_id};
+        if (!$new_object_id) {
+            warn "Undefined object $object_id referenced in constellation\n";
+            next;
+        }
+        
+        foreach my $instance (@{ $self->{_instances}{$object_id} }) {
+            $self->{_model}->objects->[$new_object_id]->add_instance(
+                rotation => $instance->{rz} || 0,
+                offset   => [ $instance->{deltax} || 0, $instance->{deltay} ],
+            );
+        }
     }
 }
 
diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm
index 48979da31..b648d4b34 100644
--- a/lib/Slic3r/Model.pm
+++ b/lib/Slic3r/Model.pm
@@ -3,7 +3,7 @@ use Moo;
 
 use Slic3r::Geometry qw(X Y Z);
 
-has 'materials' => (is => 'ro', default => sub { [] });
+has 'materials' => (is => 'ro', default => sub { {} });
 has 'objects'   => (is => 'ro', default => sub { [] });
 
 sub add_object {
diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl
index 90aacc535..78e5989b4 100755
--- a/utils/stl-to-amf.pl
+++ b/utils/stl-to-amf.pl
@@ -43,9 +43,10 @@ my %opt = ();
                 $f;
             } @{ $model->objects->[0]->volumes->[0]->facets };
             
-            push @{$new_model->materials}, { Name => basename($ARGV[$m]) };
+            my $material_id = scalar keys %{$new_model->materials};
+            $new_model->materials->{$material_id} = { Name => basename($ARGV[$m]) };
             $new_object->add_volume(
-                material_id => $#{$new_model->materials},
+                material_id => $material_id,
                 facets      => [@new_facets],
             );
         }