From 3b042dbb4e9cfb2932986851fe3e40a627fbfd59 Mon Sep 17 00:00:00 2001 From: ceade Date: Sat, 22 Mar 2025 13:29:00 +0100 Subject: [PATCH] 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 --- bin/dat2stl | 45 ++++++++++++++++--- lib/LDraw/Parser.pm | 103 +++++++++++++++++++++++++------------------- 2 files changed, 97 insertions(+), 51 deletions(-) diff --git a/bin/dat2stl b/bin/dat2stl index fed1312..29469af 100755 --- a/bin/dat2stl +++ b/bin/dat2stl @@ -18,6 +18,7 @@ GetOptions( 'json', 'nomodel', 'invert', + 'cache', ); if (!keys %{$opts}) { @@ -74,16 +75,35 @@ Takes an ldraw part .dat file as input and converts it into an STL file. --invert 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 } -my $parser = LDraw::Parser->new({ +my $parser_opts = { file => $opts->{file}, - $opts->{scale} ? ( scale => $opts->{scale} ) : (), - $opts->{ldrawdir} ? ( ldraw_path => $opts->{ldrawdir} ) : (), - $opts->{debug} ? ( debug => 1 ) : (), - $opts->{invert} ? ( invert => 1 ) : (), -}); +}; +if ($opts->{cache}) { + $parser_opts->{cache} = LDraw::Parser::Cache->new; +} +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; if ($opts->{json}) { @@ -99,4 +119,15 @@ if ($opts->{json}) { if ($opts->{nomodel}) { 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"); diff --git a/lib/LDraw/Parser.pm b/lib/LDraw/Parser.pm index cf91e6e..a4372c6 100644 --- a/lib/LDraw/Parser.pm +++ b/lib/LDraw/Parser.pm @@ -30,6 +30,7 @@ sub new { die "file required" unless $args->{file}; return bless({ file => $args->{file}, + cache => $args->{cache}, ldraw_path => $args->{ldraw_path} // '/usr/share/ldraw', scale => $args->{scale} // 1, mm_per_ldu => $args->{mm_per_ldu} // 0.4, @@ -38,6 +39,7 @@ sub new { d_indent => $args->{d_indent} // 0, ccw_winding => 1, _invertnext => 0, + triangles => [], }, $class); } @@ -364,24 +366,37 @@ sub parse_sub_file_reference { return; } - my $subparser = __PACKAGE__->new( { - file => $subpart_filename, - ldraw_path => $self->ldraw_path, - debug => $self->debug, - invert => $self->compute_inversion($mat), - d_indent => $self->d_indent + 2, - } ); - $subparser->parse; + my $triangles; + my $invert = $self->compute_inversion($mat); + if (defined $self->{cache}) { + $triangles = $self->{cache}->get($subpart_filename, $invert); + } + if (!$triangles) { + # Not using a cache, or cache miss + 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; - for my $triangle ( @{ $subparser->{triangles} } ) { - for my $vec ( @{ $triangle } ) { - my @new_vec = mat4xv3( $mat, $vec ); - $vec->[0] = $new_vec[0]; - $vec->[1] = $new_vec[1]; - $vec->[2] = $new_vec[2]; + for my $triangle (@{$triangles}) { + my $n_triangle = []; + for my $vec (@{$triangle}) { + push @{$n_triangle}, mat4xv3($mat, $vec); } - 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 $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 { @@ -554,32 +569,6 @@ sub _transvec { 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 { my ($self) = @_; @@ -591,11 +580,11 @@ sub stl_buffer { my ($p1, $p2, $p3) = map {_transvec($mm_per_ldu, $scale, $_)} @{$triangle}; my $n = $self->calc_surface_normal([$p1, $p2, $p3]); my $facet = { - normal => [map {sprintf('%0.4f', $_)} @{$n}], + normal => $n, vertexes => [], }; for my $vec (($p1, $p2, $p3)) { - push @{$facet->{vertexes}}, map {sprintf('%0.4f', $_)} @{$vec}; + push @{$facet->{vertexes}}, $vec; } 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;