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.

Tag: Improvements

Making the world a better place, one piece at a time

  • CNC 3018-Pro: CD Fixture Probe Camera Target

    Taping the CD fixture to the CNC 3018-Pro’s raised platform solves the repeatability problem by putting the CD at a fixed location relative to the machine’s Home coordinates. The next step puts the XY=0 coordinate origin at the exact center of the platter, so the pattern comes out exactly centered on the disc:

    CNC 3018-Pro - CD fixture
    CNC 3018-Pro – CD fixture

    The fixture has a central boss:

    Platter Fixtures - CD on 3018 - tape flange
    Platter Fixtures – CD on 3018 – tape flange

    The blue boss centers the CD’s hub hole, the red plateau supports the disc, and the white background lies 5 mm below the CD’s upper surface:

    CNC 3018-Pro - CD holder target
    CNC 3018-Pro – CD holder target

    Yup, red and blue Sharpies FTW.

    The bCNC probe camera image includes two faint cyan rings centered on the crosshair:

    CNC 3018-Pro - bCNC probe camera - red-blue CD target
    CNC 3018-Pro – bCNC probe camera – red-blue CD target

    Set the diameter to 15 mm (or a bit less), center the outer ring on the hub hole = the border between blue & red, set XY=0, and it’s within maybe ±0.1 mm of the true center.

    Done!

  • bCNC Probe Camera Calibration

    I’m sure I’ll do this again some time …

    Focus the camera at whatever distance needed to clear the longest tooling you’ll use or, at least, some convenient distance from the platform. You must touch off Z=0 at the surface before using bCNC’s probe camera alignment, because it will move the camera to the preset focus distance.

    Align the camera’s optical axis perpendicular to the table by making it stare into a mirror flat on the platform, then tweaking the camera angles until the crosshair centers on the reflected lens image. This isn’t dead centered, but it’s pretty close:

    CNC 3018-Pro - bCNC Probe Camera - collimation - detail
    CNC 3018-Pro – bCNC Probe Camera – collimation – detail

    The camera will be focused on the mirror, not the reflection, as you can tell by the in-focus crud on the mirror. Whenever you focus the lens, you’ll probably move the optical axis, so do the best you can with the fuzzy image.

    You can adjust small misalignments with the Haircross (seems backwards to me) Offset values.

    A cheap camera’s lens barrel may not be aligned with its optical axis, giving the lens a jaunty tilt when it’s correctly set up:

    CNC 3018-Pro - Engraving - taped
    CNC 3018-Pro – Engraving – taped

    With the camera focus set correctly, calibrate the camera Offset from the tool (a.k.a. Spindle) axis:

    • Put a pointy tool at XY=0
    • Touch off Z=0 on a stack of masking tape
    • Put a dent in the tape with the bit
    • Move to the camera’s focused Z level
    • Make the dent more conspicuous with a Sharpie, as needed
    • Register the spindle location
    • Jog to center the crosshair on the dent
    • Register the camera location

    Calibrate the Crosshair ring diameter thusly:

    • Put an object with a known size on the platform
    • Touch off Z=0 at its surface
    • Move to the camera’s focused Z level
    • Set the Crosshair diameter equal to the known object size
    • Adjust the Scale value to make the Crosshair overlay reality

    For example, calibrating the diameter to 10 mm against a shop scale:

    CNC 3018-Pro Probe Camera - scale factor - detail
    CNC 3018-Pro Probe Camera – scale factor – detail

    At 10 mm above the CD, setting the camera’s resolution to 11.5 pixel/mm:

    CNC 3018-Pro - bCNC probe camera - settings
    CNC 3018-Pro – bCNC probe camera – settings

    Makes the outer circle exactly 15.0 mm in diameter to match the CD hub ring ID:

    CNC 3018-Pro - bCNC probe camera - red-blue CD target
    CNC 3018-Pro – bCNC probe camera – red-blue CD target

    I doubt anybody can find the pixel/mm value from first principles, so you must work backwards from an object’s actual size.

  • CNC 3018-Pro: Diamond Drag Engraving Test Disk

    The smaller and more rigid CNC 3018-Pro should be able to engrave text faster than the larger and rather springy MPCNC, which could engrave text at about 50 mm/min. This test pattern pushes both cutting depth and engraving speed to absurd values:

    Engraving Test Pattern - 2019-09-18
    Engraving Test Pattern – 2019-09-18

    Compile the GCMC source to generate G-Code, lash a CD / DVD to the platform (masking tape works fine), touch off the XY coordinates in the center, touch off Z=0 on the surface, then see what happens:

    CNC 3018-Pro - Engraving test pattern - curved text
    CNC 3018-Pro – Engraving test pattern – curved text

    The “engraving depth” translates directly into the force applied to the diamond point, because the spring converts displacement into force. Knowing the Z depth, you can calculate or guesstimate the force.

    Early results from the 3018 suggest it can engrave good-looking text about 20 times faster than the MPCNC:

    CNC 3018-Pro - Engraving - speeds
    CNC 3018-Pro – Engraving – speeds

    You must trade off speed with accuracy on your very own machine, as your mileage will certainly differ!

    The GCMC source code as a GitHub Gist:

    // Engraving test piece
    // Ed Nisley KE4ZNU – 2019-09
    //—–
    // Command line parameters
    // -D OuterDia=number
    if (!isdefined("OuterDia")) {
    OuterDia = 120mm – 2mm; // CD = 120, 3.5 inch drive = 95
    }
    OuterRad = OuterDia / 2.0;
    comment("Outer Diameter: ",OuterDia);
    comment(" Radius: ",OuterRad);
    //—–
    // Library routines
    include("tracepath.inc.gcmc");
    include("engrave.inc.gcmc");
    //—–
    // Bend text around an arc
    function ArcText(TextPath,Center,Radius,BaseAngle,Align) {
    PathLength = TextPath[-1].x;
    Circumf = 2*pi()*Radius;
    TextAngle = to_deg(360 * PathLength / Circumf);
    AlignAngle = BaseAngle + (Align == "Left" ? 0 :
    Align == "Center" ? -TextAngle / 2 :
    Align == "Right" ? -TextAngle :
    0);
    ArcPath = {};
    foreach(TextPath; pt) {
    if (!isundef(pt.x) && !isundef(pt.y) && isundef(pt.z)) { // XY motion, no Z
    r = Radius – pt.y;
    a = 360deg * (pt.x / Circumf) + AlignAngle;
    ArcPath += {[r*cos(a) + Center.x, r*sin(a) + Center.y,-]};
    }
    elif (isundef(pt.x) && isundef(pt.y) && !isundef(pt.z)) { // no XY, Z up/down
    ArcPath += {pt};
    }
    else {
    error("Point is not pure XY or pure Z: " + to_string(pt));
    }
    }
    return ArcPath;
    }
    //—–
    // Set up for drawing
    SafeZ = 10.0mm; // above clamps and screws
    TravelZ = 1.0mm; // above workpiece
    PlotZ = -0.5mm; // tune for best results
    TextSpeed = 1000mm; // intricate detail
    DrawSpeed = 2000mm; // smooth curves
    TextFont = FONT_HSANS_1_RS;
    TextSize = [2.0mm,2.0mm];
    TextLeading = 2*TextSize.y; // line spacing
    DiskCenter = [0mm,0mm]; // middle of the platter
    InnerDia = 40mm;
    InnerRad = InnerDia / 2.0;
    comment("Inner Diameter: ",InnerDia);
    comment(" Radius: ",InnerRad);
    NumRings = ceil((OuterRad – (InnerRad + TextLeading))/TextLeading); // number of rings to draw
    comment("Numer of rings: ",NumRings);
    if (1) {
    comment("Text Size begins");
    feedrate(TextSpeed);
    ts = "Text size: " + to_string(TextSize);
    tp = scale(typeset(ts,TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,OuterRad,90deg,"Left");
    engrave(tpa,TravelZ,PlotZ);
    }
    if (1) {
    comment("Depth variations begin");
    TextRadius = OuterRad;
    pz = 0.0mm;
    repeat(NumRings ; i) {
    comment(" depth: " + to_string(pz));
    feedrate(TextSpeed);
    ts = "Depth: " + to_string(pz) + " at " + to_string(TextSpeed) + "/min";
    tp = scale(typeset(ts,TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,TextRadius,-5deg,"Right");
    engrave(tpa,TravelZ,pz);
    feedrate(DrawSpeed);
    goto([0,-TextRadius,-]);
    move([-,-,pz]);
    arc_ccw([-TextRadius,0,-],-TextRadius);
    goto([-,-,TravelZ]);
    feedrate(TextSpeed);
    tp = scale(typeset("Rad: " + to_string(TextRadius),TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,TextRadius,180deg,"Right");
    engrave(tpa,TravelZ,PlotZ);
    TextRadius -= TextLeading;
    pz -= 0.10mm;
    }
    }
    if (1) {
    comment("Feedrate variations begin");
    TextRadius = OuterRad;
    ps = 250mm;
    repeat(NumRings ; i) {
    comment(" speed: " + to_string(ps) + "/min");
    feedrate(ps);
    ts = "Speed: " + to_string(ps) + "/min at " + to_string(PlotZ);
    tp = scale(typeset(ts,TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,TextRadius,5deg,"Left");
    engrave(tpa,TravelZ,PlotZ);
    TextRadius -= TextLeading;
    ps += 250mm;
    }
    }
    if (1) {
    comment("Off-center text arcs begin");
    feedrate(TextSpeed);
    tc = [-40mm/sqrt(2),-40mm/sqrt(2)]; // center point
    r = 3mm;
    s = [0.5mm,0.5mm];
    ts = "Radius: " + to_string(r) + " Size: " + to_string(s);
    tp = scale(typeset(ts,TextFont),s);
    tpa = ArcText(tp,tc,r,0deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    r = 5mm;
    s = [1.0mm,1.0mm];
    ts = "Radius: " + to_string(r) + " Size: " + to_string(s);
    tp = scale(typeset(ts,TextFont),s);
    tpa = ArcText(tp,tc,r,0deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    r = 8mm;
    s = [1.5mm,1.5mm];
    ts = "Radius: " + to_string(r) + " Size: " + to_string(s);
    tp = scale(typeset(ts,TextFont),s);
    tpa = ArcText(tp,tc,r,0deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    r = 15mm;
    s = [3.0mm,3.0mm];
    ts = "Radius: " + to_string(r) + " Size: " + to_string(s);
    tp = scale(typeset(ts,FONT_HSCRIPT_2),s);
    tpa = ArcText(tp,tc,r,0deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    }
    if (1) {
    comment("Attribution begins");
    feedrate(TextSpeed);
    tp = scale(typeset("Ed Nisley – KE4ZNU – softsolder.com",TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,15mm,0deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    tp = scale(typeset("Engraving Test Disc",TextFont),TextSize);
    tpa = ArcText(tp,DiskCenter,15mm,180deg,"Center");
    engrave(tpa,TravelZ,PlotZ);
    }
    goto([-,-,SafeZ]);
    goto([0mm,0mm,-]);
    comment("Done!");
    #!/bin/bash
    # Engraving test pattern generator
    # Ed Nisley KE4ZNU – 2019-08
    Diameter='OuterDia=116mm'
    Flags='-P 3 –pedantic'
    # Set these to match your file layout
    LibPath='/opt/gcmc/library'
    Prolog='/mnt/bulkdata/Project Files/CNC 3018-Pro Router/Patterns/gcmc/prolog.gcmc'
    Epilog='/mnt/bulkdata/Project Files/CNC 3018-Pro Router/Patterns/gcmc/epilog.gcmc'
    Script='/mnt/bulkdata/Project Files/CNC 3018-Pro Router/Patterns/Engraving Test.gcmc'
    ts=$(date +%Y%m%d-%H%M%S)
    fn='TestPattern_'${ts}'.ngc'
    echo Output: $fn
    rm -f $fn
    echo "(File: "$fn")" > $fn
    /opt/gcmc/src/gcmc -D $Diameter $Flags \
    –include "$LibPath" –prologue "$Prolog" –epilogue "$Epilog" \
    "$Script" >> $fn

  • CNC 3018-Pro: DRV8825 Drivers at the Edge of Madness

    Having previously concluded running the CNC 3018-Pro steppers from 12 V would let the DRV8825 chips provide better current control in Fast Decay mode at reasonable speeds, I wondered what effect a 24 V supply would have at absurdly high speeds with the driver in 1:8 microstep mode to reduce the IRQ rate.

    So, in what follows, the DRV8825 chip runs in 1:8 microstep mode with Fast Decay current control. You must apply some hardware hackage to the CAMTool V 3.3 board on the CNC 3018-Pro to use those modes.

    In all the scope pix, horizontal sync comes from the DRV8825 Home pulse in the top trace, with the current in the two windings of the X axis motor in the lower traces at 1 A/div. Because only the X axis is moving, the actual axis speed matches the programmed feed rate.

    Homework: figure out the equivalent two-axis-moving speed.

    The 12 V motor supply works well at 140 mm/min, with Fast Decay mode producing clean microstep current levels and transitions:

    3018 X - Fast - 12V - 140mm-min 1A-div
    3018 X – Fast – 12V – 140mm-min 1A-div

    The sine waves deteriorate into triangles around 1400 mm/min, suggesting this is about as fast as you’d want to go with a 12 V supply:

    3018 X - Fast - 12V - 1400mm-min 1A-div
    3018 X – Fast – 12V – 1400mm-min 1A-div

    Although the axis can reach 3000 mm/min, it’s obviously running well beyond its limits:

    3018 X - Fast - 12V - 3000mm-min 1A-div
    3018 X – Fast – 12V – 3000mm-min 1A-div

    The back EMF fights the 12 V supply to a standstill during most of the waveform, leaving only brief 500 mA peaks, so there’s no torque worth mentioning and terrible position control.

    Increasing the supply to 24 V, still with 1:8 microstepping and Fast Decay …

    At a nose-pickin’ slow 14 mm/min, Fast Decay mode looks rough, albeit with no missteps:

    3018 X - Fast - 24V - 14mm-min 1A-div
    3018 X – Fast – 24V – 14mm-min 1A-div

    At 140 mm/min, things look about the same:

    3018 X - Fast - 24V - 140mm-min 1A-div
    3018 X – Fast – 24V – 140mm-min 1A-div

    For completeness, a detailed look at the PWM current control waveforms at 140 mm/min:

    3018 X - Fast detail - 24V - 140mm-min 1A-div
    3018 X – Fast detail – 24V – 140mm-min 1A-div

    The dead-flat microstep in the middle trace happens when the current should be zero, which is comforting.

    At 1400 mm/min, where the 12 V waveforms look triangular, the 24 V supply has enough mojo to control the current, with increasing roughness and slight undershoots after the zero crossings:

    3018 X - Fast - 24V - 1400mm-min 1A-div
    3018 X – Fast – 24V – 1400mm-min 1A-div

    At 2000 mm/min, the DRV8825 is obviously starting to have trouble regulating the current against the increasing back EMF:

    3018 X - Fast - 24V - 2000mm-min 1A-div
    3018 X – Fast – 24V – 2000mm-min 1A-div

    At 2500 mm/min, the back EMF is taking control away from the DRV8825:

    3018 X - Fast - 24V - 2500mm-min 1A-div
    3018 X – Fast – 24V – 2500mm-min 1A-div

    The waveforms take on a distinct triangularity at 2700 mm/min:

    3018 X - Fast - 24V - 2700mm-min 1A-div
    3018 X – Fast – 24V – 2700mm-min 1A-div

    They’re fully triangular at 3000 mm/min:

    3018 X - Fast - 24V - 3000mm-min 1A-div
    3018 X – Fast – 24V – 3000mm-min 1A-div

    In round numbers, you’d expect twice the voltage to give you twice the speed for a given amount of triangularity, because the current rate-of-change varies directly with the net voltage. I love it when stuff works out!

    At that pace, the X axis carrier traverses the 300 mm gantry in 6 s, which is downright peppy compared to the default settings.

    Bottom lines: the CNC 3018-Pro arrives with a 24 V supply that’s too high for the DRV8825 drivers in Mixed Decay mode and the CAMTool V3.3 board’s hardwired 1:32 microstep mode limits the maximum axis speed. Correcting those gives you 3000 mm/min rapids with good-looking current waveforms.

    I’m reasonably sure engraving plastic and metal disks at 3000 mm/min is a Bad Idea™, but having some headroom seems desirable.

  • Mailbox Door Rebuild

    The flanges around the door of our giant mailbox rusted through, leaving the door to bend along the embossed (debossed? Whatever) lines across the front. Eventually, the bend got bad enough to keep the door from latching closed, but reviews of the current crop of mailboxes suggest they’re even more prone to rusting after even fewer years.

    Well, I can fix that:

    Mailbox door rebuild - installed
    Mailbox door rebuild – installed

    Because the bottom third of the door, basically everything around and below that horizontal ridge, had corroded, the general idea was to stiffen it with an internal plate:

    Mailbox door rebuild - interior
    Mailbox door rebuild – interior

    The array of small holes suggest the plate’s rich lived experience. Some are even tapped!

    External angle brackets stiffen the sides along the corroded flanges and surround the equally corroded pivot holes:

    Mailbox door rebuild - exterior
    Mailbox door rebuild – exterior

    The term “brick shithouse” springs unbidden to mind, doesn’t it? Those spare holes come from previous uses; I decided this application didn’t demand cosmetic perfection and, as a result, the remaining angle stock has no holes at all.

    Also, the angle brackets are as long as they are because that’s the maximum throat depth for Tiny Bandsaw™. I splurged on a Proxxon 10-14 TPI blade (for future reference: PN 28172) that cuts aluminum like butter, much better than the stock 14 TPI blade.

    The hinge pins used to be rivets. After careful consideration, I replaced them with 1/4-20 button-head cap screws:

    Mailbox door rebuild - hinge detail
    Mailbox door rebuild – hinge detail

    Yes, the sheet metal now pivots on screw threads, rather than a nice smooth cylinder. The nyloc nut maintains the proper amount of looseness around the battered sheet metal.

    While I had the door open, I slobbered hot melt glue over the flag anchor, which should keep it from spitting the ratchet pin into the roadside debris ever again:

    Mailbox door rebuild - flag anchor
    Mailbox door rebuild – flag anchor

    A pleasant evening of Quality Shop Time, indeed!

    The alert reader will note I’m securing aluminum plates with stainless steel hardware on a (nominally) galvanized steel box, thereby forming several batteries with a brine electrolyte from wintertime road salt. My engineering judgement determined this repair will last Long Enough™ and, most likely, succumb to somebody not quite making the curve while accelerating from the traffic signal.

    Aaaaand those painted numbers still look pretty good after four years.

  • CNC 3018-Pro: Milling the CD Fixture

    It turns out that the outer diameter of CD platters isn’t quite as perfectly controlled as you (well, I) might imagine, although the differences between CDs from different sources amounts to perhaps ±0.1 mm. Of course, instantly after putting the tape-down fixture into use, the next few discs atop my stack of scrap CDs were just large enough to not quite fit.

    The Sherline’s workspace can’t maneuver the holder’s perimeter around the spindle, so embiggening the OD calls for the rotary table. The general idea is to clamp the center of the fixture to the rotary table, run a small end mill about 0.1 mm into the fixture’s OD, spin the table one revolution, and be done with it.

    Of course, the rotary table’s 3/8-16 threaded center hole doesn’t match the fixture’s 6 mm center hole: we need an adapter. Start with a 1 inch long 3/8-16 stainless steel hex bolt, center drill the end, peel off the hex head, then turn to 6 mm OD, going down far enough so the threads don’t stick up out of the table too much:

    CNC 3018-Pro - CD fixture milling - bolt turning
    CNC 3018-Pro – CD fixture milling – bolt turning

    The Sherline uses 10-32 screws, so poke a #16 drill 15 mm into the bolt to get maybe 25% thread depth (because it’s a blind hole into stainless steel for an application requiring minimal strength and I hate breaking taps), tap 10-32, clean out the hole, and call it All Good:

    CNC 3018-Pro - CD fixture milling - rotary table adapter
    CNC 3018-Pro – CD fixture milling – rotary table adapter

    Find the trim plate from an old faucet to reach around the central boss, stack up enough flat washers to meet the nut, snug a Sherline spherical nut + washer set (because it’s within reach), chuck up a 1/8 inch mill, and have at it:

    CNC 3018-Pro - CD fixture milling
    CNC 3018-Pro – CD fixture milling

    The fixture sits atop an aluminum plate cut to fit a smaller version of the table riser, but this requires zero fancy alignment. The 6 mm adapter centers the fixture on the rotary table and the cutter sits at a fixed radius from the center wherever it contacts the fixture rim; just spin the table and it cuts a neatly centered circle.

    A test fit showed the oversize discs fit perfectly:

    CNC 3018-Pro - CD fixture milling - test fit
    CNC 3018-Pro – CD fixture milling – test fit

    Bonus: a nice new adapter for the rotary table!

  • CNC 3018-Pro: Tape-Down Platter Fixture

    Diamond drag engraving doesn’t put much sideways force on the platters, so taping the CD in place suffices to hold it:

    CNC 3018-Pro - CD taped to platform
    CNC 3018-Pro – CD taped to platform

    Wrapping a flange around the screw-down platter fixture provides plenty of surface area for tape:

    Platter Fixtures - CD on 3018 - tape flange
    Platter Fixtures – CD on 3018 – tape flange

    Which looks exactly as you think it would in real life:

    CNC 3018-Pro - CD fixture - taped
    CNC 3018-Pro – CD fixture – taped

    Admittedly, masking tape doesn’t look professional, but it’s low-profile, cheap and works perfectly. Blue painter’s tape for the “permanent” hold-down strips on the platform would be a colorful upgrade.

    It’s centered on the platform at the XY=0 origin in the middle of the XY travel limits, with edges aligned parallel to the axes. Homing the 3018 and moving to XY=0 puts the tool point directly over the center of the CD without any fussy alignment.

    The blue-and-red rings around the center hole assist probe camera alignment, whenever that’s necessary.

    The OpenSCAD source code as a GitHub Gist:

    // Machining fixtures for CD and hard drive platters
    // Ed Nisley KE4ZNU February … September 2016
    // 2019-08 split from tube base models
    PlatterName = "CD"; // [3.5inch,CD]
    CNCName = "3018"; // [3018,Sherline]
    TapeFlange = true; // Generate tape attachment
    PlateThick = 5.0; // [3.0,5.0,10.0,15.0]
    RecessDepth = 4.0; // [0.0,2.0,4.0]
    //- Extrusion parameters must match reality!
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
    }
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //———————-
    // Dimensions
    P_NAME = 0; // platter name
    P_ID = 1; // … inner diameter
    P_OD = 2; // … outer diameter
    P_THICK = 3; // … thickness
    PlatterData = [
    ["3.5inch", 25.0, 95.0, 1.75],
    ["CD", 15.0, 120.0, 1.20],
    ];
    PlatterSides = 3*4*5; // polygon approximation
    B_NAME = 0; // machine name
    B_OC = 1; // … platform screw OC, use small integer for slot
    B_STUD = 2; // … screw OD clearance
    BaseData = [
    ["3018", [5.0, 45.0], 6.0], // slots along X axis
    ["Sherline", [1.16*inch,1.16*inch], 5.0], // tooling plate
    ];
    PlateRound = 10.0; // corner radius
    FlangeSize = [5.0,5.0,3*ThreadThick]; // all-around tape flange
    //– calculate values based on input parameters
    PI = search([PlatterName],PlatterData,1,0)[P_NAME]; // get platter index
    echo(str("Platter: ",PlatterName));
    Platter = [PlatterData[PI][P_ID],
    PlatterData[PI][P_OD],
    PlatterData[PI][P_THICK]];
    BI = search([CNCName],BaseData,1,0)[B_NAME]; // get base index
    echo(str("Machine: ",CNCName));
    AlignOC = IntegerMultiple(Platter[OD],10);
    echo(str("Alignment pip offset: ±",AlignOC/2));
    AlignSlot = [3*ThreadWidth,10.0,3*ThreadThick];
    StudClear = BaseData[BI][B_STUD]; // … clearance
    StudOC = [IntegerMultiple(AlignOC + 2*StudClear,BaseData[BI][B_OC].x), // … screw spacing
    BaseData[BI][B_OC].y];
    echo(str("Stud spacing: ",StudOC));
    NumStuds = [2,1 + 2*floor(Platter[OD] / StudOC.y)]; // holes only along ±X edges
    echo(str("Stud holes: ",NumStuds));
    BasePlate = [(20 + StudOC.x*ceil(Platter[OD] / StudOC.x)),
    (10 + AlignOC),
    PlateThick];
    echo(str("Plate: ",BasePlate));
    Flange = [BasePlate.x + 2*FlangeSize.x,BasePlate.y + 2*FlangeSize.y,FlangeSize.z];
    echo(str("Flange: ",Flange));
    //———————-
    // Drilling fixture for disk platters
    module PlatterFixture() {
    difference() {
    union() {
    hull() // basic plate shape
    for (i=[-1,1], j=[-1,1])
    translate([i*(BasePlate.x/2 – PlateRound),j*(BasePlate.y/2 – PlateRound),0])
    cylinder(r=PlateRound,h=BasePlate.z,$fn=4*4);
    if (TapeFlange)
    hull()
    for (i=[-1,1], j=[-1,1])
    translate([i*(Flange.x/2 – PlateRound),
    j*(Flange.y/2 – PlateRound),
    0])
    cylinder(r=PlateRound,h=FlangeSize.z,$fn=4*4);
    }
    for (i=[-1,0,1], j=[-1,0,1]) // origin pips
    translate([i*AlignOC/2,j*AlignOC/2,BasePlate.z – 2*ThreadThick])
    cylinder(d=4*ThreadWidth,h=1,$fn=6);
    for (i=[-1,1], j=[-1,1]) { // alignment slots
    translate([i*(AlignOC + AlignSlot.x)/2,
    j*Platter[OD]/4,
    (BasePlate.z – AlignSlot.z/2 + Protrusion/2)])
    cube(AlignSlot + [0,0,Protrusion],center=true);
    translate([i*Platter[OD]/4,
    j*(AlignOC + AlignSlot.x)/2,
    (BasePlate.z – AlignSlot.z/2 + Protrusion/2)])
    rotate(90)
    cube(AlignSlot + [0,0,Protrusion],center=true);
    }
    for (i=[-1,1], j=[-floor(NumStuds.y/2):floor(NumStuds.y/2)]) // mounting stud holes
    translate([i*StudOC.x/2,j*StudOC.y/2,-Protrusion])
    rotate(180/6)
    PolyCyl(StudClear,BasePlate.z + 2*Protrusion,6);
    translate([0,0,-Protrusion]) // center clamp hole
    rotate(180/6)
    PolyCyl(StudClear,BasePlate.z + 2*Protrusion,6);
    translate([0,0,BasePlate.z – Platter[LENGTH]]) // disk locating recess
    rotate(180/PlatterSides)
    linear_extrude(height=(Platter[LENGTH] + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] + 2*HoleWindage),$fn=PlatterSides);
    circle(d=Platter[ID] – HoleWindage,$fn=PlatterSides);
    }
    translate([0,0,BasePlate.z – RecessDepth]) // drilling recess
    rotate(180/PlatterSides)
    linear_extrude(height=(RecessDepth + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] – 10),$fn=PlatterSides);
    circle(d=(Platter[ID] + 10),$fn=PlatterSides);
    }
    }
    }
    //———————-
    // Build it
    PlatterFixture();