| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package CAD::Mesh3D::FormatSTL; |
|
2
|
|
|
|
|
|
|
$VERSION = v0.2.1.001; # patched version of CAD::Format::STL v0.2.1 |
|
3
|
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
310464
|
use warnings; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
162
|
|
|
5
|
2
|
|
|
2
|
|
14
|
use strict; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
73
|
|
|
6
|
2
|
|
|
2
|
|
14
|
use Carp; |
|
|
2
|
|
|
|
|
15
|
|
|
|
2
|
|
|
|
|
177
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
1204
|
use CAD::Format::STL::part; |
|
|
2
|
|
|
|
|
21833
|
|
|
|
2
|
|
|
|
|
131
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
CAD::Mesh3D::FormatSTL - read/write 3D stereolithography files |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 DON'T USE |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Please don't use this module. CAD::Mesh3D::FormatSTL exists only for |
|
17
|
|
|
|
|
|
|
L to use during testing and to overcome limitations |
|
18
|
|
|
|
|
|
|
in L. If you think you want to use this directly, |
|
19
|
|
|
|
|
|
|
use L instead, and encourage the author to implement |
|
20
|
|
|
|
|
|
|
and release the known bug-fix that is in the existing issues, possibly |
|
21
|
|
|
|
|
|
|
patching it per the instructions in the L. |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Reading: |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my $stl = CAD::Mesh3D::FormatSTL->new->load("foo.stl"); |
|
28
|
|
|
|
|
|
|
# what about the part/multipart? |
|
29
|
|
|
|
|
|
|
my @facets = $stl->part->facets; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Writing: |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $stl = CAD::Mesh3D::FormatSTL->new; |
|
34
|
|
|
|
|
|
|
my $part = $stl->add_part("my part"); |
|
35
|
|
|
|
|
|
|
$part->add_facets(@faces); |
|
36
|
|
|
|
|
|
|
$stl->save("foo.stl"); |
|
37
|
|
|
|
|
|
|
# or $stl->save(binary => "foo.stl"); |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Streaming read/write: |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $reader = CAD::Mesh3D::FormatSTL->reader("foo.stl"); |
|
42
|
|
|
|
|
|
|
my $writer = CAD::Mesh3D::FormatSTL->writer(binary => "bar.stl"); |
|
43
|
|
|
|
|
|
|
while(my $part = $reader->next_part) { |
|
44
|
|
|
|
|
|
|
my $part_name = $part->name; |
|
45
|
|
|
|
|
|
|
$writer->start_solid($part_name); |
|
46
|
|
|
|
|
|
|
while(my @data = $part->facet) { |
|
47
|
|
|
|
|
|
|
my ($normal, @vertices) = @data; |
|
48
|
|
|
|
|
|
|
my @v1 = @{$vertices[0]}; |
|
49
|
|
|
|
|
|
|
my @v2 = @{$vertices[0]}; |
|
50
|
|
|
|
|
|
|
my @v3 = @{$vertices[0]}; |
|
51
|
|
|
|
|
|
|
# that's just for illustration |
|
52
|
|
|
|
|
|
|
$writer->facet(\@v1, \@v2, \@v3); |
|
53
|
|
|
|
|
|
|
# note the omitted normal |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
$writer->end_solid; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=begin design |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
The reader auto-detects whether it is binary (but assumes ascii when |
|
61
|
|
|
|
|
|
|
seek can't go backwards.) |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
The reader and writer both take 1, 2, or {1,2}+2n arguments. |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
This package and/or the reader/writer are subclassable (though getting |
|
66
|
|
|
|
|
|
|
$self->reader to instantiate a subclass implies that you have subclassed |
|
67
|
|
|
|
|
|
|
$self.) |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
A cached_facet (or raw_facet) method is necessary to ensure uniform |
|
70
|
|
|
|
|
|
|
tranformation of shared points (and optimize the computation.) This |
|
71
|
|
|
|
|
|
|
would return the normal and points as a list of scalars rather than |
|
72
|
|
|
|
|
|
|
arrays, with a later call to unpack_point() or something. The caller |
|
73
|
|
|
|
|
|
|
needs to be able to handle the caching (or else there is a callback for |
|
74
|
|
|
|
|
|
|
non-cached (or an override for unpack_point().) |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Maybe $self->set_writer() and set_reader() immutable object methods? |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=end design |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head1 ABOUT |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
This module provides object-oriented methods to read and write the STL |
|
83
|
|
|
|
|
|
|
(Stereo Lithography) file format in both binary and ASCII forms. The |
|
84
|
|
|
|
|
|
|
STL format is a simple set of 3D triangles. |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
|
87
|
|
|
|
|
|
|
|
|
88
|
2
|
|
|
2
|
|
16
|
use Class::Accessor::Classy; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
11
|
|
|
89
|
|
|
|
|
|
|
lo 'parts'; |
|
90
|
2
|
|
|
2
|
|
492
|
no Class::Accessor::Classy; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
8
|
|
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head1 Constructor |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=head2 new |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
my $stl = CAD::Mesh3D::FormatSTL->new; |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=cut |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub new { |
|
101
|
17
|
|
|
17
|
1
|
510694
|
my $package = shift; |
|
102
|
17
|
|
100
|
|
|
106
|
my $class = ref($package) || $package; |
|
103
|
17
|
|
|
|
|
61
|
my $self = {parts => []}; |
|
104
|
17
|
|
|
|
|
49
|
bless($self, $class); |
|
105
|
17
|
|
|
|
|
68
|
return($self); |
|
106
|
|
|
|
|
|
|
} # end subroutine new definition |
|
107
|
|
|
|
|
|
|
######################################################################## |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head2 add_part |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
Create a new part in the stl. |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
my $part = $stl->add_part("name"); |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Optionally, add the faces directly: |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
my $part = $stl->add_part("name", @faces); |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=cut |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub add_part { |
|
122
|
10
|
|
|
10
|
1
|
819
|
my $self = shift; |
|
123
|
10
|
|
|
|
|
36
|
my ($name, @faces) = @_; |
|
124
|
|
|
|
|
|
|
|
|
125
|
10
|
|
|
|
|
107
|
my $part = CAD::Format::STL::part::->new($name, @faces); |
|
126
|
10
|
|
|
|
|
240
|
push(@{$self->{parts}}, $part); |
|
|
10
|
|
|
|
|
41
|
|
|
127
|
10
|
|
|
|
|
30
|
return($part); |
|
128
|
|
|
|
|
|
|
} # end subroutine add_part definition |
|
129
|
|
|
|
|
|
|
######################################################################## |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head2 part |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Get the part at $index. Negative indices are valid. |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
my $part = $stl->part($index); |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
Throws an error if there is no such part. |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub part { |
|
142
|
5
|
|
|
5
|
1
|
6313
|
my $self = shift; |
|
143
|
5
|
|
|
|
|
14
|
my ($index) = @_; |
|
144
|
|
|
|
|
|
|
|
|
145
|
5
|
100
|
|
|
|
11
|
@{$self->{parts}} or croak("file has no parts"); |
|
|
5
|
|
|
|
|
41
|
|
|
146
|
|
|
|
|
|
|
|
|
147
|
4
|
|
100
|
|
|
16
|
$index ||= 0; |
|
148
|
4
|
100
|
|
|
|
46
|
exists($self->{parts}[$index]) or croak("no part $index"); |
|
149
|
2
|
|
|
|
|
10
|
return($self->{parts}[$index]); |
|
150
|
|
|
|
|
|
|
} # end subroutine part definition |
|
151
|
|
|
|
|
|
|
######################################################################## |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head1 I/O Methods |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=head2 load |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
Load an STL file (auto-detects binary/ascii) |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
$stl = $stl->load("filename.stl"); |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Optionally, explicitly declare binary mode: |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
$stl = $stl->load(binary => "filename.stl"); |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
The $self object is returned to allow e.g. chaining to C. |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
The filename may also be a filehandle. |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=cut |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub load { |
|
172
|
13
|
|
|
13
|
1
|
248
|
my $self = shift; |
|
173
|
13
|
|
|
|
|
39
|
my ($file, @and) = @_; |
|
174
|
|
|
|
|
|
|
|
|
175
|
13
|
|
|
|
|
26
|
my $mode; |
|
176
|
13
|
100
|
|
|
|
43
|
if(@and) { |
|
177
|
6
|
100
|
|
|
|
63
|
(@and > 1) and croak('too many arguments to load()'); |
|
178
|
5
|
|
|
|
|
13
|
$mode = $file; |
|
179
|
5
|
|
|
|
|
12
|
($file) = @and; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# allow filehandle |
|
183
|
12
|
100
|
100
|
|
|
73
|
unless((ref($file) || '') eq 'GLOB') { |
|
184
|
11
|
100
|
|
|
|
700
|
open(my $fh, '<', $file) or |
|
185
|
|
|
|
|
|
|
die "cannot open '$file' for reading $!"; |
|
186
|
10
|
|
|
|
|
43
|
$file = $fh; |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# detection |
|
190
|
11
|
100
|
|
|
|
39
|
unless($mode) { |
|
191
|
6
|
50
|
|
|
|
46
|
unless(seek($file, 0,0)) { |
|
192
|
0
|
|
|
|
|
0
|
croak('must have explicit mode for non-seekable filehandle'); |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
# now, detection... |
|
195
|
|
|
|
|
|
|
$mode = sub { |
|
196
|
6
|
|
|
6
|
|
14
|
my $fh = shift; |
|
197
|
6
|
|
|
|
|
33
|
seek($fh, 80, 0); |
|
198
|
6
|
|
|
|
|
13
|
my $count = eval { |
|
199
|
6
|
100
|
|
|
|
13
|
my $buf; read($fh, $buf, 4) or die; |
|
|
6
|
|
|
|
|
285
|
|
|
200
|
5
|
|
|
|
|
30
|
unpack('L', $buf); |
|
201
|
|
|
|
|
|
|
}; |
|
202
|
6
|
100
|
|
|
|
22
|
$@ and return 'ascii'; # if we hit eof, it can't be binary |
|
203
|
5
|
100
|
|
|
|
48
|
$count or die "detection failed - no facets?"; |
|
204
|
4
|
|
|
|
|
46
|
my $size = (stat($fh))[7]; |
|
205
|
|
|
|
|
|
|
# calculate the expected file size |
|
206
|
4
|
|
|
|
|
14
|
my $expect = |
|
207
|
|
|
|
|
|
|
+ 80 # header |
|
208
|
|
|
|
|
|
|
+ 4 # count |
|
209
|
|
|
|
|
|
|
+ $count * ( |
|
210
|
|
|
|
|
|
|
+ 4 # normal, pt,pt,pt (vectors) |
|
211
|
|
|
|
|
|
|
* 4 # bytes per value |
|
212
|
|
|
|
|
|
|
* 3 # values per vector |
|
213
|
|
|
|
|
|
|
+ 2 # the trailing 'short' |
|
214
|
|
|
|
|
|
|
); |
|
215
|
4
|
100
|
|
|
|
18
|
return ($size == $expect) ? 'binary' : 'ascii'; |
|
216
|
6
|
|
|
|
|
47
|
}->($file); |
|
217
|
5
|
50
|
|
|
|
90
|
seek($file, 0, 0) or die "cannot reset filehandle"; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
10
|
|
|
|
|
50
|
my $method = '_read_' . lc($mode); |
|
221
|
10
|
50
|
|
|
|
105
|
$self->can($method) or croak("invalid read mode '$mode'"); |
|
222
|
|
|
|
|
|
|
|
|
223
|
10
|
|
|
|
|
43
|
$self->$method($file); |
|
224
|
7
|
|
|
|
|
143
|
return($self); |
|
225
|
|
|
|
|
|
|
} # end subroutine load definition |
|
226
|
|
|
|
|
|
|
######################################################################## |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 _read_ascii |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
$self->_read_ascii($filehandle); |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub _read_ascii { |
|
235
|
7
|
|
|
7
|
|
15
|
my $self = shift; |
|
236
|
7
|
|
|
|
|
20
|
my ($fh) = @_; |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
my $getline = sub { |
|
239
|
362
|
|
|
362
|
|
3629
|
while(my $line = <$fh>) { |
|
240
|
357
|
|
|
|
|
2306
|
$line =~ s/\s*$//; # allow any eol |
|
241
|
357
|
50
|
|
|
|
818
|
length($line) or next; |
|
242
|
357
|
|
|
|
|
964
|
return($line); |
|
243
|
|
|
|
|
|
|
} |
|
244
|
5
|
|
|
|
|
21
|
return; |
|
245
|
7
|
|
|
|
|
39
|
}; |
|
246
|
7
|
|
|
|
|
45
|
my $p_re = qr/([^ ]+)\s+([^ ]+)\s+([^ ]+)$/; |
|
247
|
|
|
|
|
|
|
|
|
248
|
7
|
|
|
|
|
14
|
my $part; |
|
249
|
7
|
|
|
|
|
17
|
while(my $line = $getline->()) { |
|
250
|
|
|
|
|
|
|
|
|
251
|
62
|
100
|
|
|
|
300
|
if($line =~ m/^\s*solid (.*)/) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
252
|
6
|
|
|
|
|
24
|
$part = $self->add_part($1); |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
elsif($line =~ m/^\s*endsolid (.*)/) { |
|
255
|
5
|
|
|
|
|
13
|
my $name = $1; |
|
256
|
5
|
50
|
|
|
|
16
|
$part or die "invalid 'endsolid' entry with no current part"; |
|
257
|
5
|
50
|
|
|
|
167
|
($name eq $part->name) or |
|
258
|
|
|
|
|
|
|
die "end of part '$name' should have been '", |
|
259
|
|
|
|
|
|
|
$part->name, "'"; |
|
260
|
5
|
|
|
|
|
59
|
$part = undef; |
|
261
|
|
|
|
|
|
|
} |
|
262
|
|
|
|
|
|
|
elsif($part) { |
|
263
|
50
|
50
|
|
|
|
577
|
my @n = ($line =~ m/^\s*facet\s+normal\s+$p_re/) or |
|
264
|
|
|
|
|
|
|
die "how did that happen? ($line)"; |
|
265
|
|
|
|
|
|
|
#warn "got ", join('|', @n); |
|
266
|
50
|
|
|
|
|
127
|
my @facet = (\@n); |
|
267
|
|
|
|
|
|
|
|
|
268
|
50
|
|
|
|
|
103
|
my $next = $getline->(); |
|
269
|
50
|
100
|
66
|
|
|
327
|
unless($next and ($next =~ m/^\s*outer\s+loop$/)) { |
|
270
|
1
|
|
|
|
|
43
|
die "facet doesn't start with 'outer loop' ($next)"; |
|
271
|
|
|
|
|
|
|
} |
|
272
|
49
|
|
|
|
|
85
|
push(@facet, do { |
|
273
|
49
|
|
|
|
|
78
|
my @got; |
|
274
|
49
|
|
|
|
|
98
|
while(my $line = $getline->()) { |
|
275
|
196
|
100
|
|
|
|
550
|
($line =~ m/^\s*endloop$/) and last; |
|
276
|
147
|
50
|
|
|
|
968
|
if($line =~ m/^\s*vertex\s+$p_re/) { |
|
277
|
147
|
|
|
|
|
757
|
push(@got, [$1, $2, $3]); |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
} |
|
280
|
49
|
|
|
|
|
129
|
@got; |
|
281
|
|
|
|
|
|
|
}); |
|
282
|
49
|
50
|
|
|
|
129
|
(scalar(@facet) == 4) or |
|
283
|
|
|
|
|
|
|
die "need three vertices per facet (not $#facet)"; |
|
284
|
49
|
|
|
|
|
101
|
my $end = $getline->(); |
|
285
|
49
|
50
|
33
|
|
|
271
|
($end and ($end =~ m/^\s*endfacet/)) or |
|
286
|
|
|
|
|
|
|
die "bad endfacet $line"; |
|
287
|
49
|
|
|
|
|
197
|
$part->add_facets([@facet]); |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
else { |
|
290
|
1
|
|
|
|
|
2772
|
die "what? ($line)"; |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
} |
|
293
|
5
|
50
|
|
|
|
42
|
$part and die "part '", $part->name, "' was left open"; |
|
294
|
|
|
|
|
|
|
} # end subroutine _read_ascii definition |
|
295
|
|
|
|
|
|
|
######################################################################## |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=head2 get_ |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
These functions are currently only used internally. |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=over |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=item get_triangle |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=item get_ulong |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=item get_float32 |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=item get_short |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=back |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=cut |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub get_triangle { |
|
316
|
51
|
|
|
51
|
1
|
116
|
my ($fh) = @_; |
|
317
|
|
|
|
|
|
|
|
|
318
|
51
|
|
|
|
|
105
|
my ($n, $x, $y, $z) = map({[map({get_float32($fh)} 1..3)]} 1..4); |
|
|
204
|
|
|
|
|
356
|
|
|
|
612
|
|
|
|
|
1132
|
|
|
319
|
51
|
|
|
|
|
124
|
my $scrap = get_short($fh); |
|
320
|
51
|
|
|
|
|
131
|
return($n, $x, $y, $z); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
sub get_ulong { |
|
324
|
3
|
|
|
3
|
1
|
9
|
my ($fh) = @_; |
|
325
|
|
|
|
|
|
|
|
|
326
|
3
|
|
|
|
|
7
|
my $buf; |
|
327
|
3
|
50
|
|
|
|
60
|
read($fh, $buf, 4) or warn "EOF?"; |
|
328
|
3
|
|
|
|
|
16
|
return(unpack('L', $buf)); |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub get_float32 { |
|
332
|
612
|
|
|
612
|
1
|
1107
|
my ($fh) = @_; |
|
333
|
|
|
|
|
|
|
|
|
334
|
612
|
|
|
|
|
987
|
my $buf; |
|
335
|
612
|
100
|
|
|
|
1595
|
read($fh, $buf, 4) or warn "EOF?"; |
|
336
|
612
|
|
|
|
|
1660
|
return(unpack('f', $buf)); |
|
337
|
|
|
|
|
|
|
} |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub get_short { |
|
340
|
51
|
|
|
51
|
1
|
87
|
my ($fh) = @_; |
|
341
|
|
|
|
|
|
|
|
|
342
|
51
|
|
|
|
|
432
|
my $buf; |
|
343
|
51
|
100
|
|
|
|
155
|
read($fh, $buf, 2) or warn "EOF?"; |
|
344
|
51
|
|
|
|
|
108
|
return(unpack('S', $buf)); |
|
345
|
|
|
|
|
|
|
} |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=head2 _read_binary |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
$self->_read_binary($filehandle); |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=cut |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
sub _read_binary { |
|
354
|
3
|
|
|
3
|
|
7
|
my $self = shift; |
|
355
|
3
|
|
|
|
|
9
|
my ($fh) = @_; |
|
356
|
|
|
|
|
|
|
|
|
357
|
3
|
|
|
|
|
8
|
binmode $fh; |
|
358
|
|
|
|
|
|
|
|
|
359
|
3
|
50
|
|
|
|
124
|
$self->parts and die "binary STL files must have only one part"; |
|
360
|
|
|
|
|
|
|
|
|
361
|
3
|
|
|
|
|
35
|
die "bigfloat" unless(length(pack("f", 1)) == 4); |
|
362
|
|
|
|
|
|
|
# TODO try to read part name from header (up to \0) |
|
363
|
3
|
|
|
|
|
8
|
my $name = 'a part'; |
|
364
|
3
|
|
|
|
|
50
|
seek($fh, 80, 0); |
|
365
|
|
|
|
|
|
|
|
|
366
|
3
|
|
|
|
|
10
|
my $triangles = get_ulong($fh); |
|
367
|
3
|
|
|
|
|
12
|
my $part = $self->add_part($name); |
|
368
|
|
|
|
|
|
|
|
|
369
|
3
|
|
|
|
|
6
|
my $count = 0; |
|
370
|
3
|
|
|
|
|
7
|
while(1) { |
|
371
|
51
|
|
|
|
|
106
|
my @tr = get_triangle($fh); |
|
372
|
|
|
|
|
|
|
# TODO check that the unit normal is within a thousandth of a radian |
|
373
|
|
|
|
|
|
|
# (0.001 rad is ~0.06deg) |
|
374
|
51
|
|
|
|
|
181
|
$part->add_facets([@tr]); |
|
375
|
51
|
|
|
|
|
2423
|
$count++; |
|
376
|
51
|
100
|
|
|
|
181
|
eof($fh) and last; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
3
|
100
|
|
|
|
64
|
($count == $triangles) or |
|
379
|
|
|
|
|
|
|
die "ERROR: got $count facets (expected $triangles)"; |
|
380
|
|
|
|
|
|
|
} # end subroutine _read_binary definition |
|
381
|
|
|
|
|
|
|
######################################################################## |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head2 save |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
$stl->save("filename.stl"); |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
$stl->save(binary => "filename.stl"); |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=cut |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
sub save { |
|
392
|
1
|
|
|
1
|
1
|
3122
|
my $self = shift; |
|
393
|
1
|
|
|
|
|
4
|
my ($file, @and) = @_; |
|
394
|
|
|
|
|
|
|
|
|
395
|
1
|
|
|
|
|
3
|
my $mode; |
|
396
|
1
|
50
|
|
|
|
5
|
if(@and) { |
|
397
|
0
|
0
|
|
|
|
0
|
(@and > 1) and croak('too many arguments to save()'); |
|
398
|
0
|
|
|
|
|
0
|
$mode = $file; |
|
399
|
0
|
|
|
|
|
0
|
($file) = @and; |
|
400
|
|
|
|
|
|
|
} |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# allow filehandle |
|
403
|
1
|
50
|
50
|
|
|
7
|
unless((ref($file) || '') eq 'GLOB') { |
|
404
|
0
|
0
|
|
|
|
0
|
open(my $fh, '>', $file) or |
|
405
|
|
|
|
|
|
|
die "cannot open '$file' for writing $!"; |
|
406
|
0
|
|
|
|
|
0
|
$file = $fh; |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
|
|
409
|
1
|
50
|
|
|
|
4
|
$mode = 'ascii' unless($mode); |
|
410
|
|
|
|
|
|
|
|
|
411
|
1
|
|
|
|
|
4
|
my $method = '_write_' . lc($mode); |
|
412
|
1
|
50
|
|
|
|
20
|
$self->can($method) or croak("invalid write mode '$mode'"); |
|
413
|
|
|
|
|
|
|
|
|
414
|
1
|
|
|
|
|
7
|
$self->$method($file); |
|
415
|
|
|
|
|
|
|
} # end subroutine save definition |
|
416
|
|
|
|
|
|
|
######################################################################## |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head2 _write_binary |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
$self->_write_binary($filehandle); |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=cut |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub _write_binary { |
|
425
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
426
|
0
|
|
|
|
|
0
|
my ($fh) = @_; |
|
427
|
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
0
|
my ($part, @and) = $self->parts; |
|
429
|
0
|
0
|
|
|
|
0
|
@and and die 'cannot write binary files with multiple parts'; |
|
430
|
|
|
|
|
|
|
|
|
431
|
0
|
|
|
|
|
0
|
binmode $fh; |
|
432
|
|
|
|
|
|
|
|
|
433
|
0
|
|
|
|
|
0
|
my $name = $part->name; # utf8 is ok |
|
434
|
2
|
|
|
2
|
|
7109
|
print $fh $name, "\0" x (80 - do {use bytes; length($name)}); |
|
|
2
|
|
|
|
|
1164
|
|
|
|
2
|
|
|
|
|
14
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
435
|
0
|
|
|
|
|
0
|
my @facets = $part->facets; |
|
436
|
0
|
|
|
|
|
0
|
print $fh pack('L', scalar(@facets)); |
|
437
|
0
|
|
|
|
|
0
|
foreach my $facet (@facets) { |
|
438
|
0
|
|
|
|
|
0
|
print $fh map({map({pack('f', $_)} @$_)} @$facet); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
439
|
0
|
|
|
|
|
0
|
print $fh "\0" x 2; |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
} # end subroutine _write_binary definition |
|
443
|
|
|
|
|
|
|
######################################################################## |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head2 _write_ascii |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
$self->_write_ascii($filehandle); |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=cut |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub _write_ascii { |
|
452
|
1
|
|
|
1
|
|
3
|
my $self = shift; |
|
453
|
1
|
|
|
|
|
3
|
my ($fh) = @_; |
|
454
|
|
|
|
|
|
|
|
|
455
|
1
|
|
|
|
|
2
|
my $spaces = ''; |
|
456
|
1
|
|
|
85
|
|
6
|
my $print = sub {print $fh $spaces, @_, "\n"}; |
|
|
85
|
|
|
|
|
200
|
|
|
457
|
1
|
50
|
|
|
|
49
|
my @parts = $self->parts or croak("no parts to write"); |
|
458
|
1
|
|
|
|
|
16
|
foreach my $part (@parts) { |
|
459
|
1
|
|
|
|
|
31
|
$print->('solid ', $part->name); |
|
460
|
1
|
|
|
|
|
4
|
$spaces = ' 'x2; |
|
461
|
1
|
|
|
|
|
29
|
foreach my $facet ($part->facets) { |
|
462
|
12
|
|
|
|
|
66
|
my ($n, @pts) = @$facet; |
|
463
|
12
|
|
|
|
|
52
|
$print->(join(' ', 'facet normal', @$n)); |
|
464
|
12
|
|
|
|
|
22
|
$spaces = ' 'x4; |
|
465
|
12
|
|
|
|
|
27
|
$print->('outer loop'); |
|
466
|
12
|
|
|
|
|
23
|
$spaces = ' 'x6; |
|
467
|
12
|
50
|
|
|
|
31
|
(@pts == 3) or die "invalid facet"; |
|
468
|
12
|
|
|
|
|
42
|
foreach my $pt (@pts) { |
|
469
|
36
|
|
|
|
|
143
|
$print->(join(' ', 'vertex', @$pt)); |
|
470
|
|
|
|
|
|
|
} |
|
471
|
12
|
|
|
|
|
19
|
$spaces = ' 'x4; |
|
472
|
12
|
|
|
|
|
28
|
$print->('endloop'); |
|
473
|
12
|
|
|
|
|
18
|
$spaces = ' 'x2; |
|
474
|
12
|
|
|
|
|
22
|
$print->('endfacet'); |
|
475
|
|
|
|
|
|
|
} |
|
476
|
1
|
|
|
|
|
4
|
$spaces = ''; |
|
477
|
1
|
|
|
|
|
44
|
print $fh 'endsolid ', $part->name, "\n"; |
|
478
|
|
|
|
|
|
|
} |
|
479
|
|
|
|
|
|
|
} # end subroutine _write_ascii definition |
|
480
|
|
|
|
|
|
|
######################################################################## |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=head1 AUTHOR |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
Eric Wilhelm @ |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
http://scratchcomputing.com/ |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
CAD::Format::STL Copyright (C) 2007 Eric L. Wilhelm, All Rights Reserved. |
|
491
|
|
|
|
|
|
|
CAD::Mesh3D::FormatSTL Copyright (C) 2021 Peter C. Jones, All Rights Reserved. |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head1 NO WARRANTY |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Absolutely, positively NO WARRANTY, neither express or implied, is |
|
496
|
|
|
|
|
|
|
offered with this software. You use this software at your own risk. In |
|
497
|
|
|
|
|
|
|
case of loss, no person or entity owes you anything whatsoever. You |
|
498
|
|
|
|
|
|
|
have been warned. |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=head1 LICENSE |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
|
503
|
|
|
|
|
|
|
under the same terms as Perl itself. |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head1 PATCHED BY CAD::Mesh3D |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
Per the LICENSE following the same terms as Perl, the |
|
508
|
|
|
|
|
|
|
L |
|
509
|
|
|
|
|
|
|
allows publishing a modified or patched version under the same |
|
510
|
|
|
|
|
|
|
name as long as it is made freely available or by allowing the |
|
511
|
|
|
|
|
|
|
original copyright holder to include my modifications in the |
|
512
|
|
|
|
|
|
|
standard version of the package. As the core modifications |
|
513
|
|
|
|
|
|
|
have been in CAD::Format::STL's |
|
514
|
|
|
|
|
|
|
L |
|
515
|
|
|
|
|
|
|
since Feb 2013, the CAD::Mesh3D developer feels justified in providing |
|
516
|
|
|
|
|
|
|
the patched version along with CAD::Mesh3D, which requires the patched |
|
517
|
|
|
|
|
|
|
version to be used in Windows. However, to avoid offense and confusion, |
|
518
|
|
|
|
|
|
|
the file/module that includes the patch has been renamed to |
|
519
|
|
|
|
|
|
|
CAD::Mesh3D::FormatSTL in this distribution. |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
If the original author of CAD::Format::STL ever publishes a newer version |
|
522
|
|
|
|
|
|
|
that doesn't contain the bug, this patched version will not be used by |
|
523
|
|
|
|
|
|
|
CAD::Mesh3D, and the official module will be used instead. |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=cut |
|
526
|
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# vi:ts=2:sw=2:et:sta |
|
528
|
|
|
|
|
|
|
1; |