File Coverage

blib/lib/CNC/Cog.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #Written by Mark Winder, mark.winder4@btinternet.com
2             # Copyright 2004,2005
3 1     1   24185 use GD;
  0            
  0            
4             use strict;
5            
6             package Profile;
7             use vars qw($VERSION @ISA @EXPORT);
8             $VERSION=0.061;
9            
10             my $pi=4.0 * atan2(1, 1);
11             # profile
12             sub new
13             {
14             my ($class)=shift(@_);
15             my ($p)={};
16            
17             if (ref($_[0]) eq $class)
18             {
19             $p=$_[0];
20             return $p->copy;
21             }
22            
23            
24            
25             $p->{points}=[];
26             $p->{comments}=[];
27            
28             while (@_)
29             {
30             push(@{$p->{points}},[shift(@_),shift(@_)]);
31             }
32             $#{$p->{comments}}=$#{$p->{points}};
33             return bless $p , $class;
34             }
35             #profile
36             sub comment
37             {
38             my($p,@c)=@_;
39             my $n=@{$p->{points}};
40             $p->{comments}->[$n].=join("\n",@c); # This creates a new comment entry, now @comments=@points+1;
41             }
42             # profile
43             sub copy
44             {
45             my ($p)=@_;
46             my $q={};
47            
48             $q->{points}=[];
49             $q->{comments}=[];
50            
51             for (@{$p->{points}})
52             {
53             push(@{$q->{points}},[@$_]);
54             }
55             @{$q->{comments}}=@{$p->{comments}};
56            
57             return bless $q, ref($p);
58             }
59            
60             # profile
61             # return all points, 1 point or a range of points as a 1 dimentional array (alternate x-y pairs. )
62             # eg allowed a,b are -3,-1 for last 3 points, oldest first.
63             # -1 for last point
64             # 0 for 1st point
65             # nothing for all points
66            
67             sub points
68             {
69             my ($p,$a,$b)=@_;
70            
71             if (!defined $a and !defined $b)
72             {
73             return map { ($$_[0],$$_[1]) } @{$p->{points}};
74             }
75             elsif (!defined $b)
76             {
77             return map { ($$_[0],$$_[1]) } ${$p->{points}}[$a];
78             }
79             else
80             {
81             my @pp=@{$p->{points}};
82             @pp=@pp[$a,$b];
83             return map { ($$_[0],$$_[1]) } @pp;
84             }
85             }
86            
87             # profile
88             # Take 2 (for a move) or 4 (for an arc) points and add to end of the profile
89             # can also take an existing profile, adding it to the 1st.
90             sub ppush
91             {
92             my ($p)=shift(@_);
93             if (ref($_[0]) ne '')
94             {
95             my $q=shift(@_);
96             my $sp=@{$p->{points}};
97            
98             $#{$q->{comments}}=$#{$q->{points}};
99            
100             my @comments=@{$q->{comments}};
101             if ($#{$p->{comments}}>$#{$p->{points}}) # if we called comment in advance, we need to add the last comment on to 1st comment of new one.
102             {
103             $p->{comments}->[-1]=$p->{comments}->[-1].$q->{comments}->[0];
104             shift (@comments);
105             }
106             push(@{$p->{comments}},@comments);
107             push(@{$p->{points}},@{$q->{points}});
108             }
109             else
110             {
111             push(@{$p->{points}},[@_]);
112             }
113             $#{$p->{comments}}=$#{$p->{points}}; # if a comment has been added, this has no effect if 1 point added, otherwise it adds empty entries to the comments array.
114             return $p;
115             }
116            
117             #profile
118             sub shift
119             {
120             my ($p)=shift(@_);
121            
122             shift(@{$p->{comments}}); # Throw away this comment.
123             my $pp=shift @{$p->{points}};
124             return @$pp;
125             }
126            
127             # profile
128             # insert a point backwards into the profile. n=1 means between last point and point before.
129             sub insertback
130             {
131             my ($p,$n,$x,$y,$r,$ccw)=@_; # set n to zero makes same as push, n=1 means 1 before latest point.
132             $#{$p->{comments}}=$#{$p->{points}};
133             splice(@{$p->{points}},@{$p->{points}}-$n,0,[$x,$y,$r,$ccw]);
134             splice(@{$p->{comments}},@{$p->{comments}}-$n,0,"");
135            
136             return $p;
137             }
138            
139             # profile
140             # This function deduplicates repeated points. These can arrise for example if you mirror or rotate a profile and then add them
141             # together. Because rounding errors can and do arrise, we need to have a fudge factor here that is the small amount
142             # used in comparison. Anything smaller than this is considered the same. I may have set this a little small,
143             # but it worked for me. Change $d if you need to.
144             #
145             # The reson duplicates are bad is that it confuses reference to particular points, eg 5 points before the
146             # present one when doing things like smoothing.
147             sub dedupe
148             {
149             my ($p)=@_;
150            
151             my $d=1e-10; # How small before point is considered a duplicate ?
152            
153             my @points;
154             my @comments;
155            
156             my $old;
157            
158             my $i=0;
159             for (@{$p->{points}})
160             {
161            
162             if (abs($$_[0]-$$old[0])>$d or abs($$_[1]-$$old[1])>$d or !$old)
163             {
164             push(@points,$_);
165             push(@comments,$p->{comments}->[$i]);
166             }
167             else
168             { # point is a duplicate, not including.
169             $comments[-1].=$p->{comments}->[$i];
170             }
171             $i++;
172             $old=$_;
173             }
174             # @{$p->{points}}=@points;
175             $p->{points}=\@points;
176             $p->{comments}=\@comments;
177             return $p;
178             }
179            
180             # profile.
181             # reverse the cut direction of a profile. This also cleverly attempts to move both arc paramters and comments
182             # around to take account of the new cut order, so that the comments still get printed out in the
183             # right place. Bit academic really as if you are reversing round a bend comments about whats round the corner
184             # are probably misguided any way!
185             sub reverse
186             {
187             my ($pp)=@_;
188            
189             $#{$pp->{comments}}=$#{$pp->{points}};
190             my $p=$pp->copy;
191            
192             @{$p->{points}}=reverse @{$p->{points}};
193             @{$p->{comments}}=reverse @{$p->{comments}};
194            
195             my @arc1=();
196             my @arc2=();
197             for (@{$p->{points}})
198             {
199             @arc2=($$_[2],$$_[3]==0);
200             @$_[2,3]=@arc1;
201             @arc1=@arc2;
202             }
203            
204            
205             return $p;
206             }
207             #profile
208             # replace the latest point or some point before it.
209             sub replaceback
210             {
211             my ($p,$n,$x,$y,$r,$ccw)=@_; # set n to zero to replace the latest point
212             splice(@{$p->{points}},@{$p->{points}}-$n-1,1,[$x,$y,$r,$ccw]);
213             return $p;
214             }
215             #profile
216             sub print # formatted debug
217             {
218             my ($p)=@_;
219             $"=",";
220             my $i=0;
221             for (@{$p->{points}})
222             {
223             my $c=$p->{comments}->[$i];
224             $c.="\n" if ($c and $c!~ m/\n$/s );
225             print "**** $c" if ($c);
226             print "[ @$_ ]\n";
227             $i++;
228            
229             }
230             my $c=$p->{comments}->[$i];
231             $c.="\n" if ($c and $c!~ m/\n$/s );
232             print "**** $c" if ($c);
233            
234             return $p;
235             }
236            
237            
238             # just so as I remember matric rotations are as follows:
239             # cw rotation, cos a sin a
240             # -sin a cos a
241             #
242             # ccw rotation cos a -sin a
243             # sin a cos a
244            
245            
246            
247             # Gives ccw rotation about the supplied point by an angle $a in radians
248             # if xc,$yc ommitted, rotation about origin.
249             # profile
250             sub rotate
251             {
252             my ($pp,$a,$xc,$yc)=@_;
253            
254             my $p=$pp->copy;
255            
256             for (@{$p->{points}})
257             {
258             @$_=($$_[0]-$xc,$$_[1]-$yc,$$_[2],$$_[3]);
259            
260             }
261            
262             for (@{$p->{points}})
263             {
264             @$_=($$_[0]*cos($a)-$$_[1]*sin($a),$$_[0]*sin($a)+$$_[1]*cos($a),$$_[2],$$_[3]);
265            
266             }
267            
268            
269             for (@{$p->{points}})
270             {
271             @$_=($$_[0]+$xc,$$_[1]+$yc,$$_[2],$$_[3]);
272             }
273             return $p;
274             }
275            
276             # mirror about the y axis
277             # profile
278             sub mirrory
279             {
280             my ($pp)=@_;
281            
282             my $p=$pp->copy;
283            
284             for (@{$p->{points}})
285             {
286             @$_=(-$$_[0],$$_[1],$$_[2],$$_[3]==0);
287            
288             }
289            
290             return $p;
291             }
292            
293             # A translation, all points moved by this vector.
294             # profile
295             sub move
296             {
297             my ($pp,$xc,$yc)=@_;
298            
299             my $p=$pp->copy;
300            
301             for (@{$p->{points}})
302             {
303             @$_=($$_[0]+$xc,$$_[1]+$yc,$$_[2],$$_[3]);
304            
305             }
306             return $p;
307             }
308            
309             # similar to smooth, which joints a line to an arc with another arc, this function
310             # takes two lines that join at an angle and generates an arc that joins them of radius r,
311             # chopping out a section of each line where the arc goes. Idea is to make a smooth transition
312             # so that on concave cuts it is possible to cut with a circular cutter of more than
313             # infinitely small diameter! and on conves cuts it just looks nicer, or can do.
314             # This could be incorporated into smooth so that smooth works even when lines rather
315             # than arcs are given, but havnt done that.
316             # profile
317             sub linesmooth
318             {
319             my ($p,$r,$n)=@_;
320            
321            
322             # assume 3 points l1,l2,l3, where l2 is the nth point in the profile, 0 means penultimate point though.
323             # want to insert circular section such that circle radius r is tangential.
324            
325             # simple trig gives distance of the join points along each line as
326             # l=r/tan k where k is half the angle l1 l2 l3.
327             # calulate this as 0.5 * ( 180 -a1 -a3 ) where tan a1 = (x2-x1)/(y2-y1), tan a3=(x3-x2)/(y3-y2)
328             # from l we calculate the 2 new points parametrically sliding from l2 to l1, and l2 to l3.
329            
330             my (@points)=@{$p->{points}};
331            
332            
333             $n=$#points if ($n==0);
334            
335             my (@p1)=@{$points[$n-1]};
336             my (@p2)=@{$points[$n]};
337             my (@p3)=@{$points[$n+1]};
338            
339             my (@extra)=(abs($r));
340            
341             my $k=0.5*($pi-atan2(abs($p2[1]-$p1[1]),abs($p2[0]-$p1[0]))-atan2(abs($p3[1]-$p2[1]),abs($p3[0]-$p2[0])));
342            
343             $extra[1]=($r>0);
344             $r=abs($r);
345            
346            
347             my $l=$r*cos($k)/sin($k);
348            
349            
350             # Find the start of arc by parametric substitution into p2,p1
351             my $ll=sqrt(($p2[0]-$p1[0])**2+($p2[1]-$p1[1])**2); # length of this line;
352            
353             my $sax=$p2[0]*($ll-$l)/$ll+$p1[0]*($l/$ll);
354             my $say=$p2[1]*($ll-$l)/$ll+$p1[1]*($l/$ll);
355            
356             # Find the end of arc by parametric substitution into line p2,p3
357             $ll=sqrt(($p3[0]-$p2[0])**2+($p3[1]-$p2[1])**2); # length of this line;
358             my $eax=$p2[0]*($ll-$l)/$ll+$p3[0]*($l/$ll);
359             my $eay=$p2[1]*($ll-$l)/$ll+$p3[1]*($l/$ll);
360            
361             $p->replaceback(@points-$n-1,$sax,$say);
362             $p->insertback(@points-$n-1,$eax,$eay,@extra);
363             return $p;
364             }
365             # see wheel smooth.
366             # profile
367             # p the profile, smooth3e the last 3 points, which should be line then arc. Smooth with circle radius r.
368             # x, y is the center of the arc. joining last 2 points in profile
369             # profile
370             sub smooth
371             {
372             my ($p,$r,$x,$y)=@_;
373             $"=',';
374             my $c;
375            
376             my $w=Wheel->new(); # actually, we know that no characteristics from wheel are used here, so use a new one.
377             # assume you call with last point is an arc,
378            
379             $p=$p->move(-$x,-$y);
380            
381             my @ps=@{$p->{points}};
382            
383             my @last=grep { defined } @{$ps[-1]};
384             my @l2 =grep { defined } @{$ps[-2]};
385             my @l1 =grep { defined } @{$ps[-3]};
386            
387             my $last="last";
388             my $l2='l2';
389             my $swap=0;
390            
391             my @extra;
392            
393             if (@last==2) # need to swap order.
394             {
395             my @tmp=@l1; @l1=@last; @last=@tmp;
396             @last[2,3]=@l2[2,3];
397             @extra=@l2[2,3];
398             @l2[2,3]=();
399             @l2=grep{ defined} @l2;
400             @last=grep { defined } @last;
401             $l2='last';
402             $last='l2';
403             $swap=1;
404             }
405            
406             die "$last point needs to be arc is @last" if (@last==2);
407             die "$l2 must be line , is @l2" if (@l2 != 2);
408            
409             @l1=@l1[0,1]; # not intrested in arc or line.
410            
411             my $a1=180*atan2($last[1]-$l2[1],$last[0]-$l2[0])/$pi;
412             my $a2=180*atan2($l2[1]-$l1[1],$l2[0]-$l1[0])/$pi;
413            
414             $c=0;
415             $c=1 if (($a1-$a2+360+180)%360-180<0); # This works round bugs. reverses circle.
416            
417             my (undef,undef,$sax,$say,$eax,$eay)= $w->smooth(@l1,@l2,$last[2],-$r*(1-$c-$c) );
418            
419             if ($swap)
420             {
421             my @tmp=@l1; @l1=@last; @last=@tmp;
422             @last[2,3]=();
423             ($sax,$say,$eax,$eay)=($eax,$eay,$sax,$say);
424             }
425            
426             $p->replaceback(1,$sax,$say,@extra);
427             $p->insertback(1,$eax,$eay,$r,($swap==0)!=$c);
428             $p=$p->move($x,$y);
429             return $p;
430             }
431            
432             # profile
433             #
434             # move 1 point from start to finish. Used when we want to start cutting a profile in better place.
435             # move any comments also.
436             sub movestartfin
437             {
438             my ($p)=@_;
439            
440             $#{$p->{comments}}=$#{$p->{points}};
441             my @points=@{$p->{points}};
442             my @comments=@{$p->{comments}};
443            
444            
445             @points=(@points[1..$#points,0]);
446             @comments=(@comments[1..$#points,0]);
447            
448             # @{$p->{points}}=@points;
449             $p->{points}=\@points;
450             $p->{comments}=\@comments;
451            
452             return $p;
453            
454             }
455            
456             # profile
457             #
458             # move 1 point from finish to start. Used when we want to start cutting a profile in better place.
459             # move any comments also.
460             sub movefinstart
461             {
462             my ($p)=@_;
463            
464             $#{$p->{comments}}=$#{$p->{points}};
465             my @points=@{$p->{points}};
466             my @comments=@{$p->{comments}};
467            
468            
469             @points=(@points[-1..$#points-1]);
470             @comments=(@comments[-1..$#points-1]);
471            
472             $p->{points}=\@points;
473             $p->{comments}=\@comments;
474            
475             return $p;
476            
477             }
478            
479             # profile
480             sub plot
481             {
482            
483             my ($p,$g,$z,$passes,$passdepth,$open)=@_;
484            
485             my @points=@{$p->{points}};
486             my @point1=@{$points[0]};
487             my @point2=@{$points[1]};
488             my $point;
489            
490             my $zup=0.05;
491             my $zdown=$z;
492            
493             $z=$zup;
494            
495             $g->gmove('z',$z);
496             $g->gmove('x',$point1[0],'y',$point1[1]);
497            
498             my $i=0;
499            
500             for my $pass ( 1..$passes)
501             {
502             # $z+=$passdepth;
503             $i=0;
504             for $point (@points,\@point1)
505             {
506            
507             if ($point==\@point1 and $open ) # last point, return to start. Open if set, allows a non-closed profile to be cut. Debug ?
508             {
509             $z=0.1;
510             $g->gmove('z',$z);
511             }
512            
513             $g->gcomment("Pass $pass of $passes ".$p->{comments}->[$i]) if ($p->{comments}->[$i]);
514            
515             if ($$point[2] and $$point[3] and $i!=0) # radius of curveture, ccw , not cw for arc.
516             {
517             $g->garcccw('z',$z,'x',$$point[0],'y',$$point[1],'r',$$point[2]);
518             }
519             elsif ($$point[2] and $i!=0 ) # for cw arc
520             {
521             $g->garccw('z',$z,'x',$$point[0],'y',$$point[1],'r',$$point[2]);
522             }
523             else # for move. First point is always a move, since we're there already in fact.
524             {
525             $g->gmove('z',$z,'x',$$point[0],'y',$$point[1]);
526             }
527            
528             $z=$zdown+$passdepth*$pass;
529             $i++;
530            
531             }
532             }
533             # finally we repeat point 2 becasue z was being phased in during the this point and we want full depth.
534             $point=\@point2; $i=1;
535             $g->gcomment("Pass final ".$p->{comments}->[$i]) if ($p->{comments}->[$i]);
536            
537             if ($$point[2] and $$point[3] and $i!=0) # radius of curveture, ccw , not cw for arc.
538             {
539             $g->garcccw('z',$z,'x',$$point[0],'y',$$point[1],'r',$$point[2]);
540             }
541             elsif ($$point[2] and $i!=0 ) # for cw arc
542             {
543             $g->garccw('z',$z,'x',$$point[0],'y',$$point[1],'r',$$point[2]);
544             }
545             else # for move. First point is always a move, since we're there already in fact.
546             {
547             $g->gmove('z',$z,'x',$$point[0],'y',$$point[1]);
548             }
549            
550            
551             $g->gmove('z',0.05);
552             }
553             #############################################
554             # end of package profile
555             #############################################
556            
557             # This holds the information for a a single wheel: a pinion or spur gear.
558             package Wheel;
559            
560             use vars qw($VERSION @ISA @EXPORT);
561             $VERSION=0.061;
562            
563             my $mm;
564             $mm=$mm=1.0/25.4;; # 1mm is this inches;
565            
566             sub hole
567             {
568             my ($w,$s,$x,$y)=@_;
569            
570             if (!defined $x and !defined $y) # assume center
571             {
572             $w->{holesize}=$s;
573             }
574             else
575             {
576             die "x/y parameters to hole function not yet implemented. ";
577             my $h=Hole->new($w->{cuttersize},$w->{passes},$w->{passdepth},$s/2);
578             my @holes;
579             $w->{holes}=\@holes if (!$w->{holes});
580             push(@{$w->{holes}},$h);
581             }
582             }
583             # wheel
584             sub passes
585             {
586             my ($w)=@_;
587            
588             return $w->{passes};
589             }
590             # wheel
591             # set the module and number of teeth.
592             sub new
593             {
594             my ($s,$m,$n)=@_;
595             $s={};
596             $s->{n}=$n;
597             $s->{m}=$m;
598            
599             return bless $s;
600             }
601             # Trepan: - to cut a hole in. If you want holes in your wheels, call this functions. ( Ie you want spokes!)
602             # Known bugs: (1) As you increase the number of spokes, roe, spoke width or width at base factor or decreasethe boss radius there comes a time when there is
603             # "not enough room round the boss for the spokes. This is not handles well, instead of doing a little arc which is the circumference of the booss
604             # what happens is you get most of a circle in the other direction. Catastrophic of course.
605             sub trepan
606             {
607             my ($w, # pointer to self, a wheel.
608             $spoken, # number of spokes
609             $wos, # total width of spokes in inches
610             $bsf, # boss radius as a factor of pitch radius (dimentionless) : Now absolute in inches.
611             $rsf, # rim size in inches.
612             $roe, # radius of window edge (in inches, the curved radius of the join between a spoke and the rim or boss
613             # not that this must be more than the cutter size or you cant cut it! This is radius, not diameter.
614             $wobf, # width at base factor, > 1 for wider spoke base. Tapered spokes anyone ?
615             $srf # spoke rotation factor rotates spoke position this proportion of a full rotation
616             # on outer rim
617             # values 0 to 0.1 give good results.
618             )=@_;
619            
620             # ($spoken,$wos,$bsf,$rsf,$roe,$wobf)=(6,0.5,0.25,0.35,0.075,1.0);
621            
622             # for now we just store the values in the wheel.
623             $w->{spoken}=$spoken;
624             $w->{wos}=$w->dim($wos);
625             $w->{bsf}=$w->dim($bsf);
626             $w->{rsf}=$w->dim($rsf);
627             $w->{roe}=$w->dim($roe);
628             $w->{wobf}=$wobf;
629             $w->{srf}=$srf;
630             }
631            
632             sub bossindent
633             {
634             my ($c,$sized,$passdepth,$passes,$feed)=@_;
635             # arguments are cog, diameter of indent, depth for each pass and number of passes, optional $feedrate
636             # note that these are different to the main cutting ones because it doesnt go all the way through material for a start.
637            
638             $c->{bi_passdepth}=$c->dim($passdepth);
639             $c->{bi_passes}=$passes;
640             $c->{bi_feed}=$feed;
641             $c->{bi_sized}=$c->dim($sized); # diameter
642            
643             }
644            
645             # private function #
646             sub cutbossindent
647             {
648             my ($c,$g,$x,$y)=@_;
649             return if (!$c->{bi_sized});
650            
651             $c->{bi_feed}||=$g->{feed};
652             $g->grapid('z',0.1);
653             $g->grapid('x',$x,'y',$y);
654            
655             my $sized=$c->{bi_sized}-$c->{cuttersize};
656            
657             my $pass=0;
658             my ($step)=$c->{cuttersize}/2;
659             $step==0 and die "Need a cuttersize set.";
660            
661             my $z=0;
662             while ($pass++<$c->{bi_passes})
663             {
664             #print "pass=$pass cpasses =".$c->{bi_passes}."\n";
665            
666             $z+=$c->{bi_passdepth};
667             $g->gcomment("Cutting indent pass $pass of ".$c->{bi_passes});
668             $g->gmove('x',$x,'y',$y,'z',$z,'f',$c->{bi_feed});
669             my $r=0;
670             while ($r+$step<0.5*$sized)
671             {
672             $r+=$step;
673             $g->gcomment("r is $r");
674             $g->gmove('x',$x+$r,'y',$y);
675             $g->garccw('x',$x-$r,'y',$y,'r',$r);
676             $g->garccw('x',$x+$r,'y',$y,'r',$r);
677             }
678            
679             $step=0.5*$sized-$r;
680             if ($step>0)
681             {
682             $r+=$step;
683             $g->gcomment("final r is $r");
684             $g->gmove('x',$x+$r,'y',$y);
685             $g->garccw('x',$x-$r,'y',$y,'r',$r);
686             $g->garccw('x',$x+$r,'y',$y,'r',$r);
687             }
688             }
689            
690             $g->grapid('z',0.1);
691             }
692            
693             # actually cut the requested trepanning scheme
694             # note that the nasty smooth algorithmn only works if the circle is centered on origin 0,0.
695             # for the moment, easy way to correct this is to do all calculations assuming origin based wheel
696             # then offset just before plotting with xi yi, the initial position, which is the real wheel center.
697             # private function #
698             sub cuttrepan
699             {
700             my ($cp,
701             $gp, $xi,$yi,$zi) =@_;
702            
703             my ($spoken, # number of spokes
704             $wos, # total width of spokes
705             # $bsf, # boss radius as a factor of pitch radius
706             $bossradius, # now absolute in inches
707             $rsf, # rim size factor as proportion of pitch radius. # Now absolute in inches, size of rim
708             $roe, # radius of window edge
709             $wobf, # width at base factor, > 1 for wider spoke base
710             $srf
711             )= map { $cp->{$_} } qw(spoken wos bsf rsf roe wobf srf);
712            
713             $cp->{mm} or $cp->{mm}=1.0/25.4;
714            
715             map{ eval '\$$_=$cp->{$_} ' } qw(spoken wos bsf rsf roe wobf);
716             $wobf ||= 1.0; # default to non-tapered spokes.
717            
718             return if (!$spoken); # no spokes, no trepanning.
719            
720             my $mm=$cp->{mm};
721             my $pi=4.0 * atan2(1, 1);
722            
723             my $rr=$mm*(1-$rsf)*$cp->{dw}/2; # rim radius;
724            
725            
726             $wos=0.5*$wos/$spoken;
727             # $wosb=$wobf*$wos; # width at boss
728            
729             my $wosb=$wos; # width of spoke base, near center of wheel.
730             $wos=$wos/$wobf; # width of spoke at the rim, less than base width if wid of base factor greater than 1.
731            
732             $gp->grapid('z',0.1);
733             $gp->grapid('x',$xi,'y',$yi);
734             $srf=$srf*2*$pi;
735             my (@xy,@l2);
736             my ($tx,$ty); # temory x,y variables;
737             my ($x,$y);
738             my ($wsx,$wsy);
739            
740             for my $w ( 0..$spoken-1) # for each window, we calculate all the points, and put them on a stack.
741             { # before we plot, we process to radius the sharp edges.
742             my $t1=2*$pi*$w/$spoken;
743             my $t2=$t1+2*$pi/$spoken; # end of this window
744             my $d;
745             my $first=1; # This flag to control entry moves on 1st pass.
746             my $z=$zi;
747             $d=$wosb/$bossradius;
748            
749             my $passno=0;
750             while ($passno++ < $cp->{passes})
751             {
752            
753             $x=$bossradius*cos($t1+$pi/$spoken); # positioning for 2nd half of circle segment at bossradius.
754             $y=$bossradius*sin($t1+$pi/$spoken);
755             push(@xy,$x,$y);
756            
757             $x+=$bossradius*(cos($t2-$d)-cos($t1+$pi/$spoken)); # rotation at boss radius
758             $y+=$bossradius*(sin($t2-$d)-sin($t1+$pi/$spoken));
759             my $t=$t2-$d;
760             push(@xy,$x,$y);
761            
762             $d=$wos/$rr;
763             $x+=$rr*cos($t2-$d+$srf)-$bossradius*cos($t); # radial move to rim
764             $y+=$rr*sin($t2-$d+$srf)-$bossradius*sin($t);
765             push(@xy,$x,$y);
766            
767             $x+=$rr*(cos($t1+$d+$srf)-cos($t2-$d+$srf)); # rotatin around rim
768             $y+=$rr*(sin($t1+$d+$srf)-sin($t2-$d+$srf));
769             $t=$t1+$d+$srf;
770             push(@xy,$x,$y);
771            
772             $d=$wosb/$bossradius;
773             $x+=$bossradius*cos($t1+$d)-$rr*cos($t); # radial move back towards center
774             $y+=$bossradius*sin($t1+$d)-$rr*sin($t);
775             push(@xy,$x,$y);
776            
777             $x=$bossradius*cos($t1+$pi/$spoken); # remaining half of rotation around boss.
778             $y=$bossradius*sin($t1+$pi/$spoken);
779             push(@xy,$x,$y);
780            
781             if ($first)
782             {
783             $gp->gcompr('d',$gp->{toolnumber},$gp->gmove('x',$xi+shift(@xy),'y',$yi+shift(@xy)));
784             }
785             else
786             {
787             # $gp->gmove('x',$xi+shift(@xy),'y',$yi+shift(@xy),'z',$z)
788             shift(@xy); shift(@xy);
789             }
790             $gp->gcomment(sprintf "Trepanning - window %d pass %d of %d", $w+1,$passno,$cp->{passes});
791             $z+=$cp->{passdepth} ; # passdepth -ve
792            
793             @l2=$cp->rsmooth(shift(@xy),shift(@xy),shift(@xy),shift(@xy),$bossradius,-$roe);
794             @xy=(@l2,@xy);
795             $gp->garcccw('x',$xi+($tx=shift(@xy)),'y',$yi+($ty=shift(@xy)),'r',$bossradius,'z',$z); # rotation at boss radius, add in z incremen
796            
797            
798             @l2=$cp->smooth(shift(@xy),shift(@xy),shift(@xy),shift(@xy),$rr,$roe);
799             @xy=(@l2,@xy);
800             $gp->garccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$roe);
801            
802             $gp->gmove('x',$xi+shift(@xy),'y',$yi+shift(@xy)); # line outwards
803             $gp->garccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$roe);
804            
805            
806             @l2=$cp->rsmooth(shift(@xy),shift(@xy),shift(@xy),shift(@xy),$rr,-$roe);
807             @xy=(@l2,@xy);
808             $gp->garccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$rr); # outer radius
809            
810             @l2=$cp->smooth(shift(@xy),shift(@xy),shift(@xy),shift(@xy),$bossradius,$roe);
811             @xy=(@l2,@xy);
812            
813             $gp->garccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$roe);
814             #
815             $gp->gmove('x',$xi+shift(@xy),'y',$yi+shift(@xy)); # line inwards
816             $gp->garccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$roe);
817            
818             ### Actually we dont want to do this! The next point is always on the same arc, so this sometimes causes
819             ### problems if we've already gone past this point, get an arc in wrong direction. This mitigates known bug 1.
820             ### $gp->garcccw('x',$xi+shift(@xy),'y',$yi+shift(@xy),'r',$bossradius);
821             shift(@xy), shift(@xy); ###
822            
823             # $gp->gmove('z',0.1);
824             $first=0;
825             } # all passes complete.
826             # repeat 1st move as z was being ramped up during this move.
827             $gp->garcccw('x',$xi+$tx,'y',$yi+$ty,'r',$bossradius,'z',$z); # rotation at boss radius, add in z incremen
828             $gp->grapid('z',0.1);
829             ($x,$y)=($tx,$ty);
830             # $x+=3*$cp->{cuttersize}*$tx/sqrt($tx**2+$ty**2);
831             # $y+=3*$cp->{cuttersize}*$ty/sqrt($tx**2+$ty**2);
832            
833            
834             # $gp->gcomment("This was designed to be where we take comp off but cant get it to work. so delay this till next move");
835             # $gp->garccw('x',$xi+$x,'y',$yi+$y,'r',1.5*$cp->{cuttersize}); # to avoid problems with imaginary gauginging
836             # we move avay in arc at this point of twice radius of cutter.
837             $x=0.75*$bossradius*cos($t2);
838             $y=0.75*$bossradius*sin($t2);
839             $gp->gcomp0($gp->gmove('x',$xi+$x,'y',$yi+$y)); #
840             }
841             }
842             # private #
843             sub cuthole
844             { # diameter V
845            
846            
847             my ($cp,$gp,$x,$y,$z,$size,$feed,$cuttersize)=@_;
848            
849             return if (!defined($size) or $size<=0);
850            
851             my $holesize=$cp->{holesize};
852             $cuttersize ||= $cp->{cuttersize}; # can be optional, use wheel cutter if not supplied
853             $gp->gcomment("Positioning for Hole");
854             $gp->grapid('z',0.05);
855             $gp->grapid('x',$x,'y',$y);
856             $gp->grapid('z',$z);
857             $gp->gmove('x',$x,'y',$y,'z',$z,'f',$feed);
858            
859             my ($passes,$passdepth)=($cp->{passes},$cp->{passdepth});
860            
861             if ($cp->{holedepth})
862             {
863             $passes=abs($cp->{holedepth}/$passdepth+1);
864             $passdepth=$cp->{holedepth}/$passes;
865             }
866             if ($holesize>$cuttersize)
867             {
868             $gp->gcomment("Hole bigger than cutter");
869             $holesize-=$cuttersize; # because we want to compensate for the size of the tool.
870             $gp->gmove('x',$x+$holesize/2,'y',$y);
871            
872             my $passn=0;
873             while ($passn++ < $passes)
874             {
875             $gp->gcomment(sprintf("pass %d",$passn));
876             $z+=$cp->{passdepth}; # passdepth negative
877             $gp->garcccw('x',$x-$holesize/2,'y',$y,'r',$holesize/2,'z',$z,'f',$feed);
878             $gp->garcccw ('x',$x+$holesize/2,'y',$y,'r',$holesize/2);
879             }
880             $gp->garcccw('x',$x-$holesize/2,'y',$y,'r',$holesize/2,'z',$z); # we always redo this as z depth was being faded in during this arc.
881             $gp->gmove('x',$x,'y',$y); # move into center to avoid withdrawal while still in contact with work.
882             }
883             else
884             { # else if holesize eq or less than cutter size, just do a plunge.
885             $z=$passdepth*$passes;
886             $gp->gmove('z',$z,'f',$feed);
887             $gp->gdwell('p',0.75);
888             }
889            
890             # $gp->gmove('z', 0.05,$xs,$ys);
891             $gp->gmove('z', 0.05); # return to surface
892             $gp->gcomment("Hole done");
893             }
894            
895             # debug
896             sub makepoint
897             {
898             my ($gp,$x,$y,$d)=@_; # make a small arrow point.
899             $gp->gmove('x',$x+$d,'y',$y+$d,);
900             $gp->gmove('x',$x,'y',$y);
901             $gp->gmove('x',$x+$d,'y',$y-$d,);
902             $gp->gmove('x',$x,'y',$y);
903             }
904            
905             # wheel - private
906             # Given any combination of $depth,$passes,$passdepth
907             # return a valid passes and passdepth.
908             # eg ($s->{holepassdepth},$s->{holepasses})=passdepth($s->{holepassdepth},$s->{holepasses},$s->{holedepth});
909             sub passdepth
910             {
911             my ($w,$passdepth,$passes,$depth)=@_;
912             if (!defined($depth)) # and !defined($passes and !defined($passdepth)
913             {
914             }
915             elsif (defined($depth) and !defined($passes)) # passdepth must be def
916             {
917             $passes=abs($depth/$passdepth);
918             $passes=int($passes)+1 if ($passes!=int($passes));
919             $passdepth=-abs($depth)/$passes;
920             }
921             elsif (defined($depth) and defined($passes)) # ignore passdepth even if provided. and !defined($passdepth)
922             {
923             $passdepth=-abs($depth)/$passes;
924             }
925             return ($passdepth,$passes);
926             }
927            
928             sub cutset
929             {
930             my ($w,$cuttersize,$passes,$passdepth)=@_;
931             my ($depth,$holedepth);
932             my ($h)=$cuttersize;
933            
934             if (ref($h) eq 'HASH')
935             {
936            
937             ($cuttersize,$passes,$passdepth,$depth,$holedepth)=map { $h->{$_} } split(',',"cuttersize,passes,passdepth,depth,holedepth");
938             $holedepth||=$depth;
939             ($passdepth,$passes)=$w->passdepth($passdepth,$passes,$holedepth);
940            
941             }
942             $w->{cuttersize}=$cuttersize;
943             $w->{passdepth}=$passdepth;
944             $w->{passes}=$passes;
945             return $w;
946             }
947            
948             # previously cutwheel
949             # public
950             sub cut
951             {
952             # my ($x,$y,$z,
953             # $m,$np,$nw,
954             # $gr,$cp,$dd,$dw,
955             # $dp,$pf,$ad,$ar,$feed)=@_;
956            
957             # 1 cut dededum.
958            
959             my ($cp , # wheel
960             $gp, # graphics package, either generate graphics or gcode
961             $x,$y,$z, # where to put the wheel
962             )=@_;
963            
964             $cp->{mm} or die;
965             $pi or die;
966            
967             return $cp->cycut($gp,$x,$y,$z) if ($cp->{cycloidal});
968            
969             my $t=0; # theta, angle of wheel;
970             my $ti=0.5*360/$cp->{n}; # half tooth increment.
971             $ti*= $pi/180; # in radians now.
972            
973            
974            
975             # In some situations particularly pinions tooth and gap angles are not the same.
976             # define twf as factor extra for tooth, less than 1 for a wider gap
977             my $tig=$ti*(2-$cp->{twf}); # width of a gap in radians
978             $ti=$ti*$cp->{twf}; # this is now the width of a tooth. $tig+$ti is unchanged bu changes to $twf
979            
980            
981             my ($xs,$ys,$zs)=($x,$y,$z);
982            
983            
984             $gp->gmove('z',0.1,'f',$gp->{feed});
985            
986             $cp->cutbossindent($gp,$x,$y);
987             $cp->cuthole($gp,$x,$y,$z,$cp->{holesize},$gp->{feed});
988            
989             $cp->{ring}->cut($gp,$x,$y,$z) if ($cp->{ring});
990            
991             $cp->cuttrepan($gp,$xs,$ys,$zs);
992            
993             my $qtc=0.5*$cp->{mm}*$cp->{dw}*$pi/$cp->{n}; # quarter tooth circumference.
994             $x+=$cp->{mm}*$cp->{dw}*0.5;
995             $y+= -$qtc;
996             $gp->gcomment("Move Away From Work");
997            
998             $gp->gmove('x',$x,'y',$y);
999            
1000             # $x-= $qtc;
1001             $y+= $qtc;
1002             # $gp->gcompr('d',$gp->{toolnumber},$gp->garccw('x',$x,'y',$y,'r',$qtc));
1003             $gp->gcompr('d',$gp->{toolnumber},$gp->gmove('x',$x,'y',$y));
1004            
1005             $gp->gcomment("Start Cutting");
1006             $gp->gmove('z',$z+$cp->{passdepth});
1007            
1008            
1009             my $passes=0;
1010            
1011            
1012             while ($passes++ < $cp->{passes})
1013             {
1014             my $tcount=0;
1015             my $t=0;
1016             $z+= $cp->{passdepth};
1017             while ($t/2.0/$pi<0.999 )
1018             {
1019            
1020             $gp->gcomment(sprintf("Tooth number %d pass %d",++$tcount,$passes));
1021            
1022             $x-=$cp->{mm}*$cp->{dd}*cos($t);
1023             $y-=$cp->{mm}*$cp->{dd}*sin($t);
1024             # printf "G1 X$f Y$f Z$f F$ff\n", $x,$y,$z, $gp->{feed}; # radial stroke towards center of wheel
1025            
1026             $gp->gmove('x',$x,'y',$y,'z',$z,'f',$gp->{feed});
1027            
1028            
1029             # 1st attempt, flat tooth bottom:
1030             # $x+=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(cos($t+$tig)-cos($t));
1031             # $y+=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(sin($t+$tig)-sin($t));
1032             # printf "G3 X$f Y$f R$f F$ff\n",$x,$y,$mm*($dw*0.5-$dd),$gp->{feed}; # bottom of tooth, flat bottom
1033             # $gp->gmove('x',$x,'y',$y);
1034            
1035             # 2nd attemt, circular bottom
1036             # $x+=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(cos($t+$tig)-cos($t));
1037             # $y+=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(sin($t+$tig)-sin($t));
1038             # $gp->garccw('x',$x,'y',$y,'r',$cp->{mm}*($cp->{dw}-2*$cp->{dd})*$tig/4,'f',$gp->{feed});
1039            
1040             # last attempt 2 quarter circ arcs
1041             my $dx=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(cos($t+$tig)-cos($t));
1042             my $dy=$cp->{mm}*($cp->{dw}*0.5-$cp->{dd})*(sin($t+$tig)-sin($t));
1043             $dx/=2.0;
1044             $dy/=2.0;
1045            
1046             my $dr=sqrt($dx*$dx+$dy*$dy);
1047             my $cr=$cp->{cuttersize}/2; # cutter radius
1048            
1049             my $crx1=($cr/$dr)*$dx; # a vector in the direction of the end of the tooth bottom, the size of the cutterradius.
1050             my $cry1=($cr/$dr)*$dy;
1051            
1052             my $crx2= -($cr/$dr)*$dy; # a vector normal to the other one, and roughly speaking inwards.
1053             my $cry2=($cr/$dr)*$dx;
1054            
1055             $x+=$crx1+$crx2;
1056             $y+=$cry1+$cry2;
1057            
1058             $gp->garccw('x',$x,'y',$y,'r',$cr);
1059            
1060             $x+=((2*$dr-2*$cr)/$dr)*$dx;
1061             $y+=((2*$dr-2*$cr)/$dr)*$dy;
1062             $gp->gmove('x',$x,'y',$y); # This bit is the flat part of the tooth bottom, after we've subtracted the radius of the cutter from each corner
1063             # we have to program these moves as arcs to make the cutter move in this way because cutter compensation
1064             # is on.
1065             $x+=$crx1-$crx2; # reverse out the deth of the cutter extra that we went
1066             $y+=$cry1-$cry2;
1067            
1068             $gp->garccw('x',$x,'y',$y,'r',$cr);
1069            
1070            
1071             # $x+=$dx+$dy
1072             # $y+=$;
1073             #
1074             # $x*= ($cp->{dw}*0.5-$cp->{dd}-$r)/($cp->{dw}*0.5-$cp->{dd});
1075             # $y*= ($cp->{dw}*0.5-$cp->{dd}-$r)/($cp->{dw}*0.5-$cp->{dd});
1076            
1077             # $gp->garccw('x',$x,'y',$y,'r',$r,'f',$gp->{feed});
1078            
1079            
1080             $t+=$tig;
1081             $x+=$cp->{mm}*$cp->{dd}*cos($t); # radial stroke outwards
1082             $y+=$cp->{mm}*$cp->{dd}*sin($t);
1083             $gp->gmove('x',$x,'y',$y);
1084            
1085             # addendum
1086             # add in addendum height.
1087            
1088             $x+=$cp->{mm}*$cp->{ad}*cos($t);
1089             $y+=$cp->{mm}*$cp->{ad}*sin($t);
1090             # rotate to middle of tooth, 0.25 of full tooth+gap width.
1091             $x+=$cp->{mm}*($cp->{dw}*0.5+$cp->{ad})*(cos($t+0.5*$ti)-cos($t));
1092             $y+=$cp->{mm}*($cp->{dw}*0.5+$cp->{ad})*(sin($t+0.5*$ti)-sin($t));
1093             $gp->garcccw('x',$x,'y',$y,'r',$cp->{mm}*$cp->{ar},'f',$gp->{feed});
1094            
1095             # back out the addendum height.
1096             $x-=$cp->{mm}*$cp->{ad}*cos($t+0.5*$ti);
1097             $y-=$cp->{mm}*$cp->{ad}*sin($t+0.5*$ti);
1098             # rotate a further half .25 tooth pitch
1099             $x+=$cp->{mm}*($cp->{dw}*0.5)*(cos($t+$ti)-cos($t+0.5*$ti));
1100             $y+=$cp->{mm}*($cp->{dw}*0.5)*(sin($t+$ti)-sin($t+0.5*$ti));
1101             $gp->garcccw('x',$x,'y',$y,'r',$cp->{mm}*$cp->{ar},'f',$gp->{feed});
1102            
1103             $t+=$ti;
1104            
1105             }
1106             }
1107            
1108             $gp->gmove('z',0.1);
1109             $gp->gcomp0();
1110             $gp->gmove('x',$xs,'y',$ys,'f',$gp->{feed} );
1111            
1112            
1113            
1114             $cp->cutfillet($gp,$xs,$ys,$zs,$gp->{feed},$cp->{fpasses},$cp->{fpassdepth}) if ($cp->{fillet});
1115            
1116             # $gp->gend();
1117             }
1118            
1119             sub dist
1120             {
1121             shift if (ref($_[0]));
1122             my ($x1,$y1,$x2,$y2)=@_;
1123             return sqrt(($x2-$x1)**2+($y2-$y1)**2);
1124             }
1125            
1126             # public
1127             #wheel
1128             sub outerradius
1129             # return the radius of a circle that contains the wheel including teeth
1130             {
1131            
1132             my ($w)=@_;
1133             my $r=($w->{dw}/2.0+$w->{ad})*$w->{mm};
1134             printf "dw=%f ad=%f outerrad is %f\n",$w->{dw},$w->{ad},$r;
1135             return $r;
1136             }
1137             # public
1138             sub innerradius
1139             # return the radius of a circle that contains the wheel including teeth
1140             {
1141            
1142             my ($w)=@_;
1143             return ($w->{dw}/2.0-$w->{dd})*$w->{mm};
1144             }
1145            
1146             sub fillet
1147             {
1148             my ($c , # wheel
1149             # optional parameters:
1150             $npasses,
1151             $passdepth,
1152             )=@_;
1153            
1154            
1155             $c->{fillet}=1; # flag to say do fillet;
1156             $c->{fpasses}=$npasses;
1157             $c->{fpassdepth}=$passdepth;
1158             }
1159             # Similar to cutwheel, except that this cuts away the little triangles left when the wheel has been cut out
1160             # If you are cutting from a sheet, then its not necessary, but where you are not cutting the full deth,
1161             # eg cutting 2 wheels on top of each other, you need this to remove the triangular fillets
1162             # theres scope for this to be much more comlex, and it may fail as it currently is.
1163             # (Ie its not a great algorithmn, but I've used it inthis form a couple of times. )
1164             sub cutfillet
1165             {
1166             my ($cp , # wheel
1167             $gp, # graphics package, either generate graphics or gcode
1168             $xi,$yi,$z, # where to put the wheel
1169             $feed,
1170             # optional parameters:
1171             $npasses,
1172             $passdepth
1173             )=@_;
1174            
1175             my $cr=$cp->{mm}*$cp->{dw}/2-$cp->{cuttersize}*1.25; # radius to cut to. No attemt made to calcultate, its empirical
1176             my $t=0; # theta, angle of wheel;
1177             my $ti=0.5*360/$cp->{n}; # half tooth increment.
1178             $ti*= $pi/180; # in radians now.
1179            
1180            
1181            
1182             # In some situations particularly pinions tooth and gap angles are not the same.
1183             # define twf as factor extra for tooth, less than 1 for a wider gap
1184             my $tig=$ti*(2-$cp->{twf}); # width of a gap in radians
1185             $ti=$ti*$cp->{twf}; # this is now the width of a tooth. $tig+$ti is unchanged bu changes to $twf
1186            
1187            
1188            
1189             ## my ($xs,$ys)=($x,$y);
1190             my ($x,$y);
1191            
1192             $gp->gmove('z',0.1,'f',$feed);
1193            
1194             $npasses||=$cp->{passes};
1195             $passdepth||=$cp->{passdepth};
1196            
1197             my $passes=0;
1198             while ($passes++ < $npasses)
1199             {
1200            
1201             my $tcount=0;
1202             my $t=-$ti/2;
1203             $z+= $passdepth;
1204             $x=($cp->{mm}*($cp->{dw}/2+$cp->{ad})+$cp->{cuttersize}/2)*cos($t);
1205             $y=($cp->{mm}*($cp->{dw}/2+$cp->{ad})+$cp->{cuttersize}/2)*sin($t); # takes us to adendum point, comensates for cuttersize
1206            
1207             while ($t/2.0/$pi<0.999 )
1208             {
1209            
1210             $gp->gcomment(sprintf("Filleting Tooth number %d pass %d of %d",++$tcount,$passes,$npasses));
1211            
1212             $gp->gmove('x',$xi+$x,'y',$yi+$y);
1213             $gp->gmove('z',$z);
1214            
1215             $t+=$ti/2+$tig/2;
1216             my $tx=$cr*cos($t);
1217             my $ty=$cr*sin($t);
1218            
1219             $t+=$ti/2+$tig/2;
1220            
1221            
1222             my $dx=($cp->{mm}*($cp->{dw}/2+$cp->{ad})+$cp->{cuttersize}/2)*cos($t)-$x;
1223             my $dy=($cp->{mm}*($cp->{dw}/2+$cp->{ad})+$cp->{cuttersize}/2)*sin($t)-$y;
1224            
1225             $x+=$dx/2; # this takes us to middle of gap, but by straight line, so cut off a bit more of that wedge
1226             $y+=$dy/2;
1227             $gp->gmove('x',$x+$xi,'y',$y+$yi,'z',$z,'f',$feed);
1228            
1229             $gp->gmove('x',$tx+$xi,'y',$ty+$yi);
1230            
1231             $gp->gmove('x',$x+$xi,'y',$y+$yi,'z',$z,'f',$feed);
1232            
1233             $x+=$dx/2; # this takes us to next addendum point
1234             $y+=$dy/2;
1235            
1236             }
1237             }
1238            
1239             $gp->gmove('z',0.1);
1240             $gp->gmove('x',$xi,'y',$yi);
1241            
1242             }
1243            
1244             sub smooth
1245             {
1246             # given a circle radius b and a point on circumference l2
1247             # the plan is to smooth the join between the line l1/l2 (each of these are points) and the circle circumference by
1248             # replacing a bit of the line l1/l2 with a circle of radius r.
1249             # The following are supplied where x1 y1 is l1 point 1, x2, y2 point 2 or l2.
1250            
1251             # so call with l1,l2,b ,r
1252             # what comes back is l1 (unchanged), ,replacement l2 point sa which is start of arc, l1,sa are on the line
1253             # l1,l2 but sa is nearer l1 than l2.
1254             # ea where ea is on the original arc radius b, like l2, but is moved away from original l2.
1255             # sa, ea can be joined with arc radius b.
1256            
1257            
1258             my ($w,$x1,$y1,$x2,$y2,$b,$r)=@_;
1259            
1260             # print "smooth x1=$x1,y1=$y1,x2=$x2,y2=$y2,b=$b,r=$r\n";
1261            
1262             #($x1,$y1,$x2,$y2,$b,$r)=(-0.2 , 0.2 , -0.707 , 0.707 , 1.0 , 0.1);
1263             # ($x1,$y1,$x2,$y2,$b,$r)= (-0.279378067455943, 0.65017874253593, 0.702760341741972, 0.0831408677831007, 0.9,0.1);
1264            
1265             my $ks;
1266            
1267             # straight line l1 l2 has eqn y=mx+c
1268            
1269             my $m=($y2-$y1)/($x2-$x1);
1270             my $c=$y1-($y2-$y1)*$x1/($x2-$x1);
1271             $ks=$r>0?1:-1;
1272             # $ks=-$ks if ($y2>0);
1273             $r=abs($r);
1274             $ks=-$ks if ($x1<$x2);
1275            
1276            
1277             # line paralell to this and distance r away is y=mx+j where j=c+k or j=c-k
1278            
1279             my $k = abs($r) * sqrt( (($x2-$x1)**2+($y2-$y1)**2)/($x2-$x1)**2);
1280             my $j=$c+$k*$ks; # ks is the sign of k from above.
1281            
1282             # we need to solve this with the circle x^2+y^2=(b+r)^2
1283             # substituting for y in here gives
1284             #
1285             # x^2+y^2=(b+r)^2
1286             # y=mx+j
1287             # x^2+m^2x^2+2mxj+j^2=(b+r)^2
1288             # (1+m^2) x^2 + 2mj x +j^2-(b+r)^2=0
1289             # use quadratic equation formula to find x:
1290             # x=(-b+- sqrt(b^2-4ac)/2a
1291             #
1292             # x=(-2mj +- sqrt(4m^2j^2-4(1+m^2)(j^2-(b+r)^2)))/2(1+m^2)
1293             # This is the center point of the arc.
1294            
1295             # s is the distance between c and l2, we now have x and y coords for both c and l2.
1296             # u^2=s^2-r^2 and is distance from l2 in direction of l1 for the new l2 point at start of smoothing arc.
1297             # The end of the arc is oc scaled such that distance is b, the radius of the circle.
1298             $r=-$r if (dist(0,0,$x1,$y1)<$b);
1299            
1300             my $cxa=(-2*$m*$j + sqrt(abs(4*$m**2*$j**2-4*(1+$m**2)*($j**2-($b+$r)**2))))/2/(1+$m**2);
1301             my $cxb=(-2*$m*$j - sqrt(abs(4*$m**2*$j**2-4*(1+$m**2)*($j**2-($b+$r)**2))))/2/(1+$m**2); # This is the other root of the quadratic.
1302             # use the one closest to l2?
1303             my $cya=$m*$cxa+$j;
1304             my $cyb=$m*$cxb+$j;
1305            
1306             ($cxa,$cya,$cxb,$cyb)=($cxb,$cyb,$cxa,$cya) if (dist($x2,$y2,$cxb,$cyb)
1307             # swap rather than assign so that we still have the other root if we need to look at it for debug purposes.
1308            
1309             my $s=dist($x2,$y2,$cxa,$cya);
1310             my $u=sqrt($s**2-$r**2);
1311             my $sax=$x2+($x1-$x2)*$u/dist($x1,$y1,$x2,$y2); #start of arc
1312             my $say=$y2+($y1-$y2)*$u/dist($x1,$y1,$x2,$y2);
1313             my $eax=$cxa*$b/dist(0,0,$cxa,$cya);
1314             my $eay=$cya*$b/dist(0,0,$cxa,$cya);
1315            
1316             return ($x1,$y1,$sax,$say,$eax,$eay); #ending on the circle
1317            
1318             # The code below is used for graphing out test cases. Its debug only.
1319             my $gd=gdcode::new(undef,"test.png",6.0,2500,2500);
1320             $gd->gmove('z',0.1);
1321             $gd->gmove('x',$x1,'y',$y1);
1322             $gd->gmove('z',-0.1);
1323             $gd->gmove('x',$x2,'y',$y2);
1324            
1325             $gd->gmove('z',0.1);
1326             $gd->gmove('x',0,'y',$b);
1327             $gd->gmove('z',-0.1);
1328             $gd->garcccw('x',0,'y',-$b,'r',$b);
1329             $gd->garcccw('x',0,'y',$b,'r',$b);
1330            
1331            
1332             $gd->gmove('z',0.1);
1333             # ($cxa,$cya)=($cxb,$cyb); # want to see the other one ?
1334             $gd->gmove('x',$cxa+0.05,'y',$cya+0.05); # mark the center with an X
1335             $gd->gmove('x',$cxa-0.05,'y',$cya-0.05,'z',-0.1);
1336             $gd->gmove('x',$cxa+0.05,'y',$cya-0.05,'z',0.1);
1337             $gd->gmove('x',$cxa-0.05,'y',$cya+0.05,'z',-0.1);
1338            
1339             # $gd->gmove('x',$cxa,'y',$cya+$r,'z',0.1);
1340             # $gd->garcccw('x',$cxa,'y',$cya-$r,'r',abs($r),'z',-0.1);
1341             # $gd->garcccw('x',$cxa,'y',$cya+$r,'r',abs($r),'z',-0.1);
1342            
1343            
1344             $gd->gmove('x',$sax,'y',$say,'z',0.1);
1345             $gd->garccw('x',$eax,'y',$eay,'r',abs($r),'z',-0.1);
1346            
1347            
1348             $gd->gend();
1349             die;
1350             }
1351             # This is a convienence wrapper funcion for smooth.
1352             # thing is parameter order depends on weather you are coming or going.
1353             # this function does the appropriate swap, both of the input parameters and the result
1354             sub rsmooth
1355             {
1356             my ($w,$x1,$y1,$x2,$y2,$br,$r)=@_; # params are point1, point 2, bossradius, radius
1357             my @xy=($x2,$y2,$x1,$y1); # point 1 and 2 given in wrong order for reverse smooth, so swap
1358             @xy=$w->smooth(@xy,$br,$r); # xy now is point, start of arc end of arc
1359             @xy=@xy[4,5,2,3,0,1]; # return start of arc, end of arc and point.
1360             return @xy; # need in order given which is reverse, so give end of arc first
1361             }
1362            
1363             # this function returns dimentions in inches.
1364             # input can be a string such as 22mm, 72pt or 2.3i
1365             # output is somethig like 0.9, 1.0,2.3 as 22mm is about 0.9 inches, 72 points is exactly 1 inch and 2.3i means 2.3 inches.
1366             # t is thousandths of an inch.
1367             sub dim
1368             {
1369             my ($w)=shift; # self pointer to wheel
1370             my @a= map
1371             {
1372            
1373             s/i//g;
1374             s/mm//g and $_=$_*$w->{mm};
1375             s/pt//g and $_=$_/72;
1376             s/t//g and $_=$_/1000;
1377             $_;
1378            
1379             } @_;
1380             return $a[0] if (@a==1);
1381             return @a;
1382             }
1383             # this function returns dimentions in radians
1384             # input can be a string such as 0.01r 5d or 5
1385             # default units are degrees.
1386             # note that additional multipliers are allowd but ignore, so you can add an r to make the defult into radians,
1387             # but if degrees is specified, we get 5dr and this is interpreted as degrees as is 5dd.
1388             # Output is always in radians.
1389            
1390             sub dimr
1391             {
1392             my ($w)=shift; # self pointer to wheel
1393             my @a= map
1394             {
1395             if (m/([dr])/i)
1396             {
1397             my $d=$1;
1398             s/[dr]//ig;
1399             if ($d eq 'd')
1400             {
1401             $_=$_*$pi/180.0;
1402             }
1403             }
1404             else
1405             {
1406             $_=$_*$pi/180.0;
1407             }
1408             $_;
1409            
1410             } @_;
1411             return $a[0] if (@a==1); # for scalar context, need to return a scalar.
1412             return @a;
1413             }
1414            
1415             # parameters:
1416             # toothradiuspc and toothradius used only when topshape is bicirc circlead circtrail
1417             package Grahamwheel;
1418            
1419             use vars qw($VERSION @ISA @EXPORT);
1420             $VERSION=0.05;
1421            
1422             @ISA=('Wheel');
1423            
1424             sub new
1425             {
1426             my (
1427             $class, # self pointer;
1428             $n, # hash (now)
1429             )=@_;
1430            
1431             #die "$d,$dd";
1432            
1433            
1434             my $s={};
1435             shift;
1436             if (ref($n)) # means we've been passed a hash ref
1437             {
1438             my $h=$n;
1439            
1440             for my $key (qw(lift nteeth externald toothdepth toothtoppc toothtop toothbase toothbasepc offset holesize topshape toothradius toothradiuspc filletradius ))
1441             {
1442             $s->{$key}=$h->{$key};
1443             }
1444             $s->{n}=$s->{nteeth}; delete $s->{nteeth};
1445             $s->{d}=$s->{externald}; delete $s->{externald};
1446             $s->{dd}=$s->{toothdepth}; delete $s->{toothdepth};
1447             }
1448             else
1449             {
1450             die "You need to pass a hash reference to the Graham wheel constructor";
1451             # map { $s->{$_}=shift } qw(n d dd offset toothbase toothtop topshape topshape);
1452             }
1453            
1454            
1455             #$d=$s->dim($d);
1456             #$dd=$s->dim($dd);
1457             #$offset||=-6;
1458             #$offset=$s->dimr($offset);
1459             #$toothbase||=33.333; # in %
1460             #$toothbase/=100.0; # as a proportion .
1461             #$toothtop||=0.5; # in degrees
1462             #$toothtop=$s->dimr($toothtop);
1463            
1464             bless $s, $class;
1465            
1466             $s->{d}=$s->dim($s->{d});
1467             $s->{dd}=$s->dim($s->{dd});
1468             $s->{offset}||=-6;
1469             $s->{offset}=$s->dimr($s->{offset});
1470             $s->{toothbase}||=$s->{toothbasepc}; # synonym.
1471             $s->{toothbase}/=100.0; # convert to proportion.
1472             $s->{toothtop}||=0.5; # in degrees
1473             $s->{toothtop}=$s->dimr($s->{toothtop}); # now in radians.
1474             if ($s->{toothtoppc} > 0.01)
1475             {
1476             $s->{toothtop}=$s->{toothtoppc}*0.02*$pi/$s->{n};
1477             }
1478            
1479             $s->{topshape}||='semi';
1480            
1481             if (!grep { $s->{topshape} eq $_} qw(semi flat circ bicirc circlead circtrail))
1482             {
1483             die "unknown topshape '$s->{topshape}'";
1484             }
1485             $s->{dw}=($s->{d}-$s->{dd}*2)/$mm; # we set tis so trepanning works. This is a total fudge!
1486            
1487            
1488             return $s;
1489             }
1490             # given line p1,p2, and line p3, p4 find cross point.
1491             sub solve
1492             {
1493             my ($w,$x1,$y1,$x2,$y2,$x3,$y3,$x4,$y4)=@_;
1494            
1495             my ($m1,$m2,$k1,$k2,$t1);
1496            
1497             $m1=($x3-$x4)/($x1-$x2);
1498             $k1=($x4-$x2)/($x1-$x2);
1499            
1500             $m2=($y3-$y4)/($y1-$y2);
1501             $k2=($y4-$y2)/($y1-$y2);
1502            
1503             $t1=($k1-$k2*$m1/$m2)/(1-$m1/$m2);
1504            
1505            
1506             my ($x,$y);
1507            
1508             $x=$t1*$x1+(1-$t1)*$x2;
1509             $y=$t1*$y1+(1-$t1)*$y2;
1510            
1511             return ($x,$y);
1512            
1513             }
1514            
1515             sub ttr
1516             {
1517             my ($w,$gp,$pr)=@_;
1518             my ($x1,$y1,$x2,$y2,$x3,$y3,$x4,$y4)=@$pr;
1519             #
1520             # p2/ |p3 4 points with x y cords as in diagram. Calculating radius of circle
1521             # / | tat will join p2 p3.
1522             # p1/ |p4
1523             # _/ |_
1524             #
1525             #
1526             #
1527            
1528             # calclate bisector point of angle p1 p2 p3
1529             # method, make unit vector in direction of lines, make point half way between ends of vectors. (half point for 2, hp2)
1530             # make vector from p2 to half way point.
1531             # repeat with p4p3p2
1532             # cross vectors to get circle center point
1533            
1534             #solve($w,2.5,5.2,6.8,2.9,1.0,0,6.8,0);
1535             #exit;
1536            
1537             $gp->gline($x1,$y1,$x2,$y2);
1538             $gp->gline($x3,$y3,$x4,$y4);
1539            
1540             # This is a unit vector in the x1 x2 direction
1541             my $u21x=($x1-$x2)/sqrt(($x1-$x2)**2+($y1-$y2)**2);
1542             my $u21y=($y1-$y2)/sqrt(($x1-$x2)**2+($y1-$y2)**2);
1543            
1544             # $gp->gline($x2,$y2,$x2+$u21x,$y2+$u21y);
1545            
1546            
1547             # This is a unit vector in the x3 x2 direction
1548             my $u23x=($x3-$x2)/sqrt(($x3-$x2)**2+($y3-$y2)**2);
1549             my $u23y=($y3-$y2)/sqrt(($x3-$x2)**2+($y3-$y2)**2);
1550            
1551             #$gp->gline($x2,$y2,$x2+$u23x,$y2+$u23y);
1552            
1553             # A point half way between unit vectors gives the angle bisector.
1554             my $hp2x=$x2+0.5*($u21x+$u23x); # gives angle bisecor point as absolute coord.
1555             my $hp2y=$y2+0.5*($u21y+$u23y);
1556            
1557             #$gp->gline($x2,$y2,$hp2x,$hp2y);
1558            
1559            
1560             my $u34x=($x4-$x3)/sqrt(($x4-$x3)**2+($y4-$y3)**2);
1561             my $u34y=($y4-$y3)/sqrt(($x4-$x3)**2+($y4-$y3)**2);
1562            
1563             my $u32x=-$u23x;
1564             my $u32y=-$u23y;
1565            
1566             # The other angle bisected.
1567             my $hp3x=$x3+0.5*($u32x+$u34x); # gives angle bisecor point as absolute coord.
1568             my $hp3y=$y3+0.5*($u32y+$u34y);
1569            
1570            
1571             #$gp->gline($x3,$y3,$hp3x,$hp3y);
1572            
1573            
1574             # Where these two angle bisectors cross, gives us the circle center.
1575             my ($cx,$cy)=$w->solve($x2,$y2,$hp2x,$hp2y,$x3,$y3,$hp3x,$hp3y);
1576            
1577            
1578            
1579            
1580             # cosine of the angle at p1 by the cosine rule.
1581             my $ca1=(($cx-$x2)**2+($cy-$y2)**2-($x1-$x2)**2-($y1-$y2)**2-($cx-$x1)**2-($cy-$y1)**2)/
1582             (-2*sqrt((($x1-$x2)**2+($y1-$y2)**2)*(($cx-$x1)**2+($cy-$y1)**2)));
1583            
1584            
1585             # length from p1 to the normal point
1586             my $l1 =$ca1*sqrt((($cx-$x1)**2+($cy-$y1)**2));
1587            
1588             # proportion down the line from x1 to normal point
1589             my $t=$l1/sqrt(($x1-$x2)**2+($y1-$y2)**2);
1590            
1591             # this is the normal point, and we replace x2, y2 with this new point.
1592             my $mx2=$x1*(1-$t)+$x2*$t;
1593             my $my2=$y1*(1-$t)+$y2*$t;
1594            
1595            
1596             # do the same to calculate x3 y3 replacement oint
1597             my $ca2=(($cx-$x3)**2+($cy-$y3)**2-($x3-$x4)**2-($y3-$y4)**2-($cx-$x4)**2-($cy-$y4)**2)/
1598             (-2*sqrt((($x3-$x4)**2+($y3-$y4)**2)*(($cx-$x4)**2+($cy-$y4)**2)));
1599            
1600             my $l2 =$ca2*sqrt((($cx-$x4)**2+($cy-$y4)**2));
1601            
1602             $t=$l2/sqrt(($x3-$x4)**2+($y3-$y4)**2);
1603            
1604             my $mx3=$x4*(1-$t)+$x3*$t;
1605             my $my3=$y4*(1-$t)+$y3*$t;
1606            
1607             # calculate the radius. For best acuracy, we average these, probably not necessary.
1608             my $r=(sqrt(($cx-$mx2)**2+($cy-$my2)**2)+sqrt(($cx-$mx3)**2+($cy-$my3)**2))/2;
1609            
1610             # replace the points
1611             @$pr=($x1,$y1,$mx2,$my2,$mx3,$my3,$x4,$y4);
1612            
1613             # return the radius.
1614             return $r;
1615             }
1616            
1617            
1618             # grahamwheel cut
1619             sub cut
1620             {
1621            
1622             my ($cp , # wheel
1623             $gp, # graphics package, either generate graphics or gcode
1624             $x,$y,$z,$theta # where to put the wheel
1625             )=@_;
1626            
1627            
1628             my $ti =2*$pi/$cp->{n}; # tooth and gap angular increment.
1629             my $tig=(1-$cp->{toothbase})*$ti; # tooth increment for gap (angular)
1630             my $tt = $cp->{toothbase}*$ti; # tooth angular increment.
1631            
1632            
1633             my $topshape=$cp->{topshape};
1634             my $toothtop=$cp->{toothtop}; # anglar size of toothtop
1635             my $filletradius;
1636             $filletradius=0.02;
1637             $filletradius=0.03125; # corresponds to exactly 1/16 inch cutter
1638             $filletradius=0.033; # in practice make slightly larger
1639             $filletradius=$cp->{filletradius}; # This is the radius used at the bottom of the tooth gap, needs to be as small as possible, but bigger than the cutter
1640             # radius used, or else it cant be cut!
1641            
1642             $cp->{filletradius} or die;
1643             my $ccrunin=3*$cp->{cuttersize}; # distance used for compensation run in.
1644            
1645             # printf "cos offset=%f offset=%f\n",cos($cp->{offset}),$cp->{offset};
1646            
1647             $cp->{d}-=$cp->{d}*$toothtop*cos($cp->{offset}*$cp->{n}/5) if ($topshape eq 'semi');
1648             # 5 is an empiricle fudge factor in the above
1649             # purpose of this is to prevent the semicircle at the top of each tooth increasing diameter of wheel.
1650            
1651            
1652             my ($xs,$ys,$zs)=($x,$y,$z); # remember initial place.
1653             $gp->gcomment("Graham Wheel");
1654             $gp->gmove('z',0.1,'f',$gp->{feed});
1655             $cp->cutbossindent($gp,$x,$y);
1656             $cp->cuthole($gp,$x,$y,$z,$cp->{holesize},$gp->{feed});
1657             $cp->{ring}->cut($gp,$x,$y,$z) if ($cp->{ring});
1658             $cp->cuttrepan($gp,$xs,$ys,$zs);
1659            
1660            
1661             my $ri=$cp->{d}*0.5-$cp->{dd}; # inner radius
1662             my $ro=$cp->{d}*0.5; # outer radius
1663            
1664            
1665             my $passes=0;
1666             my $feed=3*$gp->{feed}; # faster for positioning, should really be grapid.
1667             my $offset=$cp->{offset};
1668             my $first=1;
1669             my $lift=$cp->{lift};
1670            
1671             $theta=$cp->dimr($theta); # convert radians, default degrees.
1672            
1673             $gp->gcomment("Graham Wheel - teeth");
1674             while ($passes++ < $cp->{passes})
1675             {
1676             my $tcount=0;
1677             my $t=$tig/2+$theta; # start half way through gap
1678             my (@toothgap);
1679             $z+= $cp->{passdepth};
1680             while ($t<2*$pi+$theta )
1681             {
1682             my @xy;
1683            
1684             $gp->gcomment(sprintf("Tooth number %d pass %d",++$tcount,$passes));
1685            
1686            
1687            
1688             $x=$ri*cos($t);
1689             $y=$ri*sin($t);
1690            
1691             if ($first)
1692             {
1693             $first=0;
1694             $gp->gmove('x',$xs+$x,'y',$ys+$y-$ccrunin,$feed);
1695             $gp->gcompr('d',$gp->{toolnumber},$gp->gmove('x',$xs+$x,'y',$ys+$y,$feed));
1696             # $gp->gmove('z',$z,$gp->{feed}); # pen down, slow feed
1697             $first=0;
1698             }
1699             else
1700             {
1701             $gp->gmove('x',$xs+$x,'y',$ys+$y,'f',$gp->{feed}); # inner circumference, 1st point
1702             $gp->gmove('z',$z,$gp->{feed}); # pen down, slow feed
1703             }
1704             # $feed=$cp->{d}*0.5-$cp->{dd};
1705             # 2nd half of tooth gap
1706             $t+=$tig/2;
1707             $x=$ri*cos($t);
1708             $y=$ri*sin($t);
1709            
1710             @xy=();
1711             push(@xy,$x,$y);
1712             # $gp->garcccw('x',$x,'y',$y,'r',$ri); # to end of toothgap
1713            
1714             # $x+=($ro-$ri)*cos($t+$offset)/cos($offset); # top of tooth.
1715             # $y+=($ro-$ri)*sin($t+$offset)/cos($offset);
1716            
1717             # to top of tooth:
1718            
1719            
1720             my $td=$tt-$toothtop; # Differencce in angular size of top and bott of tooth, ensures that a tapered tooth is symetric
1721             $x=($lift+$ro)*cos($t+$offset+$td/2);
1722             $y=($lift+$ro)*sin($t+$offset+$td/2);
1723            
1724            
1725            
1726            
1727            
1728             push(@xy,$x,$y); # leading edge of tooth
1729             @toothgap=();
1730             push(@toothgap,@xy); # for toothgap calculation
1731            
1732            
1733             @xy=$cp->rsmooth(@xy,$ri, -1*$filletradius);
1734            
1735             $gp->garcccw('x',$xs+shift(@xy),'y',$ys+shift(@xy),'z',$z,'r',$ri); # to end of toothgap
1736             $gp->garccw('x',$xs+shift(@xy),'y',$ys+shift(@xy),'r',$filletradius); # draw fillet
1737            
1738            
1739             $x=($ro)*cos($t+$toothtop+$offset+$td/2);
1740             $y=($ro)*sin($t+$toothtop+$offset+$td/2);
1741             # print "lift=$lift, ro=$ro ri=$ri\n";
1742            
1743            
1744             @xy=();
1745             push(@xy,$x,$y); # 1st point of trailing edge
1746            
1747            
1748             $t+=$tt;
1749             $x=$ri*cos($t);
1750             $y=$ri*sin($t);
1751            
1752             #$gp->gmove('x',$x,'y',$y); # draw trailing edge of tooth
1753             push(@xy,$x,$y); # 2nd point of trailing edge
1754             push(@toothgap,@xy); # for toothgap calculation
1755            
1756             my $toothtopradius=$cp->ttr($gp,\@toothgap) if ($topshape eq 'semi');
1757            
1758             $t+=$tig/2;
1759             $x=$ri*cos($t);
1760             $y=$ri*sin($t);
1761            
1762             @xy=$cp->smooth(@xy,$ri,$filletradius);
1763            
1764             shift(@xy); shift(@xy);
1765            
1766             my $toothtopdist=$cp->dist(@toothgap[2..5]);
1767             my $toothradius;
1768             $toothradius=0.005*$cp->{toothradiuspc}*$toothtopdist; # 100% means half the total width of tooth.
1769             $toothradius||=$cp->{toothradius};
1770            
1771             # should be flat circ bicirc circlead circtrail semi
1772             if ($topshape eq 'flat')
1773             {
1774             $gp->gmove('x',$xs+$toothgap[2],'y',$ys+$toothgap[3]); # draw leading edge of tooth
1775             $gp->gmove('x',$xs+$toothgap[4],'y',$ys+$toothgap[5]); # draw toothtop flat
1776             }
1777             elsif ($topshape eq 'circ')
1778             {
1779             $gp->gmove('x',$xs+$toothgap[2],'y',$ys+$toothgap[3]); # draw leading edge of tooth
1780             $gp->garcccw('x',$xs+$toothgap[4],'y',$ys+$toothgap[5],'r',$ro); # draw circular shaped tooth top
1781             }
1782             elsif ($topshape eq 'bicirc')
1783             {
1784             my @bc;
1785             @bc=@toothgap[0..3];
1786             @bc=$cp->smooth(@bc,$ro,-$toothradius);
1787             shift(@bc);shift(@bc);
1788            
1789             $gp->gmove('x',$xs+shift(@bc),'y',$ys+shift(@bc)); # draw leading edge of tooth
1790             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$toothradius); # draw rounded edge
1791             @bc=@toothgap[4..7];
1792             @bc=$cp->rsmooth(@bc,$ro,$toothradius);
1793             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$ro); # draw circular shaped tooth top
1794             # $gp->gmove('x',$xs+shift(@bc),'y',$ys+shift(@bc); # draw flat shaped tooth top
1795             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$toothradius); # draw rounded edge
1796             }
1797             elsif ($topshape eq 'circlead')
1798             {
1799             my @bc;
1800            
1801             $gp->gmove('x',$xs+$toothgap[2],'y',$ys+$toothgap[3]); # draw leading edge of tooth
1802            
1803             @bc=@toothgap[4..7];
1804             @bc=$cp->rsmooth(@bc,$ro,$toothradius);
1805             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$ro); # draw circular shaped tooth top
1806             # $gp->gmove('x',$xs+shift(@bc),'y',$ys+shift(@bc)); # draw flat shaped tooth top
1807            
1808             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$toothradius); # draw rounded edge
1809             }
1810             elsif ($topshape eq 'circtrail')
1811             {
1812             my @bc;
1813             push(@bc,@toothgap[0..3]);
1814             @bc=$cp->smooth(@bc,$ro,-$toothradius);
1815             shift(@bc);shift(@bc);
1816            
1817            
1818             $gp->gmove('x',$xs+shift(@bc),'y',$ys+shift(@bc)); # draw leading edge of tooth
1819             $gp->garcccw('x',$xs+shift(@bc),'y',$ys+shift(@bc),'r',$toothradius); # to end of toothgap toothgap
1820             $gp->garcccw('x',$xs+$toothgap[4],'y',$ys+$toothgap[5],'r',$ro); # draw circular shaped tooth top flat
1821             }
1822             else # if semi
1823             {
1824             $gp->gmove('x',$xs+$toothgap[2],'y',$ys+$toothgap[3]); # draw leading edge of tooth
1825             $gp->garcccw('x',$xs+$toothgap[4],'y',$ys+$toothgap[5],'r',$toothtopradius); # draw semi-circulular-type tooth top
1826             }
1827             $gp->gmove('x',$xs+shift(@xy),'y',$ys+shift(@xy));
1828             $gp->garccw('x',$xs+shift(@xy),'y',$ys+shift(@xy),'r',$filletradius); # draw fillet
1829            
1830             $gp->garcccw('x',$xs+$x,'y',$ys+$y,'r',$ri); # draw 2nd half of tooth gap
1831             # $gp->gend(); exit;
1832            
1833             }
1834             }
1835            
1836             $gp->gmove('z',0.1);
1837             $gp->gcomp0($gp->gmove('x',$xs+$x,'y',$ys+$y+$ccrunin)); # compensation off
1838             $gp->grapid('x',$xs,'y',$ys,'f',$gp->{feed} );
1839             # $cp->cutfillet($gp,$xs,$ys,$zs,$gp->{feed},$cp->{fpasses},$cp->{fpassdepth}) if ($cp->{fillet});
1840             }
1841            
1842             package Grahamyoke;
1843             use vars qw($VERSION @ISA @EXPORT);
1844             $VERSION=0.05;
1845            
1846             @ISA=('Wheel');
1847            
1848             sub new
1849             {
1850             my (
1851             $class,
1852             $n, # a hash reference containing other parameters
1853             )=@_;
1854             # die "$s , $n,".ref($n);
1855             my $s={};
1856             if (ref($n) eq 'HASH') # means we've been passed a hash ref
1857             {
1858             my $h=$n;
1859            
1860             # leradius - leading edge radius left and right, 0 or undef for none.
1861             # droopangle - angle from horizontal of main structure of each side eof the yoke.
1862             for my $key (qw(liftl liftr lift rl rr r armwidth width innerradius outerradius topradius botradius anglel
1863             angler angle holesize leradiusl leradiusr leradius droopanglel droopangler droopangle))
1864             {
1865             $s->{$key}=$h->{$key};
1866             }
1867             }
1868             else
1869             {
1870             die "A hash reference is required for new $class got ".ref($n);
1871             }
1872            
1873            
1874             # and check units of linear things
1875             for my $key ( qw(r armwidth width innerradius outerradius botradius topradius holesize leradius ))
1876             {
1877             $s->{$key}=Wheel::dim(undef,$s->{$key});
1878             }
1879            
1880             $s->{droopangler}||=$s->{droopangle};
1881             $s->{droopanglel}||=$s->{droopangle};
1882             $s->{angler}||=$s->{angle};
1883             $s->{anglel}||=$s->{angle};
1884             $s->{liftr}||=$s->{lift};
1885             $s->{liftl}||=$s->{lift};
1886             $s->{rl}||=$s->{r};
1887             $s->{rr}||=$s->{r};
1888             $s->{leradiusl}||=$s->{leradius};
1889             $s->{leradiusr}||=$s->{leradius};
1890            
1891            
1892            
1893             # make sure that things that are angles have default input in degrees, but can have radians if we want.
1894             for my $key ( qw(liftl liftr anglel angler angle droopanglel droopangler))
1895             {
1896             $s->{$key}=Wheel::dimr(undef,$s->{$key});
1897             }
1898            
1899            
1900             my $cos=""; # cosmetic check.
1901             for my $key (qw(innerradius outerradius topradius botradius)) # these are all cosmetic, and may be specified as % in which case it is % of width
1902             {
1903             $cos.=$s->{$key};
1904             $s->{$key}*=$s->{width}/100.0 if ($s->{$key}=~s/%//);
1905             }
1906             $s->{armwidth}==0 and $s->{armwidth}=$s->{width};
1907             $s->{width}==0 and $cos=~/%/ and die "You are using % and yet width is zero. % refer to width in new grahamyolk!";
1908            
1909             for my $key (qw(innerradius outerradius topradius botradius)) # these are all cosmetic, and may be specified as % in which case it is % of width
1910             {
1911             $s->{$key}*=$s->{width}/100.0 if ($s->{$key}=~s/%//);
1912             }
1913            
1914            
1915            
1916            
1917             # correction for the width of the arm, increases the angle.
1918             my $ac=0;
1919             $ac=atan2($s->{armwidth}/2,($s->{rl}+$s->{width}));
1920             $s->{anglel}+=$ac;
1921             $s->{droopanglel}-=$ac;
1922             $ac=atan2($s->{armwidth}/2,($s->{rr}+$s->{width}));
1923             $s->{angler}+=$ac;
1924             $s->{droopangler}-=$ac;
1925            
1926             bless $s, $class;
1927            
1928             return $s;
1929             }
1930            
1931             sub definehalfyoke
1932             {
1933             my ($cp,$gp,$x,$y,$z,$theta,
1934             $width,
1935             $armwidth,
1936             $droopangle,
1937             $liftouter,
1938             $liftinner,
1939             $angle,
1940             $innerpr,
1941             $outerpr,
1942             $innerradius,
1943             $outerradius,
1944             $innerlength
1945            
1946             )=@_;
1947            
1948            
1949             my ($xs,$ys,$zs)=($x,$y,$z);
1950             my $p=Profile->new();
1951            
1952             $y+=$armwidth/2/cos($droopangle);
1953            
1954             $p->ppush($x,$y);
1955             $p=$p->rotate(-$droopangle,$xs,$ys);
1956             $y=$ys+$armwidth/2;
1957            
1958            
1959             $p->comment("Top left or right corner");
1960             $p->ppush($x-sqrt(($innerlength+$width)**2-$armwidth*$armwidth/4),$y); # locates extreme nw corner
1961            
1962             $x-=$innerlength+$width;
1963             $y-=$armwidth/2; # back onto center line
1964            
1965            
1966             $p=$p->rotate(-$angle-$liftouter,$xs,$ys);
1967            
1968            
1969             $p->comment("Outermost arc of yoke");
1970             $p->ppush($x,$y,$innerlength+$width,1);
1971             $p=$p->smooth($outerradius,$xs,$ys) if ($outerradius);
1972            
1973             $p=$p->rotate(+$liftouter-$liftinner,$xs,$ys);
1974             $x+=$width;
1975            
1976             $p->comment("Pallete surface");
1977             $p->ppush($x,$y);
1978            
1979            
1980             my $a=$angle-atan2($armwidth,2*$innerlength)+$liftinner; # This is the angle to return.
1981             # Its less than angle because we need to take off half the arm width.
1982             $p=$p->rotate($a,$xs,$ys);
1983             $p=$p->smooth($outerpr,$xs,$ys) if ($outerpr);
1984             $p->comment("Innermost arc of yoke.");
1985             $p->ppush($x,$y,$innerlength,0); # draw inner surface, curved centered on xs,ys.
1986             $p=$p->smooth($innerpr,$xs,$ys) if ($innerpr);
1987             $p=$p->rotate(atan2($armwidth,2*$innerlength),$xs,$ys);
1988             $y-=$armwidth/2/cos($droopangle);
1989             $p=$p->rotate($droopangle,$xs,$ys);
1990             $p->ppush($xs,$y);
1991             $p=$p->smooth($innerradius,$xs,$ys) if ($innerradius);
1992             $p=$p->rotate($theta,$xs,$ys) if ($theta);
1993             return $p;
1994            
1995             }
1996             # graham yoke
1997             sub cut
1998             {
1999            
2000             my ($cp , # Wheel , the self pointer
2001             $gp, # graphics package, either generate graphics or gcode
2002             $x,$y,$z,$theta # where to put the Wheel, and an extra rotation cw in radians
2003             )=@_;
2004            
2005            
2006             my $ccrunin=3*$cp->{cuttersize}; # distance used for compensation run in.
2007             my $fastfeed=3*$gp->{feed}; # faster for positioning, should really be grapid.
2008            
2009             my ($xs,$ys,$zs)=($x,$y,$z); # remember initial place.
2010             $gp->gcomment("Graham Yoke at $x,$y");
2011             $gp->gmove('z',0.05,'f',$gp->{feed});
2012             $cp->cuthole($gp,$x,$y,$z,$cp->{holesize},$gp->{feed});
2013            
2014             $gp->gcomment("Graham Yoke at $x,$y");
2015             $theta=$cp->dimr($theta);
2016            
2017             my $rl=$cp->{rl};
2018             my $width=$cp->{width};
2019            
2020            
2021             my $p=$cp->definehalfyoke($gp,$x,$y,$z,$theta,
2022             $cp->{width},
2023             $cp->{armwidth},
2024             $cp->{droopanglel},
2025             $cp->{liftl},
2026             0,
2027             $cp->{anglel},
2028             $cp->{leradiusl},
2029             0,
2030             $cp->{innerradius},
2031             $cp->{outerradius},
2032             $cp->{rl}
2033             );
2034            
2035            
2036             print "left hand side \n";
2037             $p->print();
2038            
2039             my $q=$cp->definehalfyoke($gp,$x,$y,$z,-$theta,
2040             $cp->{width},
2041             $cp->{armwidth},
2042             $cp->{droopangler},
2043             0,
2044             $cp->{liftr},
2045             $cp->{angler},
2046             0,
2047             $cp->{leradiusr},
2048             $cp->{innerradius},
2049             $cp->{outerradius},
2050             $cp->{rr}
2051             );
2052            
2053             print "right hand side \n";
2054             $q->print();
2055             $q=$q->move(-$xs,0)->mirrory()->reverse()->move($xs,0);
2056             print "After flip...\n";
2057             $q->print();
2058             $p->comment("Second half-yoke");
2059             $p->ppush($q);
2060             print "After add...\n";
2061             $p->print();
2062            
2063             $p->movestartfin();
2064             $p->movestartfin();
2065            
2066             $p->dedupe();
2067            
2068             $p->linesmooth($cp->{topradius},@{$p->{points}}-2) if ($cp->{topradius});
2069             $p->linesmooth(-$cp->{botradius},(@{$p->{points}})/2-2) if ($cp->{botradius});
2070            
2071            
2072             #my @first=$p->shift();
2073             my @first=$p->points(0);
2074             $gp->gmove('x',$first[0]+$ccrunin,'y',$first[1]);
2075             $gp->gcompr('d',$gp->{toolnumber},$gp->gmove('x',$first[0],'y',$first[1]));
2076             $p->plot($gp,$z,$cp->{passes},$cp->{passdepth},0);
2077             $gp->gcomp0($gp->gmove('x',$first[0]+$ccrunin,'y',$first[1]));
2078            
2079             return;
2080             }
2081            
2082             # end of grahamyoke
2083             #################################
2084            
2085             # This is used for creating one piece of metal with 2 or more components vertically stacked on top of each other.
2086             package Stack;
2087             use vars qw($VERSION @ISA @EXPORT);
2088             $VERSION=0.05;
2089            
2090            
2091             sub new
2092             {
2093             my ($t,$cuttersize,$passes,$passdepth,$facedepth)=@_;
2094             my $s={};
2095             my @c;
2096             $s->{c}=\@c;
2097             $s->{cuttersize}=$cuttersize;
2098             $s->{passdepth}=$passdepth;
2099             $s->{facedepth}=$facedepth if ($facedepth);
2100             $s->{passes}=$passes;
2101             return bless $s,$t;
2102             }
2103            
2104             sub add
2105             {
2106             my ($s,@c)=@_;
2107            
2108             my $c=$s->{c};
2109            
2110             push(@$c,@c);
2111            
2112             }
2113             sub insert
2114             {
2115             my ($s,@c)=@_;
2116            
2117             my $c=$s->{c};
2118             unshift(@$c,@c);
2119             }
2120            
2121             sub objects
2122             {
2123             my ($s)=@_;
2124            
2125             return @{$s->{c}};
2126             }
2127            
2128             sub objectcount
2129             {
2130             my ($s)=@_;
2131             my $n=scalar($s->objects());
2132             return $n;
2133             }
2134             # stack
2135             sub cut
2136             {
2137             my ($s,$g,$x,$y,$zi)=@_;
2138             my (@r);
2139             my (@s);
2140             my (@f) ;
2141             my ($z);
2142            
2143             my @c=$s->objects();
2144             for my $i (0..$#c)
2145             {
2146            
2147             if ($i !=$#c)
2148             {
2149             printf "i is $i type %s or is %f \n",ref($c[$i]),$c[$i]->outerradius();
2150            
2151             my $r=$c[$i]->{ring}=Ring->new($s->{cuttersize},$c[$i]->passes(),
2152             $c[$i]->passdepth(),$c[$i]->outerradius(),$c[$i+1]->outerradius(),
2153             $z);
2154             $r->{name}="ring4item $i";
2155            
2156             $z=$zi;
2157             }
2158             }
2159            
2160             $z=$zi;
2161             for my $c ($s->objects())
2162             {
2163             $c->{z}=$z;
2164             $z+=$c->passes()*$c->passdepth();
2165             }
2166            
2167             # resize the non-facing cuts.
2168            
2169             for my $i (0..$#c-1)
2170             {
2171             printf "Outerradius is %f\n", $c[-1]->outerradius();
2172             $c[$i]->{ring}->widen($c[-1]->outerradius()+$s->{extra}) if ($c[$i]->{ring});
2173            
2174             }
2175            
2176            
2177             for my $c (@r,@f,$s->objects())
2178             {
2179             $c->{ring}->cut($g,$x,$y,$c->{z}) if ($c->{ring});
2180             $c->{ring}="";
2181             if ($c->{holesize} )
2182             {
2183             $c->{holedepth}+=$zi;
2184             $c->cuthole($g,$x,$y,0,$c->{holesize},$g->{feed},$s->{cuttersize});
2185             $c->{holesize}=undef;
2186             $c->{holedepth}=0.0;
2187             }
2188             }
2189            
2190             for my $c (@r,@f,$s->objects())
2191             {
2192             printf "stack cut object is %s cutting at z=$c->{z}\n", ref($c);
2193             $c->cut($g,$x,$y,$c->{z});
2194             }
2195             }
2196             package CNC::Cog;
2197             use vars qw($VERSION @ISA @EXPORT);
2198             @ISA=qw(Cog);
2199            
2200             package Cog;
2201             use vars qw($VERSION @ISA @EXPORT);
2202             $VERSION=0.05;
2203            
2204            
2205             my $inches="inches";
2206             my $f="%9f ";
2207             my $ff="%2.1f"; # for feed rate;
2208            
2209             sub newcogpair
2210             {
2211             my ($this,$m,$np,$nw)=@_;
2212             my ($dpi);
2213            
2214             ($m,$np,$nw,$dpi)=map { $m->{$_} } ('module','np','nw','pitch','dpi') if (ref($m) eq 'HASH');
2215             $m=1/($dpi*$mm) if (!defined($m) and defined ($dpi));
2216            
2217             @_==4 or main::confess("wrong number of paremeters");
2218             my $cogpair=bless {};
2219            
2220             my ($w,$p);
2221            
2222             $w=$cogpair->{wheel}=Wheel->new($m,$nw);
2223             $p=$cogpair->{pinion}=Wheel->new($m,$np);
2224             my $af=$cogpair->{af}=addendumFactor($np,$nw);
2225            
2226             $p->{pa}=$w->{pa}=$cogpair->{pa}=0.95*$af; #practical addendum factor
2227             $cogpair->{gr}=$np/$nw;#gear ratio
2228             $p->{cp}=$w->{cp}=$cogpair->{cp} = $m * $pi ; # circular pitch
2229             $w->{dd}=$cogpair->{dd} = $m * $pi/2 ;
2230             $p->{dd}=$m*($af*0.95+0.4); # BSI rule for dd height for pinion.
2231             $w->{dw}=$cogpair->{dw} = $m * $nw ;
2232             $p->{dw}=$cogpair->{dp} = $m * $np ;
2233             # $p->{ad}=$w->{ad}=$cogpair->{ad} = $m * 0.95 * $af ;
2234             $w->{ad}=$cogpair->{ad} = $m * 0.95 * $af ;
2235             $w->{ar}=$cogpair->{ar} = $m * 1.40 * $af ;
2236             $w->{twf}=1.0; # tooth width factor
2237            
2238             if ($p->{n}>=10)
2239             {
2240             # pinion profile A
2241             $p->{ar}=$m*0.525 ;
2242             $p->{ad}=$m*0.525;
2243             }
2244             elsif ($p->{n}==8 or $p->{n}==9)
2245             {
2246             # profile B.
2247             $p->{ar}=$m*0.70 ;
2248             $p->{ad}=$m*0.67;
2249             }
2250             elsif ($p->{n}==6 or $p->{n}==7)
2251             {
2252             # profile C
2253             $p->{ar}=$m*1.05;
2254             $p->{ad}=$m*0.855;
2255             }
2256             if ($p->{n}>=11) # set up special tooth width profile for pinion.
2257             {
2258             $p->{twf}=1.25/1.57;
2259             }
2260             else
2261             {
2262             $p->{twf}=1.05/1.57;
2263             }
2264            
2265             $w->{mm}=$p->{mm}=$cogpair->{mm} = 1.0/25.4; # 1.0/24.8;
2266             $cogpair->{nw}=$nw;
2267             $cogpair->{np}=$np;
2268            
2269             return $cogpair;
2270             }
2271            
2272             sub addendumFactor
2273             {
2274             my ($np,$nw)=@_;
2275             my $b = 0.0 ;
2276             my $t0 = 1.0 ;
2277             my $t1 = 0.0 ;
2278             my $r2 = 2 * $nw/$np ;
2279             my $errorLimit=0.000001;
2280             $pi or die "pi is not set!";
2281             while (abs($t1 - $t0) > $errorLimit)
2282             { $t0 = $t1;
2283             $b = atan2(sin($t0), (1 + $r2 - cos($t0))) ;
2284             $t1 = $pi/$np + $r2 * $b ;
2285             }
2286             return 0.25 * $np * (sin($t1)/sin($b) - $r2);
2287             }
2288             # cog
2289             # set up some default parameters: carry these through to the individuel wheels.
2290             sub cutset
2291             {
2292             my ($cp)=shift(@_);
2293            
2294             $cp->{wheel}->cutset(@_);
2295             $cp->{pinion}->cutset(@_);
2296             return $cp;
2297             }
2298            
2299             ##### end of package cog
2300            
2301             package Ring;
2302             use vars qw($VERSION @ISA @EXPORT);
2303             $VERSION=0.05;
2304            
2305             @ISA=qw(Wheel);
2306             sub outerradius
2307             {
2308            
2309             my ($c) =@_;
2310             if ($c->{r1}<$c->{r2})
2311             {
2312             return $c->{r2};
2313             }
2314             else
2315             {
2316             return $c->{r1};
2317             }
2318             }
2319             sub innerradius
2320             {
2321             my ($c)=@_;
2322             if ($c->{r1}<$c->{r2})
2323             {
2324             return $c->{r1};
2325             }
2326             else
2327             {
2328             return $c->{r2};
2329             }
2330             }
2331            
2332             # ring
2333             # cal as either t,$cuttersize,$passes,$passdepth,$r1,$r2,$z (pld style)
2334             # or hash containing
2335             sub new
2336             {
2337             my ($t,$cuttersize,$passes,$passdepth,$r1,$r2,$z)=@_; # die "$s , $n,".ref($n);
2338            
2339             my $s={};
2340            
2341             my $h=$cuttersize; # might be has or cuttersize at this stage, we dont know.
2342            
2343             if (ref($h) eq 'HASH') # means we've been passed a hash ref
2344             {
2345             for my $key (qw(cuttersize passdepth r1 r2 z holesize holedepth holepassdepth))
2346             {
2347             $h->{$key}=Wheel::dim(undef,$h->{$key});
2348             }
2349             for my $key (qw(cuttersize passes passdepth r1 r2 z holesize holedepth holepasses holepassdepth))
2350             {
2351             $s->{$key}=$h->{$key};
2352             }
2353            
2354             # if holesize is defined, there willl be a hole at the center.
2355             # need to get holepasses and holepassdepth which we actually use.
2356            
2357             if (!defined($s->{holedepth})) # and !defined($s->{holepasses} and !defined($s->{holepassdepth})
2358             {
2359             $s->{holepassdepth}||=$s->{passdepth};
2360             $s->{holepasses}||=$s->{passes};
2361             }
2362             elsif (defined($s->{holedepth}) and !defined($s->{holepasses})) # holepassdepth def or undef
2363             {
2364            
2365             $s->{holepassdepth}||=$s->{passdepth}; # provisional.
2366             $s->{holepasses}=abs($s->{holedepth}/$s->{holepassdepth});
2367             $s->{holepasses}=int($s->{holepasses})+1 if ($s->{holepasses}!=int($s->{holepasses}));
2368             $s->{holepassdepth}=-abs($s->{holedepth})/$s->{holepasses};
2369             }
2370             elsif (defined($s->{holedepth}) and defined($s->{holepasses})) # ignore passdepth even if provided. and !defined($s->{holepassdepth})
2371             {
2372             $s->{holepassdepth}=-abs($s->{holedepth})/$s->{holepasses};
2373             }
2374            
2375             if ($s->{holesize})
2376             {
2377             my $hole=Hole->new($s->{cuttersize},$s->{holepasses},$s->{holepassdepth},$s->{holesize});
2378             $s->{hole}=$hole;
2379             map { delete $s->{$_} } qw( holesize holedepth holepasses holepassdepth );
2380             }
2381            
2382             }
2383             else
2384             {
2385             $s->{cuttersize}=Wheel::dim(undef,$cuttersize);
2386             $s->{passes}=$passes;
2387             $s->{passdepth}=Wheel::dim(undef,$passdepth);
2388             $s->{r1}=Wheel::dim(undef,$r1);
2389             $s->{r2}=Wheel::dim(undef,$r2);
2390             print "ring new r1 is $r1 r2 is $r2\n";
2391             $s->{z}=$z if (defined($z));;
2392             }
2393             $s= bless $s,$t;
2394             print "new ring s is $s\n";
2395             return $s;
2396             }
2397             # ring
2398             sub widen
2399             {
2400             my ($s,$r)=@_;
2401            
2402             if ($s->{r2}>$s->{r1} and $s->{r2}<$r)
2403             {
2404             $s->{r2}=$r;
2405             }
2406             elsif ($s->{r1}>$s->{r2} and $s->{r1}<$r)
2407             {
2408             $s->{r1}=$r;
2409             }
2410             return $r;
2411             }
2412             sub setr2
2413             {
2414             my ($s,$r)=@_;
2415             print "r2 on $s->{name} changed from $s->{r2} to $r\n";
2416             $s->{r2}=$r;
2417             return $r;
2418             }
2419             sub setr1
2420             {
2421             my ($s,$r)=@_;
2422             print "r2 on $s->{name} changed from $s->{r1} to $r\n";
2423             $s->{r1}=$r;
2424             return $r;
2425             }
2426             # ring
2427             sub cut
2428             # The purose of this function is to create an integral boss, and face off the material underneath os that the teeth can be cut.
2429             # Youll need a thick piece of material to use this as the material has to be thick enough both for the boss and the wheel.
2430             # Its appropriate particularly for pinions.
2431             # face off a circular area in steps of a half cutter radius
2432             {
2433             my ($c,$g,$x,$y,$z)=@_;
2434             # variables are
2435             # ring object
2436             # (graphics object),
2437             # where to center(x,y),
2438             # where to start in z plane, often z=0 is appropriate
2439             # what cut to take in z plane on each pass, normally negative
2440             # how many passes,
2441             # final radius,
2442             # initial radius, make bigger than final radius to start outside.
2443             # cuttersize (diameter) and
2444             # units are all inches.
2445            
2446            
2447             printf "calling cut on a %s name is %s r1 is %f r2 is %f\n",ref($c),$c->{name},$c->{r1},$c->{r2};
2448            
2449             $g->grapid('z',0.1);
2450            
2451             my $dd=abs($c->{r1}- $c->{r2});
2452             die "Anulus too narrow for toolsize r1 is $c->{r1} r2 is $c->{r2} \nanulus size is $dd toolsize is $c->{cuttersize}"
2453             if (abs($c->{r2}-$c->{r1})<$c->{cuttersize});
2454             die "Need to have a cuttersize" if ($c->{cuttersize}<=0);
2455            
2456             my $step;
2457             my ($r1,$r2);
2458             $r1=$c->{r1};
2459             $r2=$c->{r2};
2460             if ($c->{r1}<$c->{r2})
2461             {
2462             $r2=$r2-$c->{cuttersize}/2; # calculate compensated radii, compensated for tool radius.
2463             $r1=$c->{r1}+$c->{cuttersize}/2;
2464             $step=$c->{cuttersize}/2;
2465             }
2466             else
2467             {
2468             $r2=$r2+$c->{cuttersize}/2; # calculate compensated radii, compensated for tool radius.
2469             $r1=$c->{r1}-$c->{cuttersize}/2;
2470             $step= -$c->{cuttersize}/2;
2471             }
2472            
2473             $g->gcomment("cutting hole");
2474             $c->{hole}->cut($g,$x,$y,$z) if ($c->{hole});
2475             $g->gcomment("hole done");
2476            
2477             my $pass=0;
2478             while ($pass++<$c->{passes})
2479             {
2480             $z+=$c->{passdepth};
2481             $g->gcomment("Cutting Anulus $pass of $c->{passes}");
2482             # $g->gmove('x',$x,'y',$y,'z',$z,'f',$feed);
2483             my $r=$r1-$step; # compensate for re-increment in 1st pass.
2484             while (($r+$step<$r2)==($r<$r2))
2485             {
2486             $r+=$step;
2487             $g->gcomment("Radius is $r ");
2488             $g->gmove('x',$x+$r,'y',$y,'f',$g->{feed});
2489             $g->gmove('z',$z);
2490             $g->garccw('x',$x-$r,'y',$y,'r',$r);
2491             $g->garccw('x',$x+$r,'y',$y,'r',$r);
2492             }
2493            
2494             my $laststep=$r2-$r;
2495             if ($laststep>0)
2496             {
2497             $r+=$laststep;
2498             $g->gcomment("Final radius is $r");
2499             $g->gmove('x',$x+$r,'y',$y,'f',$g->{feed});
2500             $g->gmove('z',$z);
2501             $g->garccw('x',$x-$r,'y',$y,'r',$r);
2502             $g->garccw('x',$x+$r,'y',$y,'r',$r);
2503             }
2504             }
2505             $g->grapid('z',0.1);
2506             }
2507            
2508             package Boss;
2509             use vars qw($VERSION @ISA @EXPORT);
2510             $VERSION=0.061;
2511            
2512            
2513             sub new
2514             {
2515             my ($t,$cuttersize,$passes,$passdepth,$radius)=@_;
2516             my $b={};
2517             print "boss new $radius, $cuttersize\n";
2518             $b->{ring}=Ring::new($t,$cuttersize,$passes,$passdepth,$radius,$radius+$cuttersize);
2519             return bless $b,$t;
2520             }
2521             sub passes
2522             {
2523             my ($b)=@_;
2524             return $b->{ring}->{passes};
2525             }
2526             sub passdepth
2527             {
2528             my ($b)=@_;
2529             return $b->{ring}->{passdepth};
2530             }
2531             # boss
2532             sub outerradius
2533             {
2534             my ($b)=@_;
2535            
2536             return $b->{ring}->{r2}>$b->{ring}->{r1}?$b->{ring}->{r1}:$b->{ring}->{r2}; # size of remaining metal.
2537             }
2538             sub innerradius
2539             {
2540             my ($b)=@_;
2541             return $b->{ring}->{r2}>$b->{ring}->{r1}?$b->{ring}->{r1}:$b->{ring}->{r2}; # size of remaining metal.
2542             }
2543             # boss
2544             sub cut
2545             {
2546             my ($b,$g,$x,$y,$z)=@_;
2547            
2548             $b->{ring}->cut($g,$x,$y,$z) if ($b->{ring});
2549             }
2550            
2551             package Hole;
2552             use vars qw($VERSION @ISA @EXPORT);
2553             $VERSION=0.05;
2554            
2555             @ISA=('Ring');
2556             sub new
2557             {
2558             my ($t,$cuttersize,$passes,$passdepth,$diameter)=@_;
2559             # return bless SUPER::new($t,$cuttersize,$passes,$passdepth,$bosssize,0),$t;
2560             return bless Ring::new($t,$cuttersize,$passes,$passdepth,$diameter/2,0),$t;
2561             }
2562            
2563             1;
2564             __END__