A reproduction circular slide rule from the mid-1960s may not be the cutting edge of consumer demand, but the pen version of a Tektronix Circuit Computer came out pretty well:

A Bash script compiles the GCMC code with eight different parameter combinations to produce pairs of G-Code files to draw (“engrave” being aspirational) and cut (“mill”, likewise) the three decks and the cursor.
The CNC 3018XL with a Pilot V5RT pen draws the deck scales on white paper:

Better paper definitely produces better results, so I must rummage through the Big Box o’ Paper to see what lies within. Laminating the decks improves their durability and matches the original Tek surface finish.
The MPCNC with a drag knife blade cuts through a laminated deck like butter:

Setting the XY origin to dead center on each deck requires carefully calibrating the USB video camera, with the end result accurate to maybe ±0.1 mm around the entire perimeter. Both machines move equal linear distances along both axes, which was definitely comforting.
Having made half a dozen cursors from various bits of acrylic, none of which look particularly good, demonstrates my engraving hand is too weak for a complete slide rule:

With logarithmic scales in hand, however, adapting the GCMC source code to produce general-purpose circular slide rules with only two decks and smaller diameters may be the way to improve my engraving-fu, as a full-scale Tektronix Circuit Computer would chew up three square-foot plastic sheets.
A general-purpose slide rule would need multi-color (well, at least bi-color) labels and digits for red “inverse” scales to remind you (well, me) they read backwards. Some slipsticks use left-slanting italics, left-pointing markers (“<2”), or other weirdness, but they’re all different.
An early small-scale version engraved on ABS came out OK, modulo poor ink fill:

Engraving the decks on hard drive platters doesn’t count:

All in all, it’s been an interesting exercise and, as you may have guessed, will become a Digital Machinist column.
The GCMC and Bash source code as a GitHub Gist:
| // Tektronix Circuit Computer Reproduction | |
| // Ed Nisley KE4ZNU – 2019-11 | |
| //—– | |
| // Library routines | |
| include("tracepath.inc.gcmc"); | |
| include("engrave.inc.gcmc"); | |
| TekOD = to_mm(7.75in); // orginal Tek Circuit Computer diameter | |
| FALSE = 0; | |
| TRUE = 1; | |
| //—– | |
| // Command line parameters | |
| // -D various useful tidbits | |
| // add unit to speeds and depths: 2000mm / -3.00mm / etc | |
| if (!isdefined("BaseOD")) { | |
| BaseOD = TekOD; | |
| } | |
| comment("Base OD: ",BaseOD); | |
| SizeRatio = BaseOD / TekOD; // overall scaling for different base diameters | |
| comment(" scale factor: ",SizeRatio); | |
| if (!isdefined("SelectPart")) { | |
| SelectPart = "Bottom"; | |
| } | |
| comment("Part: ",SelectPart); | |
| if (!isdefined("Operation")) { | |
| Operation = "Engrave"; | |
| } | |
| comment("Operation: ",Operation); | |
| if (!isdefined("ScaleSpeed")) { | |
| ScaleSpeed = 2400mm; | |
| } | |
| if (!isdefined("TextSpeed")) { | |
| TextSpeed = 2400mm; | |
| } | |
| // Engraving & drag knife force is proportional to depth, but you must know the coefficent! | |
| if (!isdefined("EngraveZ")) { | |
| EngraveZ = -1.0mm; | |
| } | |
| if (!isdefined("KnifeZ")) { | |
| KnifeZ = -2.0mm; | |
| } | |
| if (!isdefined("KnifeSpeed")) { | |
| KnifeSpeed = 1000mm; | |
| } | |
| //—– | |
| // Define useful constants | |
| SafeZ = 10.00mm; // above all obstructions | |
| TravelZ = 1.00mm; // within engraving area | |
| //—– | |
| // Overall values | |
| ScaleHeight = to_inch(3.0/8.0) * SizeRatio; // scale-to-scale distance | |
| WindowHeight = ScaleHeight; // cutout window opening | |
| DeckBottomOD = BaseOD; // deck sizes depend on scale height | |
| DeckMiddleOD = DeckBottomOD – 2*ScaleHeight; | |
| DeckTopOD = DeckMiddleOD – 2*(ScaleHeight + WindowHeight); | |
| ScaleArc = 18deg; // angular length of one decade: +CCW | |
| ScaleExdent = 0.20; // log spacing at end of scales to identifiers | |
| Scale2Pi = log10(2*pi()) * ScaleArc; // angular offset for scales using 2*pi | |
| ScaleRT = log10(2.197225) * ScaleArc; // angular offset for risetime | |
| TauAngle = 150deg; // arbitrary offset to 1.0 on tau scales | |
| TitleAngle = -50deg; // … to Tek title, then +180deg to logo | |
| INWARD = -1; // text and tick alignment (used as integers) | |
| OUTWARD = 1; | |
| TEXT_LEFT = -1; // text justification | |
| TEXT_CENTERED = 0; | |
| TEXT_RIGHT = 1; | |
| TextFont = FONT_HSANS_1_RS; | |
| TitleTextSize = 3.1 * SizeRatio * [1.0mm,1.0mm]; | |
| LegendTextSize = 1.8 * SizeRatio * [1.0mm,1.0mm]; | |
| ScaleTextSize = 1.4 * SizeRatio * [1.0mm,1.0mm]; | |
| //—- | |
| // Define tick layout for scales | |
| // Numeric values = scale position, tick length | |
| // These are not algorithmic! | |
| TickMajor = 3.2mm * SizeRatio; // length of tick marks | |
| TickMid = 1.9mm * SizeRatio; | |
| TickMinor = 1.2mm * SizeRatio; | |
| TickScaleNarrow = { | |
| [1.0,TickMajor], | |
| [1.1,TickMinor],[1.2,TickMinor],[1.3,TickMinor],[1.4,TickMinor], | |
| [1.5,TickMid], | |
| [1.6,TickMinor],[1.7,TickMinor],[1.8,TickMinor],[1.9,TickMinor], | |
| [2.0,TickMajor], | |
| [2.2,TickMinor],[2.4,TickMinor],[2.6,TickMinor],[2.8,TickMinor], | |
| [3.0,TickMajor], | |
| [3.2,TickMinor],[3.4,TickMinor],[3.6,TickMinor],[3.8,TickMinor], | |
| [4.0,TickMajor], | |
| [4.5,TickMinor], | |
| [5.0,TickMajor], | |
| [5.5,TickMinor], | |
| [6.0,TickMajor], | |
| [6.5,TickMinor], | |
| [7.0,TickMajor], | |
| [7.5,TickMinor], | |
| [8.0,TickMajor], | |
| [8.5,TickMinor], | |
| [9.0,TickMajor], | |
| [9.5,TickMinor] | |
| }; | |
| TickScaleWide = { | |
| [1.0,TickMajor], | |
| [1.1,TickMinor],[1.2,TickMinor],[1.3,TickMinor],[1.4,TickMinor], | |
| [1.5,TickMid], | |
| [1.6,TickMinor],[1.7,TickMinor],[1.8,TickMinor],[1.9,TickMinor], | |
| [2.0,TickMajor], | |
| [2.1,TickMinor],[2.2,TickMinor],[2.3,TickMinor],[2.4,TickMinor], | |
| [2.5,TickMid], | |
| [2.6,TickMinor],[2.7,TickMinor],[2.8,TickMinor],[2.9,TickMinor], | |
| [3.0,TickMajor], | |
| [3.2,TickMinor],[3.4,TickMinor],[3.6,TickMinor],[3.8,TickMinor], | |
| [4.0,TickMajor], | |
| [4.2,TickMinor],[4.4,TickMinor],[4.6,TickMinor],[4.8,TickMinor], | |
| [5.0,TickMajor], | |
| [5.5,TickMinor], | |
| [6.0,TickMajor], | |
| [6.5,TickMinor], | |
| [7.0,TickMajor], | |
| [7.5,TickMinor], | |
| [8.0,TickMajor], | |
| [8.5,TickMinor], | |
| [9.0,TickMajor], | |
| [9.5,TickMinor] | |
| }; | |
| TickLabels = [1,2,5]; // labels only these ticks, must be integers | |
| TickGap = 0.50 * ScaleTextSize.y; // gap between text and ticks | |
| PivotOD = 5.0mm; // center bolt OD | |
| Legend1 = "Ed Nisley – KE4ZNU"; | |
| Legend2 = "softsolder.com"; | |
| //—————————————————————————– | |
| // Text & Scale Engraving | |
| //—– | |
| // Write text on a radial line | |
| function RadialText(TextPath,CenterPt,Radius,Angle,Justify,Orient) { | |
| local pl = TextPath[-1].x; // path length | |
| local ji = (Justify == TEXT_LEFT) ? 0mm : // justification, assume OUTWARD | |
| (Justify == TEXT_CENTERED) ? -pl/2 : | |
| (Justify == TEXT_RIGHT) ? -pl : | |
| 0mm; | |
| if (Orient == INWARD) { | |
| TextPath = rotate_xy(TextPath,180deg); | |
| ji = -ji; | |
| } | |
| TextPath += [Radius + ji,0mm]; | |
| return rotate_xy(TextPath,Angle) + CenterPt; | |
| } | |
| //—– | |
| // Draw a radial legend | |
| // Offset in units of char height: 0 = baseline on radius, +/- = above/below | |
| function RadialLegend(Text,Center,Radius,Angle,Justify,Orient,Offset) { | |
| local tp = scale(typeset(Text,TextFont),LegendTextSize) + [0mm,Offset * LegendTextSize.y]; | |
| local tpr = RadialText(tp,Center,Radius,Angle,Justify,Orient); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| } | |
| //—– | |
| // Bend text around an arc | |
| function ArcText(TextPath,CenterPt,Radius,BaseAngle,Justify,Orient) { | |
| local pl = TextPath[-1].x; // path length | |
| local c = 2*pi()*Radius; | |
| local ta = to_deg(360 * pl / c); // subtended angle | |
| local ja = (Justify == TEXT_LEFT ? 0deg : // assume OUTWARD | |
| (Justify == TEXT_CENTERED) ? -ta / 2 : | |
| (Justify == TEXT_RIGHT) ? -ta : | |
| 0deg); | |
| ja = BaseAngle + Orient*ja; | |
| local ArcPath = {}; | |
| local pt,r,a; | |
| foreach(TextPath; pt) { | |
| if (!isundef(pt.x) && !isundef(pt.y) && isundef(pt.z)) { // XY motion, no Z | |
| r = (Orient == OUTWARD) ? Radius – pt.y : Radius + pt.y; | |
| a = Orient * 360deg * (pt.x / c) + ja; | |
| ArcPath += {[r*cos(a) + CenterPt.x, r*sin(a) + CenterPt.y,-]}; | |
| } | |
| elif (isundef(pt.x) && isundef(pt.y) && !isundef(pt.z)) { // no XY, Z up/down | |
| ArcPath += {pt}; | |
| } | |
| else { | |
| error("ArcText – Point is not pure XY or pure Z: " + to_string(pt)); | |
| } | |
| } | |
| return ArcPath; | |
| } | |
| //—– | |
| // Draw scale legend | |
| function ArcLegend(Text,Radius,Angle,Orient) { | |
| local tp = scale(typeset(Text,TextFont),LegendTextSize); | |
| local tpa = ArcText(tp,[0mm,0mm],Radius,Angle,TEXT_CENTERED,Orient); | |
| feedrate(TextSpeed); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| } | |
| //—– | |
| // Draw a decade of ticks & labels | |
| // ArcLength > 0 = CCW, < 0 = CW | |
| // UnitOnly forces just the unit tick, so as to allow creating the last tick of the scale | |
| function DrawTicks(Radius,TickMarks,TickOrient,UnitAngle,ArcLength,Decade,LabelOrient,UnitOnly) { | |
| feedrate(ScaleSpeed); | |
| local a,r0,r1,p0,p1; | |
| if (Decade == 1 || UnitOnly) { // draw unit marker | |
| a = UnitAngle; | |
| r0 = Radius + TickOrient * (TickMajor + 2*TickGap + ScaleTextSize.y); | |
| p0 = r0 * [cos(a),sin(a)]; | |
| r1 = Radius + TickOrient * (ScaleHeight – 2*TickGap); | |
| p1 = r1 * [cos(a),sin(a)]; | |
| goto(p0); | |
| move([-,-,EngraveZ]); | |
| move(p1); | |
| goto([-,-,TravelZ]); | |
| } | |
| local ticklist = UnitOnly ? {TickMarks[0]} : TickMarks; | |
| local tick; | |
| foreach(ticklist; tick) { | |
| a = UnitAngle + ArcLength * log10(tick[0]); | |
| p0 = Radius * [cos(a), sin(a)]; | |
| p1 = (Radius + TickOrient*tick[1]) * [cos(a), sin(a)]; | |
| goto(p0); | |
| move([-,-,EngraveZ]); | |
| move(p1); | |
| goto([-,-,TravelZ]); | |
| } | |
| feedrate(TextSpeed); // draw scale values | |
| local lrad = Radius + TickOrient * (TickMajor + TickGap); | |
| if (TickOrient == INWARD) { | |
| if (LabelOrient == INWARD) { | |
| lrad -= ScaleTextSize.y; // inward ticks + inward labels = offset inward | |
| } | |
| } | |
| else { | |
| if (LabelOrient == OUTWARD) { | |
| lrad += ScaleTextSize.y; // outward ticks + outward labels = offset outward | |
| } | |
| } | |
| ticklist = UnitOnly ? [TickLabels[0]] : TickLabels; | |
| local ltext,lpath,tpa; | |
| foreach(ticklist; tick) { | |
| ltext = to_string(Decade * to_int(tick)); | |
| lpath = scale(typeset(ltext,TextFont),ScaleTextSize); | |
| a = UnitAngle + ArcLength * log10(tick); | |
| tpa = ArcText(lpath,[0mm,0mm],lrad,a,TEXT_CENTERED,LabelOrient); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| } | |
| } | |
| //—– | |
| // Mark key locations | |
| function MarkPivot() { | |
| comment("Mark center point"); | |
| feedrate(ScaleSpeed); | |
| if (TRUE) { | |
| goto([-,-,SafeZ]); | |
| goto([PivotOD/2,0,-]); | |
| move([-,-,EngraveZ]); | |
| circle_cw([0,0]); // outline pivot | |
| move([-PivotOD/2,0,-]); // draw X line | |
| goto([-,-,TravelZ]); | |
| goto([0,PivotOD/2,-]); | |
| move([-,-,EngraveZ]); | |
| move ([0,-PivotOD/2,-]); // draw Y line | |
| goto([-,-,TravelZ]); | |
| } | |
| } | |
| //—– | |
| // Draw attribution | |
| function DrawAttribution(AttribRad) { | |
| comment("Attribution at: ",AttribRad); | |
| feedrate(TextSpeed); | |
| local tp,tpa; | |
| if (Legend1) { | |
| tp = scale(typeset(Legend1,TextFont),TitleTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],AttribRad,0deg,TEXT_CENTERED,OUTWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| } | |
| if (Legend2) { | |
| tp = scale(typeset(Legend2,TextFont),TitleTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],AttribRad,180deg,TEXT_CENTERED,OUTWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| } | |
| if (FALSE) { // test code to verify ArcText | |
| comment("ArcText test"); | |
| ctr = [0mm,0mm]; | |
| tp = scale(typeset("Right Inward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,30mm,45deg,TEXT_RIGHT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| tp = scale(typeset("Right Outward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,30mm,45deg,TEXT_RIGHT,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| tp = scale(typeset("Center Inward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,20mm,45deg,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| tp = scale(typeset("Center Outward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,20mm,45deg,TEXT_CENTERED,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| tp = scale(typeset("Left Inward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,10mm,45deg,TEXT_LEFT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| tp = scale(typeset("Left Outward",TextFont),ScaleTextSize); | |
| tpa = ArcText(tp,ctr,10mm,45deg,TEXT_LEFT,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| goto([0mm,0mm,-]); | |
| move([40mm,40mm,-]); | |
| } | |
| if (FALSE) { // test code to verify RadialText | |
| comment("RadialText test"); | |
| ctr = [0mm,0mm]; | |
| r = 20mm; | |
| a = 0deg; | |
| tp = scale(typeset("Left Inward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_LEFT,INWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| tp = scale(typeset("Left Outward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_LEFT,OUTWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| a = 90deg; | |
| tp = scale(typeset("Right Inward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_RIGHT,INWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| tp = scale(typeset("Right Outward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_RIGHT,OUTWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| a = 180deg; | |
| tp = scale(typeset("Center Inward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_CENTERED,INWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| tp = scale(typeset("Center Outward",TextFont),LegendTextSize); | |
| tpr = RadialText(tp,ctr,r,a,TEXT_CENTERED,OUTWARD); | |
| feedrate(TextSpeed); | |
| engrave(tpr,TravelZ,EngraveZ); | |
| a = 270deg; | |
| RadialLegend("Offset to radius",ctr,r,a,TEXT_CENTERED,INWARD,-0.5); | |
| goto(ctr); | |
| move([0,-2*r,EngraveZ]); | |
| goto([r,0mm,-]); | |
| circle_cw(ctr); | |
| } | |
| } | |
| //—————————————————————————– | |
| // Deck Engraving | |
| //———- | |
| // Engrave bottom deck | |
| function EngraveBottom() { | |
| // Mark center pivot | |
| MarkPivot(); | |
| comment("Inductance scale"); | |
| Radius = DeckRad – ScaleHeight; | |
| MinLog = -9; | |
| MaxLog = 6; | |
| Arc = -ScaleArc; | |
| dec = 1; | |
| offset = 0deg; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE); | |
| r = Radius + TickMajor + 2*TickGap + LegendTextSize.y; | |
| logval = MinLog + 1.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("nH – nanohenry x10^-9",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("μH – microhenry x10^-6",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("mH – millihenry x10^-3",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("H – henry",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("kH – kilohenry x10^3",r,a,INWARD); | |
| r = Radius + TickMajor + TickGap; | |
| logval = MinLog – ScaleExdent; // scale identifiers | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("L Scale →",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MaxLog + ScaleExdent; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("← L Scale",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| comment("Inductive frequency scale"); | |
| Radius = DeckRad – 2*ScaleHeight; | |
| MinLog = 0; | |
| MaxLog = 9; | |
| Arc = 2*ScaleArc; // double-length scale for square roots | |
| dec = 1; | |
| offset = -(18 * ScaleArc – Scale2Pi); // using 18 degree arc length | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleWide,OUTWARD,a,Arc,dec,OUTWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleWide,OUTWARD,a,Arc,1000,OUTWARD,TRUE); | |
| feedrate(TextSpeed); // draw prefix legends | |
| r = Radius + TickMajor + 2*TickGap + 2*LegendTextSize.y; | |
| logval = MinLog + 0.5; | |
| for (i = 0; i < 3; i++) { | |
| a = offset + (i + logval) * Arc; | |
| ArcLegend("Hz – hertz",r,a,OUTWARD); | |
| } | |
| for (i = 3; i < 6; i++) { | |
| a = offset + (i + logval) * Arc; | |
| ArcLegend("kHz – kilohertz x10^3",r,a,OUTWARD); | |
| } | |
| for (i = 6; i < 9; i++) { | |
| a = offset + (i + logval) * Arc; | |
| ArcLegend("MHz – megahertz x10^6",r,a,OUTWARD); | |
| } | |
| r = Radius + TickMajor + TickGap + LegendTextSize.y; | |
| logval = MinLog – 0.5; // scale identifier | |
| a = offset + logval * Arc; | |
| ArcLegend("←——- FL Scale ——-→",r,a,OUTWARD); | |
| comment("Inductive TC / Risetime scale"); | |
| Radius = DeckRad – 3*ScaleHeight; | |
| MinLog = -12; | |
| MaxLog = 3; | |
| Arc = -ScaleArc; | |
| dec = 1; | |
| offset = -TauAngle; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE); | |
| feedrate(TextSpeed); // prefix legends | |
| r = Radius + TickMajor + 2*TickGap + LegendTextSize.y; | |
| logval = MinLog + 1.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("ps – picosecond x10^-12",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("ns – nanosecond x10^-9",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("μs – microsecond x10^-6",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("ms – millisecond x10^-3",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("s – second",r,a,INWARD); | |
| r = Radius + TickMajor + TickGap; | |
| logval = MinLog – ScaleExdent; // scale identifiers | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("τL Scale →",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MaxLog + ScaleExdent; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("← τL Scale",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| //—– | |
| // Add construction notes | |
| comment("Attribution begins"); | |
| r = DeckTopOD/2 – 2*ScaleHeight – WindowHeight; | |
| DrawAttribution(r); | |
| if (FALSE) { | |
| t = "Disk OD: " + to_string(DeckBottomOD) + " " + | |
| to_string(DeckMiddleOD) + " " + | |
| to_string(DeckTopOD) + " mm"; | |
| tp = scale(typeset(t,TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,90deg,TEXT_CENTERED,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| } | |
| goto([-,-,SafeZ]); // done, so get out of the way | |
| goto([0,0,-]); | |
| comment("Bottom deck ends"); | |
| } | |
| //———- | |
| // Engrave middle deck | |
| function EngraveMiddle() { | |
| // Mark center pivot | |
| MarkPivot(); | |
| comment("Capacitance scale"); | |
| Radius = DeckRad; | |
| MinLog = -15; | |
| MaxLog = 0; | |
| Arc = ScaleArc; | |
| dec = 1; | |
| offset = 0deg; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,INWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,1000,INWARD,TRUE); | |
| r = Radius – ScaleHeight + TickGap; | |
| logval = MinLog + 1.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("fF – femtofarad x10^-15",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("pF – picofarad x10^-12",r,a,INWARD); | |
| logval += 2.5; // offset for L/R window; | |
| a = offset + logval * Arc; | |
| ArcLegend("nF – nanofarad x10^-9",r,a,INWARD); | |
| logval += 4; // … likewise | |
| a = offset + logval * Arc; | |
| ArcLegend("μF – microfarad x10^-6",r,a,INWARD); | |
| logval += 2.5; // … restore normal spacing | |
| a = offset + logval * Arc; | |
| ArcLegend("mF – millifarad x10^-3",r,a,INWARD); | |
| r = Radius – ScaleHeight – TickGap – LegendTextSize.y; // into blank space | |
| logval = MinLog; // scale identifiers | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("←— C Scale",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MinLog + 6; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("←— C Scale —→",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MaxLog; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("C Scale —→",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| comment("Capacitive TC / risetime scale"); | |
| Radius = DeckRad – 4*ScaleHeight; | |
| MinLog = -12; | |
| MaxLog = 3; | |
| Arc = ScaleArc; | |
| dec = 1; | |
| offset = 3 * ScaleArc; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE); | |
| r = Radius + TickMajor + 2*TickGap + LegendTextSize.y; | |
| logval = MinLog + 1.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("ps – picosecond x10^-12",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("ns – nanosecond x10^-9",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("μs – microsecond x10^-6",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("ms – millisecond x10^-3",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("s – second",r,a,INWARD); | |
| r = Radius + TickMajor + TickGap; | |
| logval = MinLog – ScaleExdent; // scale identifiers | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("← τC Scale",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MaxLog + ScaleExdent; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("τC Scale →",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| logval = MinLog – 2.5; | |
| a = offset + logval * Arc; | |
| tp = scale(typeset("←— τC Scale —→",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| comment("Inductive frequency scale legend"); | |
| r = DeckRad – ScaleHeight – ScaleTextSize.y; | |
| a = 58deg; // arbitrary text placement | |
| ArcLegend("FL Scale",r,a,OUTWARD); | |
| comment("Index for resonance calculations"); | |
| Index = -(18*ScaleArc + Scale2Pi); // negative to read reciprocal of product | |
| r = DeckRad – 1.5*ScaleHeight + 0.5*LegendTextSize.y; | |
| ArcLegend("Frequency",r,Index,OUTWARD); | |
| r = DeckRad – ScaleHeight – LegendTextSize.y; | |
| ArcLegend("⇑",(r – TickGap),Index,INWARD); | |
| r = DeckRad – 2*ScaleHeight + LegendTextSize.y; | |
| ArcLegend("⇑",(r + TickGap),Index,OUTWARD); | |
| r0 = DeckRad – ScaleHeight; | |
| r1 = r0 – TickMajor; | |
| goto(r0 * [cos(Index),sin(Index)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(Index),sin(Index)]); | |
| goto([-,-,TravelZ]); | |
| r0 = DeckRad – 2*ScaleHeight; | |
| r1 = r0 + TickMajor; | |
| goto(r0 * [cos(Index),sin(Index)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(Index),sin(Index)]); | |
| goto([-,-,TravelZ]); | |
| //—– | |
| // Draw the attribution | |
| comment("Attribution begins"); | |
| r = DeckTopOD/2 – 2*ScaleHeight – WindowHeight; | |
| DrawAttribution(r); | |
| goto([-,-,SafeZ]); // done, so get out of the way | |
| goto([0,0,-]); | |
| comment("Middle deck ends"); | |
| } | |
| //———- | |
| // Engrave top deck | |
| function EngraveTop() { | |
| // Mark center pivot | |
| MarkPivot(); | |
| comment("Resistance scale"); | |
| Radius = DeckRad; | |
| MinLog = -1; | |
| MaxLog = 8; | |
| Arc = -ScaleArc; | |
| dec = 100; | |
| offset = 0deg; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,INWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,100,INWARD,TRUE); | |
| r = Radius – ScaleHeight + TickGap; | |
| logval = MinLog + 0.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("mΩ – milliohm",r,a,INWARD); | |
| logval = MinLog + 2.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("Ω – ohm",r,a,INWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("kΩ – kilohm x10^3",r,a,INWARD); | |
| logval = MaxLog – 1; | |
| a = offset + logval * Arc; | |
| ArcLegend("MΩ – megohm x10^6",r,a,INWARD); | |
| r = Radius – ScaleHeight – TickGap – LegendTextSize.y; | |
| logval = MinLog + 4; | |
| a = offset + logval * Arc; | |
| ArcLegend("←— R XC XL Scale —→",r,a,INWARD); | |
| comment("Capacitive frequency scale"); | |
| Radius = DeckRad; | |
| MinLog = 0; | |
| MaxLog = 9; | |
| Arc = ScaleArc; | |
| dec = 1; | |
| offset = 18 * -ScaleArc; | |
| for (logval = MinLog; logval < MaxLog; logval++) { | |
| a = offset + logval * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,OUTWARD,FALSE); | |
| dec = (dec == 100) ? 1 : 10 * dec; | |
| } | |
| a = offset + MaxLog * Arc; | |
| DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,1000,OUTWARD,TRUE); | |
| r = Radius – (TickMajor + 2*TickGap + LegendTextSize.y); | |
| logval = MinLog + 1.5; | |
| a = offset + logval * Arc; | |
| ArcLegend("Hz – hertz",r,a,OUTWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("kHz – kilohertz x10^3",r,a,OUTWARD); | |
| logval += 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("MHz – megahertz x10^6",r,a,OUTWARD); | |
| r = Radius – ScaleHeight – TickGap – LegendTextSize.y; | |
| logval = MaxLog – 3; | |
| a = offset + logval * Arc; | |
| ArcLegend("←— FC Scale —→",r,a,OUTWARD); | |
| comment("RC Circuit Pointers"); | |
| local ctr = [0mm,0mm]; | |
| r0 = DeckRad – 2*ScaleHeight; | |
| r1 = r0 – ScaleHeight; | |
| a = -(17 * ScaleArc); | |
| goto(r0 * [cos(a),sin(a)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(a),sin(a)]); | |
| goto([-,-,TravelZ]); | |
| ArcLegend("⇓",(r0 – TickGap),a,OUTWARD); | |
| RadialLegend(" Time Constant",ctr,r1,a,TEXT_LEFT,INWARD,-0.5); | |
| a += ScaleRT; | |
| goto(r0 * [cos(a),sin(a)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(a),sin(a)]); | |
| goto([-,-,TravelZ]); | |
| ArcLegend("⇓",(r0 – TickGap),a,OUTWARD); | |
| RadialLegend(" Risetime",ctr,r1,a,TEXT_LEFT,INWARD,-0.5); | |
| a -= ScaleRT/2; | |
| RadialLegend(" RC",ctr,r0 – 2*ScaleTextSize.y,a,TEXT_LEFT,INWARD,-0.5); | |
| comment("L/R Circuit Pointers"); | |
| r0 = DeckRad; | |
| r1 = r0 – ScaleHeight; | |
| a = -TauAngle; | |
| goto(r0 * [cos(a),sin(a)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(a),sin(a)]); | |
| goto([-,-,TravelZ]); | |
| ArcLegend("⇓",(r0 – TickGap),a,OUTWARD); | |
| RadialLegend("Time Constant ",ctr,r1,a,TEXT_RIGHT,OUTWARD,-0.5); | |
| a -= ScaleRT; | |
| goto(r0 * [cos(a),sin(a)]); | |
| move([-,-,EngraveZ]); | |
| move(r1 * [cos(a),sin(a)]); | |
| goto([-,-,TravelZ]); | |
| ArcLegend("⇓",(r0 – TickGap),a,OUTWARD); | |
| RadialLegend("Risetime ",ctr,r1,a,TEXT_RIGHT,OUTWARD,-0.5); | |
| a += ScaleRT/2; | |
| RadialLegend("L/R ",ctr,r0 – 2*ScaleTextSize.y,a,TEXT_RIGHT,OUTWARD,-0.5); | |
| comment("Title and logo"); | |
| feedrate(TextSpeed); | |
| r = 0.65*DeckRad; | |
| tp = scale(typeset("Homage",TextFont),TitleTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r -= 1.5*TitleTextSize.y; | |
| tp = scale(typeset("Tektronix",TextFont),TitleTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r -= 1.5*TitleTextSize.y; | |
| tp = scale(typeset("Circuit Computer",TextFont),TitleTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r -= 1.5*TitleTextSize.y; | |
| if (TRUE) { | |
| tp = scale(typeset("TEK 003-023",TextFont),LegendTextSize); | |
| } | |
| else { | |
| tp = scale(typeset("https://vintagetek.org/tektronix-circuit-computer/",TextFont),LegendTextSize); | |
| } | |
| tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r = 0.3*DeckRad; | |
| a = TitleAngle + 180deg; | |
| tp = scale(typeset("Ed Nisley",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r += 1.5*TitleTextSize.y; | |
| tp = scale(typeset("KE4ZNU",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| r += 1.5*TitleTextSize.y; | |
| tp = scale(typeset("softsolder.com",TextFont),LegendTextSize); | |
| tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD); | |
| engrave(tpa,TravelZ,EngraveZ); | |
| goto([-,-,SafeZ]); // done, so get out of the way | |
| goto([0,0,-]); | |
| comment("Top deck ends"); | |
| } | |
| //———- | |
| // Engrave cursor hairline | |
| function EngraveCursor() { | |
| // Mark center pivot | |
| MarkPivot(); | |
| comment("Cursor hairline"); | |
| feedrate(ScaleSpeed); | |
| goto([-,-,TravelZ]); | |
| repeat(2) { | |
| goto([DeckTopOD/2 – 2.25*ScaleHeight,0,-]); // slight overlap on arrows | |
| move([-,-,EngraveZ]); | |
| move([DeckBottomOD/2 + ScaleHeight,0,-]); | |
| goto([-,-,TravelZ]); | |
| } | |
| goto([-,-,SafeZ]); // done, so get out of the way | |
| goto([0,0,-]); | |
| } | |
| //—————————————————————————– | |
| // Deck milling | |
| // Assumes adhesive clamping to avoid protrusions above work area | |
| //—– | |
| // Bottom deck | |
| function MillBottom() { | |
| comment("Mill Bottom"); | |
| feedrate(KnifeSpeed); | |
| goto([-,-,TravelZ]); | |
| local r = PivotOD/2; | |
| goto([0,r,-]); // entry move to align knife | |
| arc_cw([r,0,0],r); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| circle_cw([0,0]); | |
| arc_cw([0,-r],r); // cut past entry point | |
| goto([-,-,TravelZ]); | |
| r = DeckRad; | |
| local a = 5deg; | |
| local p0 = r * [cos(a),sin(a),-]; // entry point | |
| local p1 = r * [cos(-a),sin(-a),-]; // exit point | |
| goto(p0); | |
| arc_cw([r,0,0],r); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| circle_cw([0,0]); // cut circle | |
| arc_cw(p1,r); // cut past entry point | |
| goto([-,-,TravelZ]); | |
| goto([0,0,-]); | |
| goto([-,-,SafeZ]); | |
| } | |
| //—– | |
| // Middle deck | |
| function MillMiddle() { | |
| FLNotchArc = 85deg; // width exposing FL scale | |
| FLRampArc = 7deg; // … width of entry & exit ramps | |
| FLNotchOffset = 2deg; // … start angle from 0° | |
| comment("Mill Middle"); | |
| feedrate(KnifeSpeed); | |
| goto([-,-,TravelZ]); | |
| local r = PivotOD/2; | |
| goto([0,r,-]); // entry move to align knife | |
| arc_cw([r,0,0],r); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| circle_cw([0,0]); | |
| arc_cw([0,-r],r); // cut past entry point | |
| goto([-,-,TravelZ]); | |
| // FL scale notch | |
| local r0 = DeckRad; | |
| local a0 = FLNotchOffset; // end of notch ramp | |
| local p0 = r0 * [cos(a0),sin(a0),-]; | |
| local a1 = a0 + FLNotchArc; // start of notch ramp | |
| local p1 = r0 * [cos(a1),sin(a1),-]; | |
| goto(p0); | |
| arc_cw([r0,0,0],r0); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| arc_cw(p1,-r0); // largest arc to start of notch | |
| local r1 = r0 – ScaleHeight; | |
| local a3 = a1 – FLRampArc; // start of notch base | |
| local p3 = r1 * [cos(a3),sin(a3),-]; | |
| local a4 = a0 + FLRampArc; // end of notch base | |
| local p4 = r1 * [cos(a4),sin(a4),-]; | |
| move(p3); | |
| arc_cw(p4,r1); // smallest arc on notch base | |
| move(p0); // end of notch ramp | |
| arc_cw([r0,0,-],r0); // round off corner | |
| local p5 = r0 * [cos(-a0),sin(-a0),-]; // small overtravel past entry point | |
| arc_cw(p5,r0); | |
| goto([-,-,TravelZ]); | |
| // L/R τ and RT Scale window | |
| local WindowArc = 39deg; | |
| ac = -6 * ScaleArc; // center of window arc | |
| r0 = DeckRad – ScaleHeight; // outer | |
| r1 = DeckRad – 2 * ScaleHeight; // inner | |
| aw = WindowArc – to_deg(atan(ScaleHeight,(r0 + r1)/2)); // window arc minus endcaps | |
| p0 = r0 * [cos(ac + aw/2),sin(ac + aw/2),-]; // endcap entry & exit | |
| p1 = r0 * [cos(ac – aw/2),sin(ac – aw/2),-]; | |
| p2 = r1 * [cos(ac – aw/2),sin(ac – aw/2),-]; | |
| p3 = r1 * [cos(ac + aw/2),sin(ac + aw/2),-]; | |
| goto(p3); // cut entry point | |
| arc_cw(p0 +| [-,-,0],ScaleHeight/2); // blade enters surface | |
| move([-,-,KnifeZ]); // apply pressure | |
| arc_cw(p1,r0); // smallest arc | |
| arc_cw(p2,ScaleHeight/2); // half a circle | |
| arc_ccw(p3,r1); | |
| arc_cw(p0,ScaleHeight/2); | |
| arc_cw(p1 +| [-,-,TravelZ],r0); // exit from cut | |
| goto([0,0,-]); | |
| goto([-,-,SafeZ]); | |
| } | |
| //—– | |
| // Top deck | |
| function MillTop() { | |
| comment("Mill Top"); | |
| feedrate(KnifeSpeed); | |
| goto([-,-,TravelZ]); | |
| local r = PivotOD/2; | |
| goto([0,r,-]); // entry move to align knife | |
| arc_cw([r,0,0],r); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| circle_cw([0,0]); | |
| arc_cw([0,-r],r); // cut past entry point | |
| goto([-,-,TravelZ]); | |
| r = DeckRad; | |
| local a = 5deg; | |
| local p0 = r * [cos(a),sin(a),-]; // entry point | |
| local p1 = r * [cos(-a),sin(-a),-]; // exit point | |
| goto(p0); | |
| arc_cw([r,0,0],r); // blade enters surface | |
| move([-,-,KnifeZ]); // apply cutting force | |
| circle_cw([0,0]); // cut circle | |
| arc_cw(p1,r); // cut past entry point | |
| goto([-,-,TravelZ]); | |
| // RC τ and RT Scale window | |
| local WindowArc = 54deg; | |
| local ac = -17 * ScaleArc + ScaleRT/2; // center of window arc | |
| local r0 = DeckRad – ScaleHeight; // outer | |
| local r1 = DeckRad – 2 * ScaleHeight; // inner | |
| local aw = WindowArc – to_deg(atan(ScaleHeight,(r0 + r1)/2)); // window arc minus endcaps | |
| p0 = r0 * [cos(ac + aw/2),sin(ac + aw/2),-]; | |
| p1 = r0 * [cos(ac – aw/2),sin(ac – aw/2),-]; | |
| local p2 = r1 * [cos(ac – aw/2),sin(ac – aw/2),-]; | |
| local p3 = r1 * [cos(ac + aw/2),sin(ac + aw/2),-]; | |
| goto(p3); | |
| arc_cw(p0 +| [-,-,0],ScaleHeight/2); // blade enters surface | |
| move([-,-,KnifeZ]); // apply pressure | |
| arc_cw(p1,r0); // smallest arc | |
| arc_cw(p2,ScaleHeight/2); // half a circle | |
| arc_ccw(p3,r1); | |
| arc_cw(p0,ScaleHeight/2); | |
| arc_cw(p1 +| [-,-,TravelZ],r0); // exit from cut | |
| goto([0,0,-]); | |
| goto([-,-,SafeZ]); | |
| } | |
| //———- | |
| // Cut cursor outline | |
| CursorHubOD = 1.0in; | |
| CursorTipWidth = to_inch(9.0/16.0); | |
| CursorTipRadius = to_inch(1.0/16.0); | |
| function MillCursor() { | |
| // Mark center pivot | |
| MarkPivot(); | |
| comment("Cursor outline"); | |
| local dr = DeckBottomOD/2; | |
| local hr = CursorHubOD/2; | |
| local a = atan(hr – CursorTipWidth/2,dr); // rough & ready approximation | |
| local p0 = hr * [sin(a),cos(a),-]; // upper tangent point on hub | |
| local c1 = [dr – CursorTipRadius,CursorTipWidth/2 – CursorTipRadius*cos(a),-]; | |
| local p1 = c1 + [CursorTipRadius*sin(a),CursorTipRadius*cos(a),-]; | |
| local p2 = c1 + [CursorTipRadius,0,-]; // around tip radius | |
| feedrate(KnifeSpeed); | |
| goto([-,-,TravelZ]); | |
| goto([-hr,0,-]); | |
| move([-,-,EngraveZ]); | |
| repeat(3) { | |
| arc_cw(p0,hr); | |
| move(p1); | |
| arc_cw(p2,CursorTipRadius); | |
| move([p2.x,-p2.y,-]); | |
| arc_cw([p1.x,-p1.y,-],CursorTipRadius); | |
| move([p0.x,-p0.y,-]); | |
| arc_cw([-hr,0,-],hr); | |
| } | |
| goto([-,-,SafeZ]); // done, so get out of the way | |
| goto([0,0,-]); | |
| } | |
| //—————————————————————————– | |
| // The actual machining sequences! | |
| //—– | |
| // Bottom Deck | |
| if (SelectPart == "Bottom") { | |
| DeckOD = DeckBottomOD; | |
| DeckRad = DeckOD / 2; | |
| comment(" OD: ",DeckOD); | |
| if (Operation == "Engrave") { | |
| EngraveBottom(); | |
| } | |
| elif (Operation == "Mill") { | |
| MillBottom(); | |
| } | |
| else { | |
| error("Invalid operation: ",Operation); | |
| } | |
| } | |
| //—— | |
| // Middle Deck | |
| if (SelectPart == "Middle") { | |
| DeckOD = DeckMiddleOD; | |
| DeckRad = DeckOD / 2; | |
| comment(" OD: ",DeckOD); | |
| if (Operation == "Engrave") { | |
| EngraveMiddle(); | |
| } | |
| elif (Operation == "Mill") { | |
| MillMiddle(); | |
| } | |
| else { | |
| error("Invalid operation: ",Operation); | |
| } | |
| } | |
| //—– | |
| // Top Deck | |
| if (SelectPart == "Top") { | |
| DeckOD = DeckTopOD; | |
| DeckRad = DeckOD / 2; | |
| comment(" OD: ",DeckOD); | |
| if (Operation == "Engrave") { | |
| EngraveTop(); | |
| } | |
| elif (Operation == "Mill") { | |
| MillTop(); | |
| } | |
| else { | |
| error("Invalid operation: ",Operation); | |
| } | |
| } | |
| //—– | |
| // Cursor | |
| if (SelectPart == "Cursor") { | |
| DeckOD = DeckBottomOD; | |
| DeckRad = DeckOD / 2; | |
| comment(" OD: ",DeckOD); | |
| if (Operation == "Engrave") { | |
| EngraveCursor(); | |
| } | |
| elif (Operation == "Mill") { | |
| MillCursor(); | |
| } | |
| else { | |
| error("Invalid operation: ",Operation); | |
| } | |
| } |
| #!/bin/bash | |
| # Tek Circuit Computer Engraving | |
| # Ed Nisley KE4ZNU – 2019-11 | |
| #OD='BaseOD=118mm' # CD = 120 | |
| #OD='BaseOD=93mm' # hard drive = 95mm | |
| #EZ='EngraveZ=-5mm' # Engraving Z | |
| Flags='-P 3 –pedantic' # avoid leading hyphen gotcha | |
| # Set these to match your file layout | |
| ProjPath='/mnt/bulkdata/Project Files/Tektronix Circuit Computer/Firmware' | |
| LibPath='/opt/gcmc/library' | |
| Prolog='prolog.gcmc' | |
| Epilog='epilog.gcmc' | |
| ScriptPath=$ProjPath | |
| Script='Tek Circuit Computer.gcmc' | |
| #—– | |
| # params: deck operation | |
| function Runit { | |
| fn=TekCC-${1}-${2}.ngc | |
| echo "(File: "$fn")" > $fn | |
| sel='SelectPart="'$1'"' | |
| op='Operation="'$2'"' | |
| echo Output: $fn | |
| echo " "$sel | |
| echo " "$op | |
| if [ -e $fn ] | |
| then rm -f $fn | |
| fi | |
| gcmc -D "$OD" -D "$EZ" \ | |
| -D "$sel" -D "$op" $Flags \ | |
| –include "$LibPath" –prologue "$Prolog" –epilogue "$Epilog" \ | |
| "$ScriptPath"/"$Script" >> "$fn" | |
| } | |
| #—– | |
| Runit Bottom Engrave | |
| Runit Bottom Mill | |
| Runit Middle Engrave | |
| Runit Middle Mill | |
| Runit Top Engrave | |
| Runit Top Mill | |
| Runit Cursor Engrave | |
| Runit Cursor Mill |
Comments
3 responses to “Homage Tektronix Circuit Computer: Pen Plotter Version”
My roommate in college (pre-HP35 era) used a circular general purpose slide “rule”, er, disk. Somehow, nobody ever wanted to borrow it.
In keeping with the climate requirements of the Midwest, I used an aluminum Pickett. Most Post(tm) sliderule owners had fun* adjusting them each season.
(*) For various definitions of the word.
The rotors on my circular Pickett were always too hard to turn. I should see if a dose of modern dry silicone lube would improve its disposition. Ya never know when you might need a slipstick, but not having used the thing in nigh onto half a century, it’s not urgent …
[…] the script running the GCMC source code to prepend the logo G-Code to the main file and it all comes out in one […]