Refactoring: moved Slic3r::STL and Slic3r::AMF to Slic3r::Format::*

This commit is contained in:
Alessandro Ranellucci 2012-02-25 17:35:25 +01:00
parent 2a86932cfe
commit 959230b099
9 changed files with 18 additions and 18 deletions

89
lib/Slic3r/Format/AMF.pm Normal file
View file

@ -0,0 +1,89 @@
package Slic3r::Format::AMF;
use Moo;
use Slic3r::Geometry qw(X Y Z);
use XXX;
sub read_file {
my $self = shift;
my ($file) = @_;
eval "require Slic3r::Format::AMF::Parser; 1"
or die "AMF parsing requires XML::SAX::ExpatXS\n";
open my $fh, '<', $file or die "Failed to open $file\n";
my $vertices = [];
my $materials = {};
my $meshes_by_material = {};
XML::SAX::ExpatXS
->new(Handler => Slic3r::Format::AMF::Parser->new(
_vertices => $vertices,
_materials => $materials,
_meshes_by_material => $meshes_by_material,
))
->parse_file($fh);
close $fh;
$_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_)
for values %$meshes_by_material;
return $materials, $meshes_by_material;
}
sub write_file {
my $self = shift;
my ($file, $materials, $meshes_by_material) = @_;
my %vertices_offset = ();
open my $fh, '>', $file;
binmode $fh, ':utf8';
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;
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{ <mesh>\n};
printf $fh qq{ <vertices>\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{ <vertex>\n};
printf $fh qq{ <coordinates>\n};
printf $fh qq{ <x>%s</x>\n}, $vertex->[X];
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};
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}) {
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{ </mesh>\n};
printf $fh qq{ </object>\n};
printf $fh qq{</amf>\n};
close $fh;
}
1;

View file

@ -0,0 +1,93 @@
package Slic3r::Format::AMF::Parser;
use strict;
use warnings;
use XML::SAX::ExpatXS;
use XXX;
use base 'XML::SAX::Base';
my %xyz_index = (x => 0, y => 1, z => 2); #=
sub new {
my $self = shift->SUPER::new(@_);
$self->{_tree} = [];
$self;
}
sub start_element {
my $self = shift;
my $data = shift;
if ($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} = [];
} 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};
}
sub characters {
my $self = shift;
my $data = shift;
if ($self->{_vertex} && $self->{_coordinate}) {
$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};
}
}
sub end_element {
my $self = shift;
my $data = shift;
pop @{$self->{_tree}};
if ($data->{LocalName} eq 'vertex') {
push @{$self->{_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};
$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;

210
lib/Slic3r/Format/STL.pm Normal file
View file

@ -0,0 +1,210 @@
package Slic3r::Format::STL;
use Moo;
use Slic3r::Geometry qw(X Y Z triangle_normal);
use XXX;
sub read_file {
my $self = shift;
my ($file) = @_;
open my $fh, '<', $file or die "Failed to open $file\n";
# let's detect whether file is ASCII or binary
my $mode;
{
my $size = +(stat $fh)[7];
$mode = 'ascii' if $size < 80 + 4;
# skip binary header
seek $fh, 80, 0;
read $fh, my $buf, 4;
my $triangle_count = unpack 'L', $buf;
die "STL file seems invalid, could not read facet count\n" if !defined $triangle_count;
my $expected_size =
+ 80 # header
+ 4 # count
+ $triangle_count * (
+ 4 # normal, pt,pt,pt (vectors)
* 4 # bytes per value
* 3 # values per vector
+ 2 # the trailing 'short'
);
$mode = ($size == $expected_size) ? 'binary' : 'ascii';
}
my $facets = [];
$mode eq 'ascii'
? _read_ascii($fh, $facets)
: _read_binary($fh, $facets);
close $fh;
my $vertices = [];
{
my %vertices_map = (); # given a vertex's coordinates, what's its index?
my @vertices_facets = (); # given a vertex index, what are the indexes of its tangent facets?
for (my $f = 0; $f <= $#$facets; $f++) {
for (1..3) {
my $point_id = join ',', @{$facets->[$f][$_]};
if (exists $vertices_map{$point_id}) {
$facets->[$f][$_] = $vertices_map{$point_id};
push @{$vertices_facets[$facets->[$f][$_]]}, $f;
} else {
push @$vertices, $facets->[$f][$_];
$facets->[$f][$_] = $vertices_map{$point_id} = $#$vertices;
$vertices_facets[$#$vertices] = [$f];
}
}
}
# The following loop checks that @vertices_facets only groups facets that
# are really connected together (i.e. neighbors or sharing neighbors);
# in other words it takes care of multiple vertices occupying the same
# point in space. It enforces topological correctness which is needed by
# the slicing algorithm.
# I'm keeping it disabled until I find a good test case.
if (0) {
my $vertices_count = $#$vertices; # store it to avoid processing newly created vertices
for (my $v = 0; $v <= $vertices_count; $v++) {
my $more_than_one_vertex_in_this_point = 0;
while (@{$vertices_facets[$v]}) {
my @facets_indexes = @{$vertices_facets[$v]};
@{$vertices_facets[$v]} = ();
my @this_f = shift @facets_indexes;
CYCLE: while (@facets_indexes && @this_f) {
# look for a facet that is connected to $this_f[-1] and whose common line contains $v
my @other_vertices_indexes = grep $_ != $v, @{$facets->[$this_f[-1]]}[1..3];
OTHER: for my $other_f (@facets_indexes) {
# facet is connected if it shares one more point
for (grep $_ != $v, @{$facets->[$other_f]}[1..3]) {
if ($_ ~~ @other_vertices_indexes) {
#printf "facet %d is connected to $other_f (sharing vertices $v and $_)\n", $this_f[-1];
# TODO: we should ensure that the common edge has a different orientation
# for each of the two adjacent facets
push @this_f, $other_f;
@facets_indexes = grep $_ != $other_f, @facets_indexes;
next CYCLE;
}
}
}
# if we're here, then we couldn't find any facet connected to $this_f[-1]
# so we should move this one to a different cluster (that is, a new vertex)
# (or ignore it if it turns to be a non-manifold facet)
if (@this_f > 1) {
push @{$vertices_facets[$v]}, $this_f[-1];
pop @this_f;
$more_than_one_vertex_in_this_point++;
} else {
last CYCLE;
}
}
if ($more_than_one_vertex_in_this_point) {
Slic3r::debugf " more than one vertex in the same point\n";
push @$vertices, $vertices->[$v];
for my $f (@this_f) {
$facets->[$f][$_] = $#$vertices for grep $facets->[$f][$_] == $v, 1..3;
}
}
}
}
}
}
return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets);
}
sub _read_ascii {
my ($fh, $facets) = @_;
my $point_re = qr/([^ ]+)\s+([^ ]+)\s+([^ ]+)\s*$/;
my $facet;
seek $fh, 0, 0;
while (my $_ = <$fh>) {
s/\R+$//;
if (!$facet) {
/^\s*facet\s+normal\s+$point_re/ or next;
$facet = [ [$1, $2, $3] ];
} else {
if (/^\s*endfacet/) {
push @$facets, $facet;
undef $facet;
} else {
/^\s*vertex\s+$point_re/ or next;
push @$facet, [map $_ * 1, $1, $2, $3];
}
}
}
if ($facet) {
die "STL file seems invalid\n";
}
}
sub _read_binary {
my ($fh, $facets) = @_;
die "bigfloat" unless length(pack "f", 1) == 4;
binmode $fh;
seek $fh, 80 + 4, 0;
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]] ];
}
}
sub write_file {
my $self = shift;
my ($file, $mesh, $binary) = @_;
open my $fh, '>', $file;
$binary
? _write_binary($fh, $mesh)
: _write_ascii($fh, $mesh);
close $fh;
}
sub _write_binary {
my ($fh, $mesh) = @_;
die "bigfloat" unless length(pack "f", 1) == 4;
binmode $fh;
print $fh pack 'x80';
print $fh pack 'L', scalar(@{$mesh->facets});
foreach my $facet (@{$mesh->facets}) {
print $fh pack '(f<3)4S',
@{_facet_normal($mesh, $facet)},
(map @{$mesh->vertices->[$_]}, @$facet[1,2,3]),
0;
}
}
sub _write_ascii {
my ($fh, $mesh) = @_;
printf $fh "solid\n";
foreach my $facet (@{$mesh->facets}) {
printf $fh " facet normal %f %f %f\n", @{_facet_normal($mesh, $facet)};
printf $fh " outer loop\n";
printf $fh " vertex %f %f %f\n", @{$mesh->vertices->[$_]} for @$facet[1,2,3];
printf $fh " endloop\n";
printf $fh " endfacet\n";
}
printf $fh "endsolid\n";
}
sub _facet_normal {
my ($mesh, $facet) = @_;
return triangle_normal(map $mesh->vertices->[$_], @$facet[1,2,3]);
}
1;