1
0
mirror of https://github.com/kristov/ldraw2stl.git synced 2025-05-15 14:20:11 -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:
ceade 2025-03-19 23:41:12 +01:00
parent 24b67d4f9a
commit 9a0073a0c5
2 changed files with 124 additions and 31 deletions

View File

@ -15,8 +15,14 @@ GetOptions(
'ldrawdir=s',
'file=s',
'debug',
'nomodel',
);
if (!keys %{$opts}) {
print_usage();
exit 0;
}
if ($opts->{help}) {
print_usage();
exit 0;
@ -50,6 +56,10 @@ Takes an ldraw part .dat file as input and converts it into an STL file.
--debug
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
}
@ -61,4 +71,7 @@ my $parser = LDraw::Parser->new( {
} );
$parser->parse;
if ($opts->{nomodel}) {
exit 0;
}
print $parser->to_stl;

View File

@ -2,6 +2,28 @@ package LDraw::Parser;
use strict;
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 {
my ($class, $args) = @_;
@ -58,7 +80,17 @@ sub DEBUG {
if (@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 {
@ -81,6 +113,9 @@ 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 {
my ($self, $line) = @_;
@ -107,19 +142,41 @@ sub parse_line {
$self->parse_optional( $rest );
}
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 {
my ($self, $rest) = @_;
my @items = split(/\s+/, $rest);
my $first = shift @items;
if ( $first && $first eq 'BFC' ) {
$self->handle_bfc_command( @items );
if (!$first) {
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 {
@ -133,7 +190,7 @@ sub handle_bfc_command {
}
if ($first eq 'INVERTNEXT') {
$self->{_invertnext} = 1;
$self->DEBUG('META: INVERTNEXT found while invert[%d]', $self->invert);
#$self->DEBUG('META: INVERTNEXT found while invert[%d]', $self->invert);
return;
}
if ($first eq 'CERTIFY') {
@ -147,6 +204,13 @@ sub handle_bfc_command {
$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 {
my ($self, $rest) = @_;
# 16 0 -10 0 9 0 0 0 1 0 0 0 -9 2-4edge.dat
@ -165,6 +229,7 @@ sub parse_sub_file_reference {
my $h = shift @items;
my $i = shift @items;
# Possible "shapes" of the matrix. The correct is the one on the right (
# / a d g 0 \ / a b c x \
# | b e h 0 | | d e f y |
# | c f i 0 | | g h i z |
@ -184,10 +249,21 @@ sub parse_sub_file_reference {
my $filename = lc($items[0]);
$filename =~ s/\\/\//g;
my $p_filename = join( '/', $self->ldraw_path, 'p', $filename );
my $hires_filename = join( '/', $self->ldraw_path, 'p/48', $filename );
my $parts_filename = join( '/', $self->ldraw_path, 'parts', $filename );
my $models_filename = join( '/', $self->ldraw_path, 'models', $filename );
# This is the layout of the ldraw library:
#
# ldraw
# ├── 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;
if (-e $hires_filename) {
@ -209,14 +285,18 @@ sub parse_sub_file_reference {
my $det = mat4determinant($mat);
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) {
$invert = 1;
}
if ($self->{_invertnext}) {
$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( {
file => $subpart_filename,