#!/usr/bin/perl -w use POSIX; # Data structures: # $loc{$id}{X} # $loc{$id}{Y} # $loc{$id}{A} may be undef #$debug=1; open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!; if ($debug) { select(DEBUG); $|=1; select(STDOUT); $|=1; } sub canf ($$) { my ($converter,$defaulter)=@_; my ($spec,$v); return &$defaulter unless @al; $spec= shift @al; $v= &$converter($spec); dv('canf ','$spec',$spec, '$v',$v); return $v; } sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); } sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); } $pi= atan2(0,-1); %units_len= qw(- mm mm 1 cm 10 m 1000); %units_ang= qw(- d r 1); $units_ang{'d'}= 2*$pi / 360; sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); } sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); } sub cva_units ($$) { my ($sp,$ua)=@_; my ($n,$u,$r); $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/ or die "lexically invalid quantity"; ($n,$u)= ($1,$2); $u=$ua->{'-'} unless length $u; defined $ua->{$u} or die "unknown unit $u"; $r= $n * $ua->{$u}; print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n"; return $r; } sub cva_idstr ($) { my ($sp)=@_; die "invalid id" unless $sp =~ m/^[-0-9a-z]+$/; return $&; } sub cva_idex ($) { my ($sp,$id)=@_; my ($r,$d,$k); $id=cva_idstr($sp); die "unknown $id" unless defined $loc{$id}; $r= $loc{$id}; $d= "idex $id"; foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; } printf DEBUG "%s\n", $d; return $r; } sub cva_idnew ($) { my ($sp,$id)=@_; $id=cva_idstr($sp); die "duplicate $id" if exists $loc{$id}; exists $loc{$id}{X}; return $loc{$id}; } sub cva_cmd ($) { my ($sp)=@_; die "command lexically invalid" if $sp =~ m/[^-0-9a-z]/i; $sp =~ y/-/_/; return $sp; } sub cva__enum ($$) { my ($sp,$el)=@_; return $sp if grep { $_ eq $sp } @$el; die "invalid option (permitted: @$el)"; } sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; } sub cmd_mark { $mark= 1; &cmd__do; } sub cmd_abs { $nl= can(\&cva_idnew); $nl->{X}= can(\&cva_len); $nl->{Y}= can(\&cva_len); $nl->{A}= cano(\&cva_ang, undef); dv('cmd_abs ','$nl',$nl,'\\%loc',\%loc); } sub cmd_rel { $from= can(\&cva_idex); $to= can(\&cva_idnew); $len= can(\&cva_len); $right= can(\&cva_len); $turn= cano(\&cva_ang, 0); $to->{X}= $from->{X} + $len * cos($from->{A}) + $right * sin($from->{A}); $to->{Y}= $from->{Y} + $len * sin($from->{A}) - $right * cos($from->{A}); $to->{A}= $from->{A} + $turn; dv('cmd_abs ','$to',$to); } sub evreff ($) { my ($pfx) = @_; $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->'); } sub evr ($) { my ($v) = @_; return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i; return $v if $v eq ($v+0.0); $v =~ s/[\\\']/\\$&/g; return "'$v'"; } sub dv1 ($$$); sub dv1_kind ($$$$$$$) { my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_; my ($ix,$any); return 0 if $ref ne $ref_exp; $any=0; foreach $ix (&$ixesfn) { $any=1; my ($v)= &$ixmapfn($ix); #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n"; dv1($pfx,$expr.sprintf($ixfmt,evr($ix)),$v); } if (!$any) { printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' '; } 1; } sub dv1 ($$$) { return ;0 unless $debug; my ($pfx,$expr,$v) = @_; $ref= ref $v; #print STDERR "dv1 >$pfx|$ref<\n"; if (!$ref) { printf DEBUG "%s%s= %s\n", $pfx,$expr, evr($v); return; } elsif ($ref eq 'SCALAR') { dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v); return; } $expr.='->' unless $expr =~ m/\]$|\}$/; return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]', sub { ($[ .. $#$v) }, sub { $v->[$_[0]] }); return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}', sub { sort keys %$v }, sub { $v->{$_[0]} }); printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref; } sub dv { my ($pfx,@l) = @_; my ($expr,$v,$ref); while (@l) { ($expr,$v,@l)=@l; dv1($pfx,$expr,$v); } } sub loc_lin_comb ($$$) { my ($a,$b,$p) = @_; my ($q,$r) = 1.0-$p; map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A); # dv("loc_lin_comb ",'$a',$a,'$b',$b,'$p',$p,'$r',$r); return $r; } $psu_ulen= 4.5; $psu_edgelw= 0.5; $psu_ticklw= 0.1; $psu_ticksperu= 3; $psu_ticklen= 3.0; $psu_allwidth= 37.0/2; $psu_gauge= 9; $psu_sleeperlen= 17; $psu_sleeperlw= 15; $psu_raillw= 1.0; sub o ($) { # fixme optional marking print "$_[0]" or die $!; } sub o_path_begin () { o(" newpath\n"); $o_path_verb= 'moveto'; } sub o_path_point ($) { my ($pt)=@_; o(" $pt $o_path_verb\n"); $o_path_verb= 'lineto'; } sub o_path_stroke ($) { my ($width)=@_; o(" $width setlinewidth stroke\n"); } sub o_line ($$$) { my ($a,$b,$width)=@_; o_path_begin(); o_path_point($a); o_path_point($b); o_path_stroke($width); } sub psu_coords ($$$) { my ($ends,$inunit,$across)=@_; # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5); # $across in mm, +ve to right. my (%ea_zo); $ea_zo{X}=$ea_zo{Y}=0; foreach $zo (qw(0 1)) { $prop= $zo ? $inunit : (1.0 - $inunit); $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A})); $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A})); } # dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across, # '\\%ea_zo', \%ea_zo); return $ea_zo{X}." ".$ea_zo{Y}; } sub parametric_segment ($$$$$) { my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_; # makes $p (global) go from $p0 to $p1 ($p1>$p0) # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj) # $lenperp is the length of one unit p, ie the curve # must have a uniform `density' in parameter space # $calcfn is invoked with $p set and should return a loc # (ie, ref to X =>, Y =>, A =>). my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick); $ppu= $psu_ulen/$lenperp; my ($railctr)=($psu_gauge + $psu_raillw)*0.5; my ($tickend)=($psu_allwidth - $psu_ticklen); my ($tickpitch)=($psu_ulen / $psu_ticksperu); my ($sleeperctr)=($psu_ulen*0.5); my ($sleeperend)=($psu_sleeperlen*0.5); print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n"; for ($pa= $p0; $pa<$p1; $pa=$pb) { $pb= $pa + $ppu; $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn; $p= $pb; $ends[1]= &$calcfn; #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n"; #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n"; $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu; o(" gsave\n"); o_path_begin(); o_path_point(psu_coords(\@ends,0,-$psu_allwidth)); o_path_point(psu_coords(\@ends,0,$psu_allwidth)); o_path_point(psu_coords(\@ends,$e,$psu_allwidth)); o_path_point(psu_coords(\@ends,$e,-$psu_allwidth)); o(" closepath clip\n"); foreach $side qw(-1 1) { o_line(psu_coords(\@ends,0,$side*$psu_allwidth), psu_coords(\@ends,1.5,$side*$psu_allwidth), $psu_edgelw); o_line(psu_coords(\@ends,0,$side*$railctr), psu_coords(\@ends,1.5,$side*$railctr), $psu_raillw); for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) { o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth), psu_coords(\@ends,$tick,$side*$tickend), $psu_ticklw); } } o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend), psu_coords(\@ends,$sleeperctr,+$sleeperend), $psu_sleeperlw); o(" grestore\n"); } } sub cmd_extend { my ($from,$to,$radius,$ctr,$beta,$ang,$how,$signum); $from= can(\&cva_idex); $to= can(\&cva_idnew); printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n"; die "no ang" unless defined $from->{A}; $how= can(cvam_enum(qw(len upto ang uptoang parallel))); if ($how eq 'len') { $len= can(\&cva_len); } elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); } elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); } $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend if ($radius eq 'Inf') { print DEBUG "extend inf $len\n"; if ($how eq 'ang') { die "len of straight spec by angle"; } if ($how eq 'upto') { $len= ($upto->{X} - $from->{X}) * cos($from->{A}) + ($upto->{Y} - $from->{Y}) * sin($from->{A}); } printf DEBUG "len $len\n"; $to->{X}= $from->{X} + $len * cos($from->{A}); $to->{Y}= $from->{Y} + $len * sin($from->{A}); $to->{A}= $from->{A}; parametric_segment(II, 0.0, 1.0, $len, sub { loc_lin_comb($from, $to, $p); }); } else { print DEBUG "radius >$radius<\n"; $signum= $radius / abs($radius); $ctr->{X}= $from->{X} + $radius * sin($from->{A}); $ctr->{Y}= $from->{Y} - $radius * cos($from->{A}); if ($how eq 'upto') { $beta= atan2(-$signum * ($upto->{X} - $ctr->{X}), $signum * ($upto->{Y} - $ctr->{Y})); $beta_interval= 1.0; } elsif ($how eq 'parallel') { $beta= $upto->{A}; $beta_interval= 1.0; } elsif ($how eq 'uptoang') { $beta= $ang; $beta_interval= 2.0; } elsif ($how eq 'len') { $beta= $from->{A} - $signum * $len / abs($radius); $beta_interval= 2.0; } else { $beta= $from->{A} - $signum * $ang; $beta_interval= 2.0; } printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n"; $beta += $signum * 4.0 * $pi; for (;;) { $delta= $beta - $from->{A}; last if $signum * $delta <= 0; $beta -= $signum * $beta_interval * $pi; } printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n"; $to->{A}= $beta; $to->{X}= $ctr->{X} - $radius * sin($beta); $to->{Y}= $ctr->{Y} + $radius * cos($beta); parametric_segment(II, 0.0, 1.0, abs($radius*$delta), sub { my ($beta) = $from->{A} + $delta * $p; return { X => $ctr->{X} - $radius * sin($beta), Y => $ctr->{Y} + $radius * cos($beta), A => $beta } }); } printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n"; } sub cmd__do { $cmd= can(\&cva_cmd); &{ "cmd_$cmd" }; } $ptscale= 72/25.4 / 5.0; o("%!\n". " $ptscale $ptscale scale\n"); while (<>) { next if m/^\s*\#/; chomp; s/^\s+//; s/\s+$//; @al= split /\s+/, $_; next unless @al; print DEBUG "=== @al\n"; $mark= 0; cmd__do(); } dv('','\\%loc',\%loc);