File Coverage

blib/lib/CAD/Mesh3D/STL.pm
Criterion Covered Total %
statement 75 77 97.4
branch 31 32 96.8
condition 12 14 85.7
subroutine 13 13 100.0
pod 2 2 100.0
total 133 138 96.3


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