-
Homage Tektronix Circuit Computer: Pen Plotter Version
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:

Homage Tektronix Circuit Computer – green on white laminated 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:

Pilot V5RT holder – installed 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:

Tek CC – MPCNC drag knife 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:

Tek Circuit Computer – cursor hairline 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:

Tek CC bottom – ABS 160g 2400mm-min Engraving the decks on hard drive platters doesn’t count:

Tek CC – bottom deck – scaled to HD platter 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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters// 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); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters#!/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