| 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; |