package Slic3r::Format::STL; use Moo; use Slic3r::Geometry qw(X Y Z triangle_normal); sub read_file { my $self = shift; my ($file) = @_; Slic3r::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 = []; my $vertices = []; $mode eq 'ascii' ? _read_ascii($fh, $facets, $vertices) : _read_binary($fh, $facets, $vertices); close $fh; my $model = Slic3r::Model->new; my $object = $model->add_object(vertices => $vertices); my $volume = $object->add_volume(facets => $facets); return $model; } sub _read_ascii { my ($fh, $facets, $vertices) = @_; my $point_re = qr/(([^ ]+)\s+([^ ]+)\s+([^ ]+))/; my $facet; my %vertices_map = (); seek $fh, 0, 0; while (<$fh>) { if (!$facet) { /^\s*facet/ or next; # be tolerant with malformed STL files not having the "normal" word $facet = []; # ignore normal } else { if (/^\s*endfacet/) { push @$facets, $facet; undef $facet; } else { /^\s*vertex\s+$point_re/o or next; my $vertex_id = $1; my $vertex_idx; if (exists $vertices_map{$vertex_id}) { $vertex_idx = $vertices_map{$vertex_id}; } else { push @$vertices, [map $_ * 1, $2, $3, $4]; $vertex_idx = $vertices_map{$vertex_id} = $#$vertices; } push @$facet, $vertex_idx; } } } if ($facet) { die "STL file seems invalid\n"; } } sub _read_binary { my ($fh, $facets, $vertices) = @_; die "bigfloat" unless length(pack "f", 1) == 4; my %vertices_map = (); binmode $fh; seek $fh, 80 + 4, 0; while (read $fh, $_, 4*4*3+2) { push @$facets, my $facet = []; for (unpack 'x[f3](a[f3])3') { # ignore normal my $vertex_idx; if (exists $vertices_map{$_}) { $vertex_idx = $vertices_map{$_}; } else { push @$vertices, [ unpack 'f<3', $_ ]; $vertex_idx = $vertices_map{$_} = $#$vertices; } push @$facet, $vertex_idx; } } } sub write_file { my $self = shift; my ($file, $model, %params) = @_; Slic3r::open(\my $fh, '>', $file); $params{binary} ? _write_binary($fh, $model->mesh) : _write_ascii($fh, $model->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[-3..-1]), 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[-3..-1]; 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[-3..-1]); } 1;