6 # $ctx->{CmdLog}= undef } not in defobj
7 # $ctx->{CmdLog}[]= [ command args ] } in defobj
8 # $ctx->{LocsMade}[]= $id
11 # $ctx->{Loc}{$id}{A} may be undef
12 # $ctx->{Trans}{X0} } transformation
13 # $ctx->{Trans}{Y0} } matrix
25 open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
33 my ($converter,$defaulter)=@_;
35 return &$defaulter unless @al;
37 $v= &$converter($spec);
38 dv('canf ','$spec',$spec, '$v',$v);
41 sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
42 sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
46 %units_len= qw(- mm mm 1 cm 10 m 1000);
47 %units_ang= qw(- d r 1); $units_ang{'d'}= 2*$pi / 360;
49 sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
50 sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
51 sub cva_absang ($) { input_absang(cva_ang($_[0])) }
55 $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/
56 or die "lexically invalid quantity";
58 $u=$ua->{'-'} unless length $u;
59 defined $ua->{$u} or die "unknown unit $u";
61 print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n";
66 die "invalid id" unless $sp =~ m/^[a-z][_0-9A-Za-z]*$/;
73 die "unknown $id" unless defined $ctx->{Loc}{$id};
76 foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; }
77 printf DEBUG "%s\n", $d;
84 die "duplicate $id" if exists $ctx->{Loc}{$id};
85 exists $ctx->{Loc}{$id}{X};
86 push @{ $ctx->{LocsMade} }, $id;
87 return $ctx->{Loc}{$id};
89 sub cva_cmd ($) { return cva_idstr($_[0]); }
92 return $sp if grep { $_ eq $sp } @$el;
93 die "invalid option (permitted: @$el)";
95 sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; }
99 $nl= can(\&cva_idnew);
102 ($nl->{X}, $nl->{Y})= input_abscoords($x,$y);
103 $nl->{A}= cano(\&cva_absang, undef);
107 $from= can(\&cva_idex);
108 $to= can(\&cva_idnew);
109 $len= can(\&cva_len);
110 $right= can(\&cva_len);
111 $turn= cano(\&cva_ang, 0);
112 $to->{X}= $from->{X} + $len * cos($from->{A}) + $right * sin($from->{A});
113 $to->{Y}= $from->{Y} + $len * sin($from->{A}) - $right * cos($from->{A});
114 $to->{A}= $from->{A} + $turn;
119 $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->');
123 return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i;
124 return $v if $v =~ m/^[0-9.]+/;
125 $v =~ s/[\\\']/\\$&/g;
129 sub dv1_kind ($$$$$$$) {
130 my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_;
132 return 0 if $ref ne $ref_exp;
134 foreach $ix (&$ixesfn) {
136 my ($v)= &$ixmapfn($ix);
137 #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n";
138 dv1($pfx,$expr.sprintf($ixfmt,evr($ix)),$v);
141 printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' ';
146 return 0 unless $debug;
147 my ($pfx,$expr,$v) = @_;
149 #print STDERR "dv1 >$pfx|$ref<\n";
151 printf DEBUG "%s%s= %s\n", $pfx,$expr, evr($v);
153 } elsif ($ref eq 'SCALAR') {
154 dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v);
157 $expr.='->' unless $expr =~ m/\]$|\}$/;
158 return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]',
159 sub { ($[ .. $#$v) },
160 sub { $v->[$_[0]] });
161 return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}',
162 sub { sort keys %$v },
163 sub { $v->{$_[0]} });
164 printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref;
176 sub loc_lin_comb ($$$) {
179 map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
180 # dv("loc_lin_comb ",'$a',$a,'$b',$b,'$p',$p,'$r',$r);
189 $psu_allwidth= 37.0/2;
196 # fixme optional marking
197 print "$_[0]" or die $!;
200 sub o_path_begin () {
202 $o_path_verb= 'moveto';
204 sub o_path_point ($) {
206 o(" $pt $o_path_verb\n");
207 $o_path_verb= 'lineto';
209 sub o_path_stroke ($) {
211 o(" $width setlinewidth stroke\n");
215 my ($a,$b,$width)=@_;
219 o_path_stroke($width);
222 sub psu_coords ($$$) {
223 my ($ends,$inunit,$across)=@_;
224 # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
225 # $across in mm, +ve to right.
227 $ea_zo{X}=$ea_zo{Y}=0;
228 foreach $zo (qw(0 1)) {
229 $prop= $zo ? $inunit : (1.0 - $inunit);
230 $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A}));
231 $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A}));
233 # dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across,
234 # '\\%ea_zo', \%ea_zo);
235 return $ea_zo{X}." ".$ea_zo{Y};
238 sub parametric_segment ($$$$$) {
239 my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_;
240 # makes $p (global) go from $p0 to $p1 ($p1>$p0)
241 # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj)
242 # $lenperp is the length of one unit p, ie the curve
243 # must have a uniform `density' in parameter space
244 # $calcfn is invoked with $p set and should return a loc
245 # (ie, ref to X =>, Y =>, A =>).
246 my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick);
247 return if defined $ctx->{InDefObj};
248 $ppu= $psu_ulen/$lenperp;
249 my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
250 my ($tickend)=($psu_allwidth - $psu_ticklen);
251 my ($tickpitch)=($psu_ulen / $psu_ticksperu);
252 my ($sleeperctr)=($psu_ulen*0.5);
253 my ($sleeperend)=($psu_sleeperlen*0.5);
254 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
255 for ($pa= $p0; $pa<$p1; $pa=$pb) {
257 $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
258 $p= $pb; $ends[1]= &$calcfn;
259 #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
260 #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
261 $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
264 o_path_point(psu_coords(\@ends,0,-$psu_allwidth));
265 o_path_point(psu_coords(\@ends,0,$psu_allwidth));
266 o_path_point(psu_coords(\@ends,$e,$psu_allwidth));
267 o_path_point(psu_coords(\@ends,$e,-$psu_allwidth));
268 o(" closepath clip\n");
269 foreach $side qw(-1 1) {
270 o_line(psu_coords(\@ends,0,$side*$psu_allwidth),
271 psu_coords(\@ends,1.5,$side*$psu_allwidth),
273 o_line(psu_coords(\@ends,0,$side*$railctr),
274 psu_coords(\@ends,1.5,$side*$railctr),
276 for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
277 o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth),
278 psu_coords(\@ends,$tick,$side*$tickend),
282 o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
283 psu_coords(\@ends,$sleeperctr,+$sleeperend),
290 my ($from,$to,$radius,$ctr,$beta,$ang,$how,$signum);
291 $from= can(\&cva_idex);
292 $to= can(\&cva_idnew);
293 printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n";
294 die "no ang" unless defined $from->{A};
295 $how= can(cvam_enum(qw(len upto ang uptoang parallel)));
296 if ($how eq 'len') { $len= can(\&cva_len); }
297 elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); }
298 elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); }
299 $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend
300 if ($radius eq 'Inf') {
301 print DEBUG "extend inf $len\n";
302 if ($how eq 'ang') { die "len of straight spec by angle"; }
303 if ($how eq 'upto') {
304 $len= ($upto->{X} - $from->{X}) * cos($from->{A})
305 + ($upto->{Y} - $from->{Y}) * sin($from->{A});
307 printf DEBUG "len $len\n";
308 $to->{X}= $from->{X} + $len * cos($from->{A});
309 $to->{Y}= $from->{Y} + $len * sin($from->{A});
310 $to->{A}= $from->{A};
311 parametric_segment(II, 0.0, 1.0, $len, sub {
312 loc_lin_comb($from, $to, $p);
315 print DEBUG "radius >$radius<\n";
316 $radius *= $ctx->{Trans}{AA};
317 $signum= $radius / abs($radius);
318 $ctr->{X}= $from->{X} + $radius * sin($from->{A});
319 $ctr->{Y}= $from->{Y} - $radius * cos($from->{A});
320 if ($how eq 'upto') {
321 $beta= atan2(-$signum * ($upto->{X} - $ctr->{X}),
322 $signum * ($upto->{Y} - $ctr->{Y}));
324 } elsif ($how eq 'parallel') {
327 } elsif ($how eq 'uptoang') {
328 $beta= input_absang($ang);
330 } elsif ($how eq 'len') {
331 $beta= $from->{A} - $signum * $len / abs($radius);
334 $beta= $from->{A} - $signum * $ang;
337 printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
338 $beta += $signum * 4.0 * $pi;
340 $delta= $beta - $from->{A};
341 last if $signum * $delta <= 0;
342 $beta -= $signum * $beta_interval * $pi;
344 printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
346 $to->{X}= $ctr->{X} - $radius * sin($beta);
347 $to->{Y}= $ctr->{Y} + $radius * cos($beta);
348 parametric_segment(II, 0.0, 1.0, abs($radius*$delta), sub {
349 my ($beta) = $from->{A} + $delta * $p;
350 return { X => $ctr->{X} - $radius * sin($beta),
351 Y => $ctr->{Y} + $radius * cos($beta),
355 printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n";
359 my ($id, $cmd, $loc);
360 $ctx->{LocsMade}= [ ];
361 $cmd= can(\&cva_cmd);
363 die "too many args" if @al;
364 foreach $id (@{ $ctx->{LocsMade} }) {
365 $loc= $ctx->{Loc}{$id};
366 o("% point $id $loc->{X} $loc->{Y} ".ang2deg($loc->{A})."\n");
375 return $_[0] * 180 / $pi;
377 sub input_absang ($) {
378 return $_[0] * $ctx->{Trans}{AA} + $ctx->{Trans}{A0};
380 sub input_abscoords ($$) {
382 ($in->{X}, $in->{Y})= @_;
383 foreach $o (qw(X Y)) {
384 $out->{$o}= $ctx->{Trans}{$o.0};
385 foreach $i (qw(X Y)) {
386 $out->{$o} += $ctx->{Trans}{"$i$o"} * $in->{$i};
389 return ($out->{X}, $out->{Y});
393 $ctx= { Trans => { X0 => 0.0, Y0 => 0.0,
394 XY => 0.0, YX => 0.0,
395 A0 => 0.0, AA => 1.0,
396 XX => 1.0, YY => 1.0 } }
401 $id= can(\&cva_idstr);
402 die "nested defobj" if $defobj_save;
403 die "repeated defobj" if exists $objs{$id};
407 $ctx->{InDefObj}= $id;
412 $id= $ctx->{InDefObj};
413 die "unmatched enddefobj" unless defined $id;
414 foreach $bit (qw(CmdLog Loc)) {
415 $objs{$id}{$bit}= $ctx->{$bit};
421 sub cmd_obj { cmd__obj(1); }
422 sub cmd_objflip { cmd__obj(-1); }
425 my ($obj_id, $ctx_save, $pfx);
426 $obj_id= can(\&cva_idstr);
427 $actual= can(\&cva_idex);
428 $formal_id= can(\&cva_idstr);
429 $obj= $objs{$obj_id};
430 dv("cmd__obj ",'$obj',$obj);
431 die "unknown obj $obj_id" unless $obj;
432 $formal= $obj->{Loc}{$formal_id};
433 die "unknown formal $formal_id" unless $formal;
436 o("% obj $obj_id\n");
437 $ctx->{Trans}{AA}= $flipsignum;
438 $ctx->{Trans}{A0}= $actual->{A} - $formal->{A}/$flipsignum;
439 $ctx->{Trans}{XX}= cos($ctx->{Trans}{A0});
440 $ctx->{Trans}{YY}= $flipsignum * cos($ctx->{Trans}{A0});
441 $ctx->{Trans}{XY}= $flipsignum * sin($ctx->{Trans}{A0});
442 $ctx->{Trans}{YX}= -$flipsignum * sin($ctx->{Trans}{A0});
443 ($xformcv,$yformcv)= input_abscoords($formal->{X}, $formal->{Y});
444 print STDERR ">$xformcv|$yformcv<\n";
445 $ctx->{Trans}{X0}= $actual->{X} - $xformcv;
446 $ctx->{Trans}{Y0}= $actual->{Y} - $yformcv;
449 foreach $c (@{ $obj->{CmdLog} }) {
451 next if $al[0] eq 'enddefobj';
455 $pfx= cano(\&cva_idstr,'');
457 foreach $id (keys %{ $ctx->{Loc} }) {
459 next if exists $ctx_save->{Loc}{$newid};
460 $pt= $ctx->{Loc}{$id};
461 $newpt= { A => input_absang($pt->{A}) };
462 ($newpt->{X}, $newpt->{Y})= input_abscoords($pt->{X}, $pt->{Y});
463 $ctx_save->{Loc}{$newid}= $newpt;
469 $ptscale= 72/25.4 / 5.0;
472 " $ptscale $ptscale scale\n");
478 chomp; s/^\s+//; s/\s+$//;
479 @al= split /\s+/, $_;
481 print DEBUG "=== @al\n";
482 push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog};