line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CAD::Mesh3D::STL;
|
2
|
4
|
|
|
4
|
|
30
|
use warnings;
|
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
150
|
|
3
|
4
|
|
|
4
|
|
20
|
use strict;
|
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
81
|
|
4
|
4
|
|
|
4
|
|
19
|
use Carp;
|
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
250
|
|
5
|
4
|
|
|
4
|
|
74
|
use 5.010; # M::V::R requires 5.010, so might as well make use of the defined-or // notation :-)
|
|
4
|
|
|
|
|
13
|
|
6
|
4
|
|
|
4
|
|
40
|
use CAD::Format::STL qw//;
|
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
76
|
|
7
|
4
|
|
|
4
|
|
29
|
use CAD::Mesh3D qw/:create/;
|
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
82
|
|
8
|
|
|
|
|
|
|
our $VERSION = 0.001_011;
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
CAD::Mesh3D::STL - Used by CAD::Mesh3D to provide the STL format-specific functionality
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
use CAD::Mesh3D qw(+STL :create :formats);
|
17
|
|
|
|
|
|
|
my $vect = createVertex();
|
18
|
|
|
|
|
|
|
my $tri = createFacet($v1, $v2, $v3);
|
19
|
|
|
|
|
|
|
my $mesh = createMesh();
|
20
|
|
|
|
|
|
|
$mesh->addToMesh($tri);
|
21
|
|
|
|
|
|
|
...
|
22
|
|
|
|
|
|
|
$mesh->output(STL => $filehandle_or_filename, $ascii_or_binary);
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
This module is used by L to provide the STL format-specific functionality, including
|
27
|
|
|
|
|
|
|
saving B as STL files, or loading a B from STL files.
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
L ("stereolithography") files are a CAD format used as inputs in the 3D printing process.
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
The module supports either ASCII (plain-text) or binary (encoded) STL files.
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=cut
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
################################################################
|
36
|
|
|
|
|
|
|
# Exports
|
37
|
|
|
|
|
|
|
################################################################
|
38
|
|
|
|
|
|
|
|
39
|
4
|
|
|
4
|
|
32
|
use Exporter 5.57 'import'; # v5.57 needed for getting import() without @ISA
|
|
4
|
|
|
|
|
70
|
|
|
4
|
|
|
|
|
2559
|
|
40
|
|
|
|
|
|
|
our @EXPORT_OK = ();
|
41
|
|
|
|
|
|
|
our @EXPORT = ();
|
42
|
|
|
|
|
|
|
our %EXPORT_TAGS = (
|
43
|
|
|
|
|
|
|
all => \@EXPORT_OK,
|
44
|
|
|
|
|
|
|
);
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head2 enableFormat
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
You need to tell L where to find this STL module. You can
|
49
|
|
|
|
|
|
|
either specify C<+STL> when you C |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
use CAD::Mesh3D qw(+STL :create :formats);
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Or you can independently enable the STL format sometime later:
|
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
use CAD::Mesh3D qw(:create :formats);
|
56
|
|
|
|
|
|
|
enableFormat( 'STL' );
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=cut
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
################################################################
|
61
|
|
|
|
|
|
|
# _io_functions():
|
62
|
|
|
|
|
|
|
# CAD::Mesh3D::enableFormat('STL') calls CAD::Mesh3D::STL::_io_functions(),
|
63
|
|
|
|
|
|
|
# and expects it to return a hash with coderefs the 'input'
|
64
|
|
|
|
|
|
|
# and 'output' functions. Use undef (or leave out the key/value entirely)
|
65
|
|
|
|
|
|
|
# for a direction that doesn't exist.
|
66
|
|
|
|
|
|
|
# _io_functions { input => \&inputSTL, output => \&outputSTL }
|
67
|
|
|
|
|
|
|
# _io_functions { input => undef, output => \&outputSTL }
|
68
|
|
|
|
|
|
|
# _io_functions { output => \&outputSTL }
|
69
|
|
|
|
|
|
|
# _io_functions { input => sub { ... } }
|
70
|
|
|
|
|
|
|
################################################################
|
71
|
|
|
|
|
|
|
sub _io_functions {
|
72
|
|
|
|
|
|
|
return (
|
73
|
4
|
|
|
4
|
|
18
|
output => \&outputStl,
|
74
|
|
|
|
|
|
|
input => \&inputStl, # sub { croak sprintf "Sorry, %s's developer has not yet debugged inputting from STL", __PACKAGE__ },
|
75
|
|
|
|
|
|
|
);
|
76
|
|
|
|
|
|
|
}
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
################################################################
|
79
|
|
|
|
|
|
|
# file output
|
80
|
|
|
|
|
|
|
################################################################
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 FILE OUTPUT
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=head3 output
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=head3 outputStl
|
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
To output your B using the STL format, you should use CAD::Mesh3D's C |
89
|
|
|
|
|
|
|
wrapper method. You can also call it as a function, which is included in the C<:formats> import tag.
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
use CAD::Mesh3D qw/+STL :formats/;
|
92
|
|
|
|
|
|
|
$mesh->output(STL => $file, $asc);
|
93
|
|
|
|
|
|
|
# or
|
94
|
|
|
|
|
|
|
output($mesh, STL => $file, $asc);
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
The wrapper will call the C function internally, but
|
97
|
|
|
|
|
|
|
makes it easy to keep your code compatible with other 3d-file formats.
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
If you insist on calling the STL function directly, it is possible, but not
|
100
|
|
|
|
|
|
|
recommended, to call
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
CAD::Mesh3D::STL::outputStl($mesh, $file, $asc);
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
The C<$file> argument is either an already-opened filehandle, or the name of the file
|
105
|
|
|
|
|
|
|
(if the full path is not specified, it will default to your script's directory),
|
106
|
|
|
|
|
|
|
or "STDOUT" or "STDERR" to direct the output to the standard handles.
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
The C<$asc> argument determines whether to use STL's ASCII mode: a non-zero numeric value,
|
109
|
|
|
|
|
|
|
or the case-insensitive text "ASCII" or "ASC" will select ASCII mode; a missing or undefined
|
110
|
|
|
|
|
|
|
C<$asc> argument, or a zero value or empty string, or the case-insensitive text "BINARY"
|
111
|
|
|
|
|
|
|
or "BIN" will select BINARY mode; if the argument contains a string other than those mentioned,
|
112
|
|
|
|
|
|
|
S> will cause the script to die.
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=cut
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# outputStl(mesh, file, asc)
|
117
|
|
|
|
|
|
|
sub outputStl {
|
118
|
|
|
|
|
|
|
# verify it's a valid mesh
|
119
|
16
|
|
|
16
|
1
|
29
|
my $mesh = shift;
|
120
|
16
|
|
|
|
|
42
|
for($mesh) { # TODO = error handling
|
121
|
|
|
|
|
|
|
} # /check_mesh
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# process the filehandle / filename
|
124
|
16
|
|
|
|
|
26
|
my $doClose = 0; # don't close the filehandle when done, unless it's a filename
|
125
|
16
|
|
|
|
|
26
|
my $fh = my $fn = shift;
|
126
|
16
|
|
|
|
|
28
|
for($fh) { # check_fh
|
127
|
16
|
100
|
|
|
|
54
|
croak sprintf('!ERROR! outputStl(mesh, fh, opt): requires file handle or name') unless $_;
|
128
|
15
|
100
|
|
|
|
69
|
$_ = \*STDOUT if /^STDOUT$/i;
|
129
|
15
|
100
|
|
|
|
41
|
$_ = \*STDERR if /^STDERR$/i;
|
130
|
15
|
100
|
|
|
|
40
|
if( 'GLOB' ne ref $_ ) {
|
131
|
3
|
100
|
|
|
|
21
|
$fn .= '.stl' unless $fn =~ /\.stl$/i;
|
132
|
3
|
100
|
|
|
|
389
|
open my $tfh, '>', $fn or croak sprintf('!ERROR! outputStl(): cannot write to "%s": %s', $fn, $!);
|
133
|
2
|
|
|
|
|
9
|
$_ = $tfh;
|
134
|
2
|
|
|
|
|
9
|
$doClose++; # will need to close the file
|
135
|
|
|
|
|
|
|
}
|
136
|
|
|
|
|
|
|
} # /check_fh
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# determine whether it's ASCII or binary
|
139
|
14
|
|
100
|
|
|
44
|
my $asc = shift || 0; check_asc: for($asc) {
|
|
14
|
|
|
|
|
28
|
|
140
|
14
|
100
|
|
|
|
50
|
$_ = 1 if /^(?:ASC(?:|II)|true)$/i;
|
141
|
14
|
100
|
|
|
|
37
|
$_ = 0 if /^(?:bin(?:|ary)|false)$/i;
|
142
|
14
|
100
|
100
|
|
|
73
|
croak sprintf('!ERROR! outputStl(): unknown asc/bin switch "%s"', $_) if $_ && /\D/;
|
143
|
|
|
|
|
|
|
} # /check_asc
|
144
|
13
|
100
|
|
|
|
34
|
binmode $fh unless $asc;
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
#############################################################################################
|
147
|
|
|
|
|
|
|
# use CAD::Format::STL to output the STL
|
148
|
|
|
|
|
|
|
#############################################################################################
|
149
|
13
|
|
|
|
|
74
|
my $stl = CAD::Format::STL->new;
|
150
|
13
|
|
|
|
|
152
|
my $part = $stl->add_part("my part", @$mesh);
|
151
|
|
|
|
|
|
|
|
152
|
13
|
100
|
|
|
|
1975
|
if($asc) {
|
153
|
5
|
|
|
|
|
16
|
$stl->save( ascii => $fh );
|
154
|
|
|
|
|
|
|
} else {
|
155
|
8
|
|
|
|
|
22
|
$stl->save( binary => $fh );
|
156
|
|
|
|
|
|
|
}
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# close the file, if outputStl() is where the handle was opened (ie, not on existing fh, STDERR, or STDOUT)
|
159
|
13
|
100
|
|
|
|
3223
|
close($fh) if $doClose;
|
160
|
13
|
|
|
|
|
86
|
return;
|
161
|
|
|
|
|
|
|
}
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 FILE INPUT
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head3 input
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head3 inputStl
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
To input your B from an STL file, you should use L's C wrapper function,
|
170
|
|
|
|
|
|
|
which is included in the C<:formats> import tag.
|
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
use CAD::Mesh3D qw/+STL :formats/;
|
173
|
|
|
|
|
|
|
my $mesh = input(STL => $file, $mode);
|
174
|
|
|
|
|
|
|
my $mesh2= input(STL => $file); # will determine ascii/binary based on file contents
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
The wrapper will call the C function internally, but makes it easy to
|
177
|
|
|
|
|
|
|
keep your code compatible with other 3d-file formats.
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
If you insist on calling the STL function directly, it is possible, but not recommended, to call
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $mesh = CAD::Mesh3D::STL::inputStl($file, $mode);
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
The C<$file> argument is either an already-opened filehandle, or the name of the file
|
184
|
|
|
|
|
|
|
(if the full path is not specified, it will default to your script's directory),
|
185
|
|
|
|
|
|
|
or "STDIN" to receive the input from the standard input handle.
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
The C<$mode> argument determines whether to use STL's ASCII mode:
|
188
|
|
|
|
|
|
|
The case-insensitive text "ASCII" or "ASC" will select ASCII mode.
|
189
|
|
|
|
|
|
|
The case-insensitive text "BINARY" or "BIN" will select BINARY mode.
|
190
|
|
|
|
|
|
|
If the argument contains a string other than those mentioned, S> will cause
|
191
|
|
|
|
|
|
|
the script to die.
|
192
|
|
|
|
|
|
|
On a missing or undefined C<$mode> argument, or empty string, will cause C to try
|
193
|
|
|
|
|
|
|
to determine if it's ASCII or BINARY; C will die if it cannot determine the file's
|
194
|
|
|
|
|
|
|
mode automatically.
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Caveat: When using an in-memory filehandle, you must explicitly define the C<$mode> option,
|
197
|
|
|
|
|
|
|
otherwise C will die. (In-memory filehandles are not common. See L, search for
|
198
|
|
|
|
|
|
|
"in-memory file", to find a little more about them. It is not likely you will require such
|
199
|
|
|
|
|
|
|
a situation, but with explicit C<$mode>, they will work.)
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=cut
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub inputStl {
|
204
|
6
|
|
|
6
|
1
|
14
|
my ($file, $asc_or_bin) = @_;
|
205
|
6
|
|
|
|
|
12
|
my @pass_args = ($file);
|
206
|
6
|
100
|
100
|
|
|
48
|
if( !defined($asc_or_bin) || ('' eq $asc_or_bin)) { # automatic
|
|
|
100
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# automatic won't work on in-memory files, for which stat() will give an "unopened filehandle" warning
|
208
|
|
|
|
|
|
|
# unfortunately, perl v5.16 - v5.20 seem to _not_ give that warning. Check definedness of $size, instead
|
209
|
|
|
|
|
|
|
# (which actually simplifies the check, significantly)
|
210
|
|
|
|
|
|
|
in_memory_check: {
|
211
|
4
|
|
|
4
|
|
34
|
no warnings 'unopened'; # avoid printing the warning; just looking for the definedness of $size
|
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
1438
|
|
|
4
|
|
|
|
|
5
|
|
212
|
4
|
|
|
|
|
45
|
my $size = (stat($file))[7]; # on perl v<5.16 and v>5.20, will warn; on all tested perl, will give $size=undef
|
213
|
4
|
100
|
|
|
|
51
|
croak "\ninputStl($file): ERROR\n",
|
214
|
|
|
|
|
|
|
"\tin-memory file handles are not allowed without explicit ASCII or BINARY setting\n",
|
215
|
|
|
|
|
|
|
"\tplease rewrite the call with an explicit\n",
|
216
|
|
|
|
|
|
|
"\t\tinputStl(\$in_mem_fh, \$asc_or_bin)\n",
|
217
|
|
|
|
|
|
|
"\tor\n",
|
218
|
|
|
|
|
|
|
"\t\tinput(STL => \$in_mem_fh, \$asc_or_bin)\n",
|
219
|
|
|
|
|
|
|
"\twhere \$asc_or_bin is either 'ascii' or 'binary'\n",
|
220
|
|
|
|
|
|
|
" "
|
221
|
|
|
|
|
|
|
unless defined $size;
|
222
|
|
|
|
|
|
|
}
|
223
|
|
|
|
|
|
|
} elsif ( $asc_or_bin =~ /(asc(?:ii)?|bin(?:ary)?)/i ) {
|
224
|
|
|
|
|
|
|
# we found an explicit 'ascii/binary' indicator
|
225
|
1
|
|
|
|
|
4
|
unshift @pass_args, $asc_or_bin;
|
226
|
|
|
|
|
|
|
} else { # otherwise, error
|
227
|
1
|
|
|
|
|
17
|
croak "\ninputStl($file, '$asc_or_bin'): ERROR: unknown mode '$asc_or_bin'\n ";
|
228
|
|
|
|
|
|
|
}
|
229
|
|
|
|
|
|
|
|
230
|
3
|
|
|
|
|
20
|
my $stl = CAD::Format::STL->new()->load(@pass_args); # CFS claims it take handle or name
|
231
|
|
|
|
|
|
|
# TODO: bug report :
|
232
|
|
|
|
|
|
|
# examples show ->reader() and ->writer(), but that example code doesn't compile
|
233
|
3
|
|
|
|
|
6157
|
my @stlf = $stl->part()->facets();
|
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# facets() returns an array of array-refs;
|
236
|
|
|
|
|
|
|
# each of those has four array-refs -- three for the vertexes, and a fourth for the normal
|
237
|
|
|
|
|
|
|
# I need to igore the normal, and transform to the proper objects, in-place
|
238
|
3
|
|
|
|
|
135
|
my @facets = ();
|
239
|
3
|
|
|
|
|
8
|
foreach (@stlf) {
|
240
|
36
|
|
|
|
|
55
|
shift @$_; # ignore the normal vector
|
241
|
36
|
|
|
|
|
59
|
my @verts = ();
|
242
|
36
|
|
|
|
|
55
|
for my $v (@$_) {
|
243
|
108
|
|
|
|
|
400
|
push @verts, createVertex( @$v );
|
244
|
|
|
|
|
|
|
}
|
245
|
36
|
|
|
|
|
170
|
push @facets, createFacet(@verts);
|
246
|
|
|
|
|
|
|
}
|
247
|
3
|
|
|
|
|
18
|
return createMesh( @facets );
|
248
|
|
|
|
|
|
|
}
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head1 SEE ALSO
|
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=over
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=item * L - This is the backend used by CAD::Mesh3D::STL, which handles them
|
255
|
|
|
|
|
|
|
actual parsing and writing of the STL files.
|
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=back
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head1 KNOWN ISSUES
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head2 CAD::Format::STL binary Windows bug
|
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
There is a L in CAD::Format::STL v0.2.1,
|
264
|
|
|
|
|
|
|
which on Windows systems will cause binary STL files which happen to have the 0x0D byte to corrupt the
|
265
|
|
|
|
|
|
|
data on output or input. Most binary STL files will work just fine; but there are a non-trivial number
|
266
|
|
|
|
|
|
|
of floating-point values in the STL which include the 0x0D byte. There is a test for this in the C
|
267
|
|
|
|
|
|
|
author-tests of the CAD-Mesh3D distribution.
|
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
If your copy of CAD::Format::STL is affected by this bug, there is an easy patch, which you can manually
|
270
|
|
|
|
|
|
|
add by editing your installed C: near line 423, after the error checking in
|
271
|
|
|
|
|
|
|
C, add the line C as the fourth line of code in that sub. Similarly,
|
272
|
|
|
|
|
|
|
near line 348, add the line C as the third line of code inside the C.
|
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
The author of CAD::Format::STL has been notified, both through the
|
275
|
|
|
|
|
|
|
L, and responding to requests to
|
276
|
|
|
|
|
|
|
fix the bug. Hopefully, when the author has time, a new version of CAD::Format::STL will be released
|
277
|
|
|
|
|
|
|
with the bug fixed. Until then, patching the module is the best workaround.
|
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=head1 AUTHOR
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
Peter C. Jones Cpetercj AT cpan DOT orgE>
|
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head1 COPYRIGHT
|
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Copyright (C) 2017,2018,2019 Peter C. Jones
|
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head1 LICENSE
|
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
290
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published
|
291
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License.
|
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
See L for more information.
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=cut
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
1; |