The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

The New Hotness

  • 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
    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
    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
    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
    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
    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
    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:

    // 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/&quot;,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
    view raw TekCC.sh hosted with ❤ by GitHub