File Coverage

blib/lib/CAD/Mesh3D/STL.pm
Criterion Covered Total %
statement 68 68 100.0
branch 30 30 100.0
condition 8 8 100.0
subroutine 11 11 100.0
pod 2 2 100.0
total 119 119 100.0


line stmt bran cond sub pod time code
1             package CAD::Mesh3D::STL;
2 4     4   29 use warnings;
  4         8  
  4         156  
3 4     4   23 use strict;
  4         9  
  4         80  
4 4     4   20 use Carp;
  4         7  
  4         264  
5 4     4   77 use 5.010; # M::V::R requires 5.010, so might as well make use of the defined-or // notation :-)
  4         12  
6 4     4   28 use CAD::Format::STL qw//;
  4         8  
  4         72  
7 4     4   27 use CAD::Mesh3D qw/:create/;
  4         8  
  4         93  
8             our $VERSION = '0.003'; # auto-populated from CAD::Mesh3D
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   30 use Exporter 5.57 'import'; # v5.57 needed for getting import() without @ISA
  4         70  
  4         2456  
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   20 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 34 my $mesh = shift;
120 16         45 for($mesh) { # TODO = error handling
121             } # /check_mesh
122            
123             # process the filehandle / filename
124 16         31 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         33 for($fh) { # check_fh
127 16 100       65 croak sprintf('!ERROR! outputStl(mesh, fh, opt): requires file handle or name') unless $_;
128 15 100       72 $_ = \*STDOUT if /^STDOUT$/i;
129 15 100       51 $_ = \*STDERR if /^STDERR$/i;
130 15 100       48 if( 'GLOB' ne ref $_ ) {
131 3 100       22 $fn .= '.stl' unless $fn =~ /\.stl$/i;
132 3 100       409 open my $tfh, '>', $fn or croak sprintf('!ERROR! outputStl(): cannot write to "%s": %s', $fn, $!);
133 2         9 $_ = $tfh;
134 2         7 $doClose++; # will need to close the file
135             }
136             } # /check_fh
137            
138             # determine whether it's ASCII or binary
139 14   100     53 my $asc = shift || 0; check_asc: for($asc) {
  14         32  
140 14 100       59 $_ = 1 if /^(?:ASC(?:|II)|true)$/i;
141 14 100       45 $_ = 0 if /^(?:bin(?:|ary)|false)$/i;
142 14 100 100     83 croak sprintf('!ERROR! outputStl(): unknown asc/bin switch "%s"', $_) if $_ && /\D/;
143             } # /check_asc
144 13 100       36 binmode $fh unless $asc;
145            
146             #############################################################################################
147             # use CAD::Format::STL to output the STL
148             #############################################################################################
149 13         85 my $stl = CAD::Format::STL->new;
150 13         169 my $part = $stl->add_part("my part", @$mesh);
151            
152 13 100       1995 if($asc) {
153 5         20 $stl->save( ascii => $fh );
154             } else {
155 8         25 $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       3280 close($fh) if $doClose;
160 13         104 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 17 my ($file, $asc_or_bin) = @_;
205 6         15 my @pass_args = ($file);
206 6 100 100     53 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   35 no warnings 'unopened'; # avoid printing the warning; just looking for the definedness of $size
  4         7  
  4         1387  
  4         6  
212 4         65 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       60 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         16 croak "\ninputStl($file, '$asc_or_bin'): ERROR: unknown mode '$asc_or_bin'\n ";
228             }
229            
230 3         22 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         6586 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         127 my @facets = ();
239 3         8 foreach (@stlf) {
240 36         55 shift @$_; # ignore the normal vector
241 36         62 my @verts = ();
242 36         54 for my $v (@$_) {
243 108         409 push @verts, createVertex( @$v );
244             }
245 36         178 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. A patched copy of v0.2.1.001
278             is available through L,
279             or in the C folder of the distribution.
280            
281             =head1 AUTHOR
282            
283             Peter C. Jones Cpetercj AT cpan DOT orgE>
284            
285             =head1 COPYRIGHT
286            
287             Copyright (C) 2017,2018,2019,2020 Peter C. Jones
288            
289             =head1 LICENSE
290            
291             This program is free software; you can redistribute it and/or modify it
292             under the terms of either: the GNU General Public License as published
293             by the Free Software Foundation; or the Artistic License.
294            
295             See L for more information.
296            
297             =cut
298            
299             1;