1
0
mirror of https://github.com/kristov/ldraw2stl.git synced 2025-05-15 06:10:11 -07:00

Introduce an optional cache

With no cache:

    $ time bin/dat2stl --ldrawdir=ldraw --file ldraw/parts/11295.dat > 11295.stl
    real    0m1.857s
    user    0m1.764s
    sys     0m0.092s

    $ time bin/dat2stl --ldrawdir=ldraw --file ldraw/parts/11295.dat > 11295.stl
    real    0m1.834s
    user    0m1.786s
    sys     0m0.048s

With cache:

    $ time bin/dat2stl --cache --ldrawdir=ldraw --file ldraw/parts/11295.dat > 11295.stl
    real    0m1.084s
    user    0m1.044s
    sys     0m0.040s

    $ time bin/dat2stl --cache --ldrawdir=ldraw --file ldraw/parts/11295.dat > 11295.stl
    real    0m1.076s
    user    0m1.028s
    sys     0m0.048s
This commit is contained in:
ceade 2025-03-22 13:29:00 +01:00
parent 7965e9a890
commit 3b042dbb4e
2 changed files with 97 additions and 51 deletions

View File

@ -18,6 +18,7 @@ GetOptions(
'json', 'json',
'nomodel', 'nomodel',
'invert', 'invert',
'cache',
); );
if (!keys %{$opts}) { if (!keys %{$opts}) {
@ -74,16 +75,35 @@ Takes an ldraw part .dat file as input and converts it into an STL file.
--invert --invert
Invert the part. Used for debugging. Invert the part. Used for debugging.
--cache
Use a cache to avoid repeated geneneration of the same geometry. Many geometric
primitives are repeated (eg: a stud), and are simply translated into different
locations in the model. A combination of the sub-part name and the invert flag is
used to build a cache key to store the generated triangles for the sub-part. When
this is stable I will change this to --nocache so it's on by default.
END END
} }
my $parser = LDraw::Parser->new({ my $parser_opts = {
file => $opts->{file}, file => $opts->{file},
$opts->{scale} ? ( scale => $opts->{scale} ) : (), };
$opts->{ldrawdir} ? ( ldraw_path => $opts->{ldrawdir} ) : (), if ($opts->{cache}) {
$opts->{debug} ? ( debug => 1 ) : (), $parser_opts->{cache} = LDraw::Parser::Cache->new;
$opts->{invert} ? ( invert => 1 ) : (), }
}); if ($opts->{scale}) {
$parser_opts->{scale} = $opts->{scale};
}
if ($opts->{ldrawdir}) {
$parser_opts->{ldraw_path} = $opts->{ldrawdir};
}
if ($opts->{debug}) {
$parser_opts->{debug} = 1;
}
if ($opts->{invert}) {
$parser_opts->{invert} = 1;
}
my $parser = LDraw::Parser->new($parser_opts);
$parser->parse; $parser->parse;
if ($opts->{json}) { if ($opts->{json}) {
@ -99,4 +119,15 @@ if ($opts->{json}) {
if ($opts->{nomodel}) { if ($opts->{nomodel}) {
exit 0; exit 0;
} }
print $parser->to_stl; my $facets = $parser->stl_buffer;
printf("solid GiantLegoRocks\n");
for my $facet (@{$facets}) {
printf("facet normal %0.4f %0.4f %0.4f\n", @{$facet->{normal}});
printf(" outer loop\n");
for my $vertex (@{$facet->{vertexes}}) {
printf(" vertex %0.4f %0.4f %0.4f\n", @{$vertex});
}
printf(" endloop\n");
printf("endfacet\n");
}
printf("endsolid GiantLegoRocks\n");

View File

@ -30,6 +30,7 @@ sub new {
die "file required" unless $args->{file}; die "file required" unless $args->{file};
return bless({ return bless({
file => $args->{file}, file => $args->{file},
cache => $args->{cache},
ldraw_path => $args->{ldraw_path} // '/usr/share/ldraw', ldraw_path => $args->{ldraw_path} // '/usr/share/ldraw',
scale => $args->{scale} // 1, scale => $args->{scale} // 1,
mm_per_ldu => $args->{mm_per_ldu} // 0.4, mm_per_ldu => $args->{mm_per_ldu} // 0.4,
@ -38,6 +39,7 @@ sub new {
d_indent => $args->{d_indent} // 0, d_indent => $args->{d_indent} // 0,
ccw_winding => 1, ccw_winding => 1,
_invertnext => 0, _invertnext => 0,
triangles => [],
}, $class); }, $class);
} }
@ -364,24 +366,37 @@ sub parse_sub_file_reference {
return; return;
} }
my $subparser = __PACKAGE__->new( { my $triangles;
file => $subpart_filename, my $invert = $self->compute_inversion($mat);
ldraw_path => $self->ldraw_path, if (defined $self->{cache}) {
debug => $self->debug, $triangles = $self->{cache}->get($subpart_filename, $invert);
invert => $self->compute_inversion($mat), }
d_indent => $self->d_indent + 2, if (!$triangles) {
} ); # Not using a cache, or cache miss
$subparser->parse; my $subparser = LDraw::Parser->new({
file => $subpart_filename,
ldraw_path => $self->ldraw_path,
debug => $self->debug,
invert => $invert,
d_indent => $self->d_indent + 2,
(defined $self->{cache}) ? (cache => $self->{cache}) : (),
});
$subparser->parse;
$triangles = $subparser->{triangles};
if (defined $self->{cache}) {
$self->{cache}->put($subpart_filename, $invert, $triangles);
}
}
$self->{_invertnext} = 0; $self->{_invertnext} = 0;
for my $triangle ( @{ $subparser->{triangles} } ) { for my $triangle (@{$triangles}) {
for my $vec ( @{ $triangle } ) { my $n_triangle = [];
my @new_vec = mat4xv3( $mat, $vec ); for my $vec (@{$triangle}) {
$vec->[0] = $new_vec[0]; push @{$n_triangle}, mat4xv3($mat, $vec);
$vec->[1] = $new_vec[1];
$vec->[2] = $new_vec[2];
} }
push @{ $self->{triangles} }, $triangle; push @{$self->{triangles}}, $n_triangle;
#use Data::Dumper;
#warn Dumper($self->{triangles});
} }
} }
@ -513,7 +528,7 @@ sub mat4xv3 {
my $y_new = $b1 * $u + $b2 * $v + $b3 * $z + $b4; my $y_new = $b1 * $u + $b2 * $v + $b3 * $z + $b4;
my $z_new = $c1 * $u + $c2 * $v + $c3 * $z + $c4; my $z_new = $c1 * $u + $c2 * $v + $c3 * $z + $c4;
return ($x_new, $y_new, $z_new); return [$x_new, $y_new, $z_new];
} }
sub mat4determinant { sub mat4determinant {
@ -554,32 +569,6 @@ sub _transvec {
return [map {sprintf('%0.4f', $_ * $mm_per_ldu * $scale)} @{$vec}]; return [map {sprintf('%0.4f', $_ * $mm_per_ldu * $scale)} @{$vec}];
} }
sub to_stl {
my ($self) = @_;
my $scale = $self->scale || 1;
my $mm_per_ldu = $self->mm_per_ldu;
my $stl = "";
$stl .= "solid GiantLegoRocks\n";
for my $triangle (@{$self->{triangles}}) {
my ($p1, $p2, $p3) = map {_transvec($mm_per_ldu, $scale, $_)} @{$triangle};
my $n = $self->calc_surface_normal([$p1, $p2, $p3]);
$stl .= "facet normal " . join(' ', map {sprintf('%0.4f', $_)} @{$n}) . "\n";
$stl .= " outer loop\n";
for my $vec (($p1, $p2, $p3)) {
$stl .= " vertex " . join(' ', map {sprintf('%0.4f', $_)} @{$vec}) . "\n";
}
$stl .= " endloop\n";
$stl .= "endfacet\n";
}
$stl .= "endsolid GiantLegoRocks\n";
return $stl;
}
sub stl_buffer { sub stl_buffer {
my ($self) = @_; my ($self) = @_;
@ -591,11 +580,11 @@ sub stl_buffer {
my ($p1, $p2, $p3) = map {_transvec($mm_per_ldu, $scale, $_)} @{$triangle}; my ($p1, $p2, $p3) = map {_transvec($mm_per_ldu, $scale, $_)} @{$triangle};
my $n = $self->calc_surface_normal([$p1, $p2, $p3]); my $n = $self->calc_surface_normal([$p1, $p2, $p3]);
my $facet = { my $facet = {
normal => [map {sprintf('%0.4f', $_)} @{$n}], normal => $n,
vertexes => [], vertexes => [],
}; };
for my $vec (($p1, $p2, $p3)) { for my $vec (($p1, $p2, $p3)) {
push @{$facet->{vertexes}}, map {sprintf('%0.4f', $_)} @{$vec}; push @{$facet->{vertexes}}, $vec;
} }
push @facets, $facet; push @facets, $facet;
} }
@ -630,4 +619,30 @@ sub gl_buffer {
}; };
} }
package LDraw::Parser::Cache;
sub new {
my ($class) = @_;
return bless({_cache => {}}, $class);
}
sub get {
my ($self, $file, $invert) = @_;
my $key = sprintf("%s__%d", $file, $invert);
if (defined $self->{_cache}->{$key}) {
return $self->{_cache}->{$key};
}
return;
}
sub put {
my ($self, $file, $invert, $trianges) = @_;
my $key = sprintf("%s__%d", $file, $invert);
if (defined $self->{_cache}->{$key}) {
die sprintf("repeated put for %s", $key);
}
$self->{_cache}->{$key} = $trianges;
return;
}
1; 1;