mirror of
https://github.com/kristov/ldraw2stl.git
synced 2025-05-15 22:30:10 -07:00
Hopefully better support for Windows users
I was concatenating paths together using the unix slash `/`. This change used `File::Spec::catpath` instead, however I haven't been able to test it on Windows, and there may be other path issues. Also note: without debugging on the user doesn't see when files are not found (eg: due to path issues), so it silently creates an empty STL file.
This commit is contained in:
parent
24b67d4f9a
commit
9a0073a0c5
17
bin/dat2stl
17
bin/dat2stl
@ -15,14 +15,20 @@ GetOptions(
|
|||||||
'ldrawdir=s',
|
'ldrawdir=s',
|
||||||
'file=s',
|
'file=s',
|
||||||
'debug',
|
'debug',
|
||||||
|
'nomodel',
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $opts->{help} ) {
|
if (!keys %{$opts}) {
|
||||||
print_usage();
|
print_usage();
|
||||||
exit 0;
|
exit 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !$opts->{file} ) {
|
if ($opts->{help}) {
|
||||||
|
print_usage();
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$opts->{file}) {
|
||||||
print "ERROR: --file is required! (try --help)\n";
|
print "ERROR: --file is required! (try --help)\n";
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
@ -50,6 +56,10 @@ Takes an ldraw part .dat file as input and converts it into an STL file.
|
|||||||
--debug
|
--debug
|
||||||
Print debugging messages to STDERR
|
Print debugging messages to STDERR
|
||||||
|
|
||||||
|
--nomodel
|
||||||
|
Do not print the stl output. I am using this to run the script over all
|
||||||
|
parts to try to detect issues.
|
||||||
|
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,4 +71,7 @@ my $parser = LDraw::Parser->new( {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
$parser->parse;
|
$parser->parse;
|
||||||
|
if ($opts->{nomodel}) {
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
print $parser->to_stl;
|
print $parser->to_stl;
|
||||||
|
@ -2,6 +2,28 @@ package LDraw::Parser;
|
|||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
use File::Spec;
|
||||||
|
|
||||||
|
# A meta command is a comment line (type 0) followed by some magic. Unfortunately, being a
|
||||||
|
# comment line it can also be followed by regular old comments. Here are some common words
|
||||||
|
# that are the first word in a comment line, which are not meta commands we need to
|
||||||
|
# consider.
|
||||||
|
#
|
||||||
|
my @META_IGNORE = (
|
||||||
|
"Hi-Res",
|
||||||
|
"Name:",
|
||||||
|
"Author:",
|
||||||
|
"!LDRAW_ORG",
|
||||||
|
"!LICENSE",
|
||||||
|
"!HISTORY",
|
||||||
|
"Technic",
|
||||||
|
"Box",
|
||||||
|
"Cylinder",
|
||||||
|
"Peg",
|
||||||
|
"Rectangle",
|
||||||
|
"Stud",
|
||||||
|
);
|
||||||
|
my %MI = map {lc($_) => 1} @META_IGNORE;
|
||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
my ($class, $args) = @_;
|
my ($class, $args) = @_;
|
||||||
@ -52,13 +74,23 @@ use constant Y => 1;
|
|||||||
use constant Z => 2;
|
use constant Z => 2;
|
||||||
|
|
||||||
sub DEBUG {
|
sub DEBUG {
|
||||||
my ( $self, $message, @args) = @_;
|
my ($self, $message, @args) = @_;
|
||||||
return if !$self->debug;
|
return if !$self->debug;
|
||||||
my $indent = " " x $self->d_indent;
|
my $indent = " " x $self->d_indent;
|
||||||
if ( @args ) {
|
if (@args) {
|
||||||
$message = sprintf($message, @args);
|
$message = sprintf($message, @args);
|
||||||
}
|
}
|
||||||
print STDERR sprintf("%s%s\n", $indent, $message);
|
print STDERR sprintf("%sDEBUG: %s\n", $indent, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub WARN {
|
||||||
|
my ($self, $class, $message, @args) = @_;
|
||||||
|
my $indent = " " x $self->d_indent;
|
||||||
|
if (@args) {
|
||||||
|
$message = sprintf($message, @args);
|
||||||
|
}
|
||||||
|
$self->{_warn_classes}->{$class}++;
|
||||||
|
print STDERR sprintf("%sWARN: [%s] %s\n", $indent, $class, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub parse {
|
sub parse {
|
||||||
@ -81,8 +113,11 @@ sub parse_handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Lines start with a line type, which is an integer. The type defines the format of the
|
||||||
|
# rest of the line.
|
||||||
|
#
|
||||||
sub parse_line {
|
sub parse_line {
|
||||||
my ( $self, $line ) = @_;
|
my ($self, $line) = @_;
|
||||||
|
|
||||||
$line =~ s/^\s+//;
|
$line =~ s/^\s+//;
|
||||||
|
|
||||||
@ -107,23 +142,45 @@ sub parse_line {
|
|||||||
$self->parse_optional( $rest );
|
$self->parse_optional( $rest );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
warn "unhandled line type: $line_type";
|
$self->WARN("UNKNOWN_LINE_TYPE", "unhandled line type: %s", $line_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Comments can usually be ignored, except for the "BFC" meta command. This is used to
|
||||||
|
# define the winding order of triangles in the file (Back Face Culling).
|
||||||
|
#
|
||||||
|
# "Changing the winding setting will only affect the current file. It will not modify the
|
||||||
|
# winding of subfiles."
|
||||||
|
#
|
||||||
|
# I need to check this, because I think my logic might be flawed here.
|
||||||
|
#
|
||||||
sub parse_comment_or_meta {
|
sub parse_comment_or_meta {
|
||||||
my ( $self, $rest ) = @_;
|
my ($self, $rest) = @_;
|
||||||
my @items = split( /\s+/, $rest );
|
my @items = split(/\s+/, $rest);
|
||||||
my $first = shift @items;
|
my $first = shift @items;
|
||||||
|
|
||||||
if ( $first && $first eq 'BFC' ) {
|
if (!$first) {
|
||||||
$self->handle_bfc_command( @items );
|
return;
|
||||||
}
|
}
|
||||||
|
if ($first eq '//') {
|
||||||
|
# The form 0 // <comment> is preferred as the // marker clearly indicates that the
|
||||||
|
# line is a comment, thereby permitting parsers to stop processing the line. The
|
||||||
|
# form 0 <comment> is deprecated.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($MI{lc($first)}) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($first eq 'BFC') {
|
||||||
|
$self->handle_bfc_command(@items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#$self->WARN("UNKNOWN_META", "unknown meta command: %s", $first);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub handle_bfc_command {
|
sub handle_bfc_command {
|
||||||
my ( $self, @items ) = @_;
|
my ($self, @items) = @_;
|
||||||
|
|
||||||
my $first = shift @items;
|
my $first = shift @items;
|
||||||
|
|
||||||
@ -133,7 +190,7 @@ sub handle_bfc_command {
|
|||||||
}
|
}
|
||||||
if ($first eq 'INVERTNEXT') {
|
if ($first eq 'INVERTNEXT') {
|
||||||
$self->{_invertnext} = 1;
|
$self->{_invertnext} = 1;
|
||||||
$self->DEBUG('META: INVERTNEXT found while invert[%d]', $self->invert);
|
#$self->DEBUG('META: INVERTNEXT found while invert[%d]', $self->invert);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($first eq 'CERTIFY') {
|
if ($first eq 'CERTIFY') {
|
||||||
@ -147,10 +204,17 @@ sub handle_bfc_command {
|
|||||||
$self->DEBUG('META: Unknown BFC: %s', $items[0]);
|
$self->DEBUG('META: Unknown BFC: %s', $items[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A sub-file reference is a shape described in another file, placed in a certain location
|
||||||
|
# in the model. Note: this is recursive, so sub-files can contain references to other
|
||||||
|
# sub-files. The first number is a color (ignored) followed by a 3x3 translation matrix
|
||||||
|
# for how to position the sub-file shape within the model. This matrix encodes rotation
|
||||||
|
# and translation, and is converted here into a 4x4 matrix with "identity" set for the
|
||||||
|
# skew part of the matrix.
|
||||||
|
#
|
||||||
sub parse_sub_file_reference {
|
sub parse_sub_file_reference {
|
||||||
my ( $self, $rest ) = @_;
|
my ($self, $rest) = @_;
|
||||||
# 16 0 -10 0 9 0 0 0 1 0 0 0 -9 2-4edge.dat
|
# 16 0 -10 0 9 0 0 0 1 0 0 0 -9 2-4edge.dat
|
||||||
my @items = split( /\s+/, $rest );
|
my @items = split(/\s+/, $rest);
|
||||||
my $color = shift @items;
|
my $color = shift @items;
|
||||||
my $x = shift @items;
|
my $x = shift @items;
|
||||||
my $y = shift @items;
|
my $y = shift @items;
|
||||||
@ -165,10 +229,11 @@ sub parse_sub_file_reference {
|
|||||||
my $h = shift @items;
|
my $h = shift @items;
|
||||||
my $i = shift @items;
|
my $i = shift @items;
|
||||||
|
|
||||||
# / a d g 0 \ / a b c x \
|
# Possible "shapes" of the matrix. The correct is the one on the right (
|
||||||
# | b e h 0 | | d e f y |
|
# / a d g 0 \ / a b c x \
|
||||||
# | c f i 0 | | g h i z |
|
# | b e h 0 | | d e f y |
|
||||||
# \ x y z 1 / \ 0 0 0 1 /
|
# | c f i 0 | | g h i z |
|
||||||
|
# \ x y z 1 / \ 0 0 0 1 /
|
||||||
|
|
||||||
my $mat = [
|
my $mat = [
|
||||||
$a, $b, $c, $x,
|
$a, $b, $c, $x,
|
||||||
@ -177,29 +242,40 @@ sub parse_sub_file_reference {
|
|||||||
0, 0, 0, 1,
|
0, 0, 0, 1,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ( scalar( @items ) != 1 ) {
|
if (scalar(@items) != 1) {
|
||||||
warn "um, filename is made up of multiple parts (or none)";
|
warn "um, filename is made up of multiple parts (or none)";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $filename = lc( $items[0] );
|
my $filename = lc($items[0]);
|
||||||
$filename =~ s/\\/\//g;
|
$filename =~ s/\\/\//g;
|
||||||
|
|
||||||
my $p_filename = join( '/', $self->ldraw_path, 'p', $filename );
|
# This is the layout of the ldraw library:
|
||||||
my $hires_filename = join( '/', $self->ldraw_path, 'p/48', $filename );
|
#
|
||||||
my $parts_filename = join( '/', $self->ldraw_path, 'parts', $filename );
|
# ldraw
|
||||||
my $models_filename = join( '/', $self->ldraw_path, 'models', $filename );
|
# ├── models
|
||||||
|
# ├── p
|
||||||
|
# │ ├── 48
|
||||||
|
# │ └── 8
|
||||||
|
# └── parts
|
||||||
|
# ├── s
|
||||||
|
# └── textures
|
||||||
|
#
|
||||||
|
my $p_filename = File::Spec->catfile($self->ldraw_path, 'p', $filename);
|
||||||
|
my $hires_filename = File::Spec->catfile($self->ldraw_path, 'p', '48', $filename);
|
||||||
|
my $parts_filename = File::Spec->catfile($self->ldraw_path, 'parts', $filename);
|
||||||
|
my $models_filename = File::Spec->catfile($self->ldraw_path, 'models', $filename);
|
||||||
|
|
||||||
my $subpart_filename;
|
my $subpart_filename;
|
||||||
if ( -e $hires_filename ) {
|
if (-e $hires_filename) {
|
||||||
$subpart_filename = $hires_filename;
|
$subpart_filename = $hires_filename;
|
||||||
}
|
}
|
||||||
elsif ( -e $p_filename ) {
|
elsif (-e $p_filename) {
|
||||||
$subpart_filename = $p_filename;
|
$subpart_filename = $p_filename;
|
||||||
}
|
}
|
||||||
elsif (-e $parts_filename ) {
|
elsif (-e $parts_filename) {
|
||||||
$subpart_filename = $parts_filename;
|
$subpart_filename = $parts_filename;
|
||||||
}
|
}
|
||||||
elsif ( -e $models_filename ) {
|
elsif (-e $models_filename) {
|
||||||
$subpart_filename = $models_filename;
|
$subpart_filename = $models_filename;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -209,14 +285,18 @@ sub parse_sub_file_reference {
|
|||||||
|
|
||||||
my $det = mat4determinant($mat);
|
my $det = mat4determinant($mat);
|
||||||
my $invert = $self->invert;
|
my $invert = $self->invert;
|
||||||
$self->DEBUG('FILE: %s BEFORE det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
#$self->DEBUG('FILE: %s BEFORE det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
||||||
|
|
||||||
|
# This logic around the `invert`, `_invertnext` and matrix determinant needs to be
|
||||||
|
# figured out properly.
|
||||||
|
#
|
||||||
if ($det < 0) {
|
if ($det < 0) {
|
||||||
$invert = 1;
|
$invert = 1;
|
||||||
}
|
}
|
||||||
if ($self->{_invertnext}) {
|
if ($self->{_invertnext}) {
|
||||||
$invert = $invert ? 0 : 1;
|
$invert = $invert ? 0 : 1;
|
||||||
}
|
}
|
||||||
$self->DEBUG('FILE: %s AFTER det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
#$self->DEBUG('FILE: %s AFTER det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
||||||
|
|
||||||
my $subparser = __PACKAGE__->new( {
|
my $subparser = __PACKAGE__->new( {
|
||||||
file => $subpart_filename,
|
file => $subpart_filename,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user