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

Making parts with mathematics

  • Thinwall and Solid Boxes for 3D Printer Calibration

    A revision to my Fundamental Calibration Object adds some variations …

    The classic thinwall open box:

    Calibration Box - open - 1 thread - solid model
    Calibration Box – open – 1 thread – solid model

    A solid box:

    Calibration Box - solid - solid model
    Calibration Box – solid – solid model

    A solid box with text embossed on the lower surface:

    Calibration Box - solid text - solid model
    Calibration Box – solid text – solid model

    You must consider how the slicer settings interact with the solid model parameters, particularly now that slicers can produce adaptive infill for small gaps between perimeter threads. Previewing the slicer’s output will show you what assumptions it makes and prevent surprising results out there on the platform.

    A single-thread wall comes out properly:

    Thinwall open box - 0.40 wall - Slic3r
    Thinwall open box – 0.40 wall – Slic3r

    The results look just like the preview, with firmly bonded layers and no fluff:

    Thinwall open box - 1 thread walls
    Thinwall open box – 1 thread walls

    This wall should be two threads wide, but Slic3r inserts very very thin infill thread:

    Thinwall open box - 0.80 wall - Slic3r
    Thinwall open box – 0.80 wall – Slic3r

    I think that’s a result of forcing the two perimeter threads to sit with their centers exactly one thread width apart, making the (nominal, ideal) inner walls tangent to each other.  Setting the wall to 1.9 mm eliminates the hair-fine infill thread, at the cost of producing an object 0.1 mm smaller than it looks.

    Unfortunately, that fine infill doesn’t produce enough plastic flow for a continuous thread. The PET I’m using accumulates on the nozzle until enough of a glob forms to stick on the previous layer, but hair-fine strands connect those globs to each other and the nozzle, producing awful results:

    Thinwall open box - 2 thread walls
    Thinwall open box – 2 thread walls

    A triple-thread wall allows Slic3r to produce a fatter infill thread that works the way you’d expect:

    Thinwall open box - 1.20 wall - Slic3r
    Thinwall open box – 1.20 wall – Slic3r

    The threads bond firmly in all directions:

    Thinwall open box - 3 thread walls
    Thinwall open box – 3 thread walls

    It’s not obvious from that picture, but the bond between successive infill threads produces a glass-clear vertical plastic slab that relays images from the bottom to the top. The perimeter threads are also firmly bonded, albeit with not quite the same optical quality.

    To use these boxes:

    • Set the OpenSCAD extrusion parameters to match whatever the slicer will use
    • Set the wall height and thickness to whatever you like
    • Compile-and-render, export the result as a solid model in STL / AMF / whatever
    • Feed the solid model into your favorite slicer and save the G-Code
    • Feed the G-Code into your printer, watch it magically create a little box
    • Measure the printed results and compare with the ideal settings
    • Change the slicing configuration and iterate until satisfied

    Verify these measurements before adjusting anything else:

    • Filament diameter: actual vs. nominal will be different
    • Extruder steps per millimeter: mark 100 mm on filament, extrude 100 mm, compare

    Then you can verify / adjust some finicky settings:

    • Extrusion multiplier: does the actual single wall width match slicer’s nominal value?
    • Infill density: 100% infill should perfectly fill the solid box
    • Initial Z offset: does actual height match the model setting?
    • Platform alignment: print five boxes at platform center + corners, verify heights
    • First layer adhesion: if these don’t stick, the platform has weak adhesion
    • Minimum time per layer: if the walls slump, you’re printing too fast
    • Extrusion temperature: good bonding and no delamination along any axis

    The OpenSCAD source code as a GitHub gist:

    // Simple calibration boxes
    // Thin wall open box – verify Extrusion Multiplier
    // Solid box – verify infill settings
    // Ed Nisley – KE4ZNU
    // https://softsolder.com/
    Layout = "Open"; // Open Solid
    Texting = "Text!"; // text message on solid box or empty string to suppress
    //——-
    //- Extrusion parameters must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //——-
    // Dimensions
    WallThick = 1.0 * ThreadWidth;
    echo(str("Wall thickness: ",WallThick));
    BoxSize = 20.0;
    echo(str("Overall size: ",BoxSize));
    NominalHeight = 3.0;
    echo(str("Nominal height: ",NominalHeight));
    Height = IntegerMultiple(NominalHeight,ThreadThick);
    echo(str("Actual height: ",Height));
    Rotation = 0; // 45 to exercise X and Y axis motors at same time
    CornerRadius = 2.0;
    CornerSides = 8*4;
    //——–
    module Solid() {
    difference() {
    hull()
    for (i=[-1,1], j=[-1,1])
    translate([i*(BoxSize – 2*CornerRadius)/2,j*(BoxSize – 2*CornerRadius)/2,0])
    cylinder(r=CornerRadius,h=Height,$fn=CornerSides);
    if (len(Texting))
    translate([0,0,-Protrusion/2])
    linear_extrude(height=3*ThreadThick + Protrusion)
    mirror([1,0,0])
    text(text=Texting,size=6,spacing=1.05,font="ITC Zapf Chancery:style=Italic",halign="center",valign="center");
    }
    }
    module Thinwall() {
    difference() {
    Solid();
    hull()
    for (i=[-1,1], j=[-1,1])
    translate([i*(BoxSize – 2*CornerRadius)/2,j*(BoxSize – 2*CornerRadius)/2,-Protrusion])
    cylinder(r=(CornerRadius – WallThick),h=(Height + 2*Protrusion),$fn=CornerSides);
    }
    }
    //——-
    rotate(Rotation)
    if (Layout == "Open")
    Thinwall();
    else
    Solid();
  • Knurled Inserts: Epoxy Anchoring

    After taking the incandescent lamp socket off its base, I drilled the tapped (yeah, in plastic) 6-32 holes out to a firm press fit for the knurled 6-32 inserts, buttered the inserts with epoxy, and pressed them firmly in place:

    Lamp Base - epoxy knurled insert
    Lamp Base – epoxy knurled insert

    Fast forward a day and they’re stuck in there like they were glued. You can see a bit of the epoxy around the right rim of the insert; I wiped a bit more off around the other one.

    Putting The Right Amount of epoxy on the insert requires dialing back my “The bigger the blob, the better the job” enthusiasm, but wasn’t all that difficult. It’s certainly more tedious than just ramming the inserts into a printed hole and might actually produce better retention. I doubt that will make the least difference for (almost) anything I build.

    On the whole, they look good…

  • Vacuum Tube LEDs: Noval Tube on a Platter

    Replacing the hex nut traps with knurled insert cylinders slims the ends of the socket:

    Noval Socket - knurled inserts - bottom - Slic3r preview
    Noval Socket – knurled inserts – bottom – Slic3r preview

    Making the raised part of the socket fit the 25 mm ID of a hard drive platter swells the midsection of the socket, but the platter won’t need any machining or punching:

    Noval Socket - knurled inserts - top - Slic3r preview
    Noval Socket – knurled inserts – top – Slic3r preview

    The octal and duodecar sockets will require a punch to open up the platter hole and all sockets require two drilled clearance holes for the screws. Given that I’ll eventually do this on the Sherline, maybe milling the hole for the bigger tubes will be faster & easier than manually punching them.

    I moved the screw centers to 35 mm (from the historically accurate 28 mm) to accommodate the larger center, not that anybody will ever notice, and enlarged the central hole to 7.5 mm (from 5.0 mm) to let more light into the tube base.

    The support structures inside the (now much smaller) knurled insert cylinders might not be strictly necessary, but I left them in place to see how well they built. Which was perfectly, as it turns out, and they popped out with a slight push:

    Noval socket - knurled inserts - support structures
    Noval socket – knurled inserts – support structures

    They’re just the cutest little things (those are 0.100 inch grid squares in the background):

    Noval socket - support structures
    Noval socket – support structures

    Anyhow, the knurled inserts pressed into their holes with a slight shove:

    Noval socket - installing knurled insert
    Noval socket – installing knurled insert

    The chuck jaws were loose on the screw cutoff stud and stopped at the surface, putting the knurled inserts perfectly flush with the socket:

    Noval socket - knurled inserts - installed
    Noval socket – knurled inserts – installed

    The surface looks very slightly distorted around the inserts, although it’s still smooth to the touch, and I think the PETG will slowly relax around the knurls. Even without heat or epoxy, they’re now impossible to pull out with any force I’m willing to apply to the screws threaded into them. Given that the platter screws will (be trying to) pull the inserts through the socket, I think a dry install will suffice for my simple needs.

    Match-mark, drill #27 6-32 clearance holes, and the screws drop right in:

    Noval socket - installed
    Noval socket – installed

    Those stainless steel pan-head 6-32 screws seem a bit large in comparison with the socket. Perhaps I should use 4-40 screws, even though they’re not, ahem, historically accurate.

    The tube pin holes get hand-reamed with a #53 drill = 1.5 mm. That’s a bit over the nominal 1.1 mm pin diameter, but seems to provide both easy insertion and firm retention. For permanent installation, an adhesive would be in order.

    Buff off the fingerprints, stick the tube in place, and it looks pretty good:

    Noval socket - tube on platter
    Noval socket – tube on platter

    Yeah, those screws are too big. Maybe a brace of black M3 socket head screws would look better, despite a complete lack of historicity.

    Now to wire it up and ponder how to build a base.

    The OpenSCAD source code as a GitHub Gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU February 2016
    Layout = "Socket"; // Cap LampBase USBPort Socket(s) (Build)FinCap
    DefaultSocket = "Noval";
    Section = false; // cross-section the object
    Support = true;
    //- Extrusion parameters must match reality!
    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);
    //———————-
    // Dimensions
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    // punch & screw OC modified for drive platter chassis plate
    // platter = 25 mm ID
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (overestimate)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch env pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 25.0, 18.0, 5.0, 35.0], // punch 11/16, screw 22.5 OC
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 39.0],
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 25.0 , 21.0, 7.5, 35.0], // punch 7/8, screw 28.0 OC
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, (4 + 1)/4 * inch, 38.0, 12.5, 39.0], // aka Compactron
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    Nut = [3.5,5.2,7.2]; // socket mounting nut recess — threaded insert
    NutSides = 8;
    BaseShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 2.0; // rim around socket below punchout
    PanelThick = 1.5; // socket extension through punchout
    FinCutterOD = 1/8 * inch;
    FinCapSize = [(Pixel[OD] + 2*FinCutterOD),30.0,(10.0 + 2*Pixel[LENGTH])];
    //———————-
    // Useful routines
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 3.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 6*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides)),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Heatsink tube cap
    module FinCap() {
    CableOD = 3.5; // cable + braid diameter
    BulbOD = 3.75 * inch; // bulb OD; use 10 inches for flat
    echo(str("Fin Cutter: ",FinCutterOD));
    FinSides = 2*4;
    BulbRadius = BulbOD / 2;
    BulbDepth = BulbRadius – sqrt(pow(BulbRadius,2) – pow(FinCapSize[OD],2)/4);
    echo(str("Bulb OD: ",BulbOD," recess: ",BulbDepth));
    NumFins = floor(PI*FinCapSize[ID] / (2*FinCutterOD));
    FinAngle = 360 / NumFins;
    echo(str("NumFins: ",NumFins," angle: ",FinAngle," deg"));
    difference() {
    union() {
    cylinder(d=FinCapSize[ID],h=FinCapSize[LENGTH],$fn=2*NumFins); // main body
    for (i = [0:NumFins – 1]) // fins
    rotate(i * FinAngle)
    hull() {
    translate([FinCapSize[ID]/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    translate([(FinCapSize[OD] – FinCutterOD)/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    }
    rotate(FinAngle/2) // cable entry boss
    translate([FinCapSize[ID]/2,0,FinCapSize[LENGTH]/2])
    cube([FinCapSize[OD]/4,FinCapSize[OD]/4,FinCapSize[LENGTH]],center=true);
    }
    for (i = [1:NumFins – 1]) // fin inner gullets, omit cable entry side
    rotate(i * FinAngle + FinAngle/2) // joint isn't quite perfect, but OK
    translate([FinCapSize[ID]/2,0,-Protrusion])
    rotate(0*180/FinSides)
    cylinder(d=FinCutterOD/cos(180/FinSides),h=(FinCapSize[LENGTH] + 2*Protrusion),$fn=FinSides);
    translate([0,0,-Protrusion]) // PCB recess
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),FinSides);
    PolyCyl(Pixel[ID],(FinCapSize[LENGTH] – 3*ThreadThick),FinSides); // bore for LED wiring
    translate([0,0,(FinCapSize[LENGTH] – 3*ThreadThick – 2*CableOD/(2*cos(180/8)))]) // cable inlet
    rotate(FinAngle/2) rotate([0,90,0]) rotate(180/8)
    PolyCyl(CableOD,FinCapSize[OD],8);
    if (BulbOD <= 10.0 * inch) // curve for top of bulb
    translate([0,0,-(BulbRadius – BulbDepth + 2*ThreadThick)]) // … slightly flatten tips
    sphere(d=BulbOD,$fn=16*FinSides);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,28.0])
    rotate([90,0,0])
    linear_extrude(height=28.0)
    polygon(points=[
    [0,0],
    [8.0,0],
    [8.0,4.0],
    // [4.0,4.0],
    [4.0,6.5],
    [-4.0,6.5],
    // [-4.0,4.0],
    [-8.0,4.0],
    [-8.0,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [Nut[OD], // 6-32 tapped brass insert
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = DefaultSocket) {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[ID][T_PUNCHOD]," mm = ",TubeData[ID][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[ID][T_SCREWOC]," mm =",TubeData[ID][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + BaseShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight)
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2.0*Nut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides); // boss in chassis punch hole
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps / threaded inserts
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(Nut[OD],(Nut[LENGTH] + Protrusion),NutSides);
    PolyCyl(Nut[ID],(OAH + 2*Protrusion),NutSides);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(Nut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*Nut[OD],(Nut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "FinCap") {
    if (Section) render(convexity=5)
    difference() {
    FinCap();
    // translate([0,-FinCapSize[OD],FinCapSize[LENGTH]])
    // cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    translate([-FinCapSize[OD],0,FinCapSize[LENGTH]])
    cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    }
    else
    FinCap();
    }
    if (Layout == "BuildFinCap")
    translate([0,0,FinCapSize[LENGTH]])
    rotate([180,0,0])
    FinCap();
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    }
  • Knurled Inch Inserts

    Tagging along behind the metric inserts, a sack of knurled brass inch-size screw inserts arrived:

    Threaded Inserts - metric and inch
    Threaded Inserts – metric and inch

    The nice stainless steel screws on the right range from 4-40 to 10-32, which suffice for nearly everything I build around here.

    Unlike the splined metric inserts on the left, these inserts have actual knurls and ridges that should hold them firmly in place. The specs give hard-inch dimensions, of course, that (seem to) correspond to the root diameter of the knurls. You can find nice engineering drawings of precise tapered holes (by drilling down into the Heat-Set Inserts for Plastics item on that page), but a few metric measurements of the actual parts on hand should suffice for my simple needs.

    Thread: overall length x small rim OD x (knurl length x larger knurl OD)

    • 4-40: 5.8 x 3.9 x (4.0 x 4.6)
    • 6-32: 7.1 x 4.7 x (4.6 x 5.5)
    • 8-32: 8.1 x 5.5 x (5.9 x 6.3)
    • 10-32: 9.5 x 6.3 x (7.0 x 7.1)

    Rather than fussing with a tapered hole, just punch a cylinder with the small rim OD (to clear the screw) through the part and put a cylinder with the knurl OD x length at the surface.

    Using cylinders without diameter correction will make them slightly undersized for heat bonding. The usual 3D printing tolerances don’t justify anything fussier than that.

    Using PolyCyl diameter correction will make the holes nearly spot on for epoxy bonding: butter ’em up, ram ’em in, pause for curing, done.

    That’s the plan, anyhow…

  • Vacuum Tube LEDs: Ersatz Heat Sink Plate Cap

    I wanted a slightly larger “plate cap” to fit a big incandescent bulb and it seemed a fake heatsink might add gravitas to the proceedings:

    Vacuum Tube LEDs - large incandescent bulb
    Vacuum Tube LEDs – large incandescent bulb

    Yeah, that antique ceramic socket holds the bulb at a rakish angle. Worse, even though I painstakingly laid out the position of the heatsink atop the bulb, it’s visibly off-center. Which wouldn’t be so bad, had I not epoxied the damn thing in place.

    After reaming out the M2’s filament drive, the entire blue base printed without incident.

    A closer look at the cap:

    Vacuum Tube LEDs - ersatz heatsink plate cap
    Vacuum Tube LEDs – ersatz heatsink plate cap

    Memo to Self: Next time, line it up with the vertical glass support inside the bulb and ignore the external evidence.

    The boss has a hole for the braid-enclosed cable to the knockoff Neopixel:

    Vacuum Tube Lights - finned cap - Slic3r preview
    Vacuum Tube Lights – finned cap – Slic3r preview

    The cupped surface perfectly fits the bulb’s 3.75 inch diameter. While you wouldn’t mill out a real heatsink, it definitely looks better this way and (alas) gives the epoxy more footprint for a better grip.

    I built the fins with a 1/8 inch cutter in mind, so the fin root radius allows for a G3/G3 arc without gouging. I doubt machining a fake heatsink from aluminum makes any sense, but the cheap extruded heatsinks on eBay don’t look very good. Plus, they sport completely unnecessary tapped holes for LED mounts and suchlike.

    A cross-section shows the wiring channel and cable entry:

    Vacuum Tube Lights - fin cap solid model - section
    Vacuum Tube Lights – fin cap solid model – section

    I epoxied the Neopixel in place, applied double-sided carpet tape to the whole thing, then painstakingly trimmed around the fins with an Xacto knife:

    Vacuum Tube LEDs - Ersatz Heatsink plate cap - tape
    Vacuum Tube LEDs – Ersatz Heatsink plate cap – tape

    That looked better from the top side (where it was completely hidden) and came heartbreakingly close to working, but after about a day the cable + braid put enough torque on the cap to peel it off the bulb. Obviously, the tape holds much less enthusiastically after that.

    Part of the problem came from the cable’s rather sharp angle just outside the cap:

    Vacuum Tube LEDs - Ersatz Heatink plate cap - detail
    Vacuum Tube LEDs – Ersatz Heatink plate cap – detail

    Rakish angle, indeed. Two of ’em, in fact.

    Unlike the smaller cap on the halogen bulb, this time I didn’t bother with a brass tube ferrule, mostly to see how it looks. I think it came out OK and the black braid looks striking in person. Conversely, a touch of brass never detracts from the appearance.

    Obviously, the cable wasn’t long enough, either. Part of that problem came from underestimating the braid length: it shortens dramatically when slipped over the cable, even when you expect shortening. Somehow I managed to overlook that, despite cutting the cable quite long enough, thankyouverymuch. There’s a tradeoff between gentle angles and having the cable stick out too far for comfort.

    Memo to Self: Use a cable at least four inches longer than necessary, measure the combined cable + braid assembly after screwing the bulb in the socket, and don’t epoxy anything before all the parts are ready for assembly.

    That’s why it’s a prototype made out of blue PETG…

    Protip: running old ceramic sockets through the dishwasher greatly simplifies their subsequent cleanup.

    All in all, I like it.

    The OpenSCAD source code as a GitHub gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU January 2016
    Layout = "FinCap"; // Cap LampBase USBPort Socket(s) (Build)FinCap
    Section = true; // cross-section the object
    Support = true;
    //- Extrusion parameters must match reality!
    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);
    //———————-
    // Dimensions
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (overestimate)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch env pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 11/16 * inch, 18.0, 5.0, 22.5],
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 39.0],
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 7/8 * inch, 21.0, 5.0, 28.0],
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, 1.25 * inch, 38.0, 12.5, 39.0],
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    Nut = [3.5,8.0,3.0]; // socket mounting nut recess
    BaseShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 2.0; // rim around socket below punchout
    PanelThick = 2.0; // socket extension through punchout
    //———————-
    // Useful routines
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 3.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 6*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides)),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Heatsink tube cap
    CableOD = 3.5; // cable + braid diameter
    BulbOD = 3.75 * inch; // bulb OD; use 10 inches for flat
    FinCutterOD = 1/8 * inch;
    echo(str("Fin Cutter: ",FinCutterOD));
    FinSides = 2*4;
    FinCapSize = [(Pixel[OD] + 2*FinCutterOD),30.0,(10.0 + 2*Pixel[LENGTH])];
    BulbRadius = BulbOD / 2;
    BulbDepth = BulbRadius – sqrt(pow(BulbRadius,2) – pow(FinCapSize[OD],2)/4);
    echo(str("Bulb OD: ",BulbOD," recess: ",BulbDepth));
    module FinCap() {
    NumFins = floor(PI*FinCapSize[ID] / (2*FinCutterOD));
    FinAngle = 360 / NumFins;
    echo(str("NumFins: ",NumFins," angle: ",FinAngle," deg"));
    difference() {
    union() {
    cylinder(d=FinCapSize[ID],h=FinCapSize[LENGTH],$fn=2*NumFins); // main body
    for (i = [0:NumFins – 1]) // fins
    rotate(i * FinAngle)
    hull() {
    translate([FinCapSize[ID]/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    translate([(FinCapSize[OD] – FinCutterOD)/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    }
    rotate(FinAngle/2) // cable entry boss
    translate([FinCapSize[ID]/2,0,FinCapSize[LENGTH]/2])
    cube([FinCapSize[OD]/4,FinCapSize[OD]/4,FinCapSize[LENGTH]],center=true);
    }
    for (i = [1:NumFins – 1]) // fin inner gullets, omit cable entry side
    rotate(i * FinAngle + FinAngle/2) // joint isn't quite perfect, but OK
    translate([FinCapSize[ID]/2,0,-Protrusion])
    rotate(0*180/FinSides)
    cylinder(d=FinCutterOD/cos(180/FinSides),h=(FinCapSize[LENGTH] + 2*Protrusion),$fn=FinSides);
    translate([0,0,-Protrusion]) // PCB recess
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),FinSides);
    PolyCyl(Pixel[ID],(FinCapSize[LENGTH] – 3*ThreadThick),FinSides); // bore for LED wiring
    translate([0,0,(FinCapSize[LENGTH] – 3*ThreadThick – 2*CableOD/(2*cos(180/8)))]) // cable inlet
    rotate(FinAngle/2) rotate([0,90,0]) rotate(180/8)
    PolyCyl(CableOD,FinCapSize[OD],8);
    if (BulbOD <= 10.0 * inch) // curve for top of bulb
    translate([0,0,-(BulbRadius – BulbDepth + 2*ThreadThick)]) // … slightly flatten tips
    sphere(d=BulbOD,$fn=16*FinSides);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,28.0])
    rotate([90,0,0])
    linear_extrude(height=28.0)
    polygon(points=[
    [0,0],
    [8.0,0],
    [8.0,4.0],
    // [4.0,4.0],
    [4.0,6.5],
    [-4.0,6.5],
    // [-4.0,4.0],
    [-8.0,4.0],
    [-8.0,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [0.107 * inch, // 6-32 mounting screws
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    # cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = "Mini7") {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[ID][T_PUNCHOD]," mm = ",TubeData[ID][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[ID][T_SCREWOC]," mm =",TubeData[ID][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + BaseShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight)
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2*Nut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides);
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(Nut[OD],(Nut[LENGTH] + Protrusion),6);
    PolyCyl(Nut[ID],(OAH + 2*Protrusion),6);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(Nut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*Nut[OD],(Nut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "FinCap") {
    if (Section) render(convexity=5)
    difference() {
    FinCap();
    // translate([0,-FinCapSize[OD],FinCapSize[LENGTH]])
    // cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    translate([-FinCapSize[OD],0,FinCapSize[LENGTH]])
    cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    }
    else
    FinCap();
    }
    if (Layout == "BuildFinCap")
    translate([0,0,FinCapSize[LENGTH]])
    rotate([180,0,0])
    FinCap();
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    }
  • Vacuum Tube LEDs: Ersatz Tube Sockets

    Even vacuum tubes destined to be decorations need sockets:

    Vacuum Tube Bases - solid models
    Vacuum Tube Bases – solid models

    They’re entirely plastic, of course, but they match the dimensions of “real” tube sockets pretty closely. The bosses around the pins have hard-inch dimensions, so you (well, I) can unleash Genuine Greenlee Radio Chassis Punches on sheet metal.

    All the key dimensions come from a table, so you can build whatever sockets you need. These four seem to cover the most common relics of the Hollow State Empire:

    T_NAME = 0;                                             // common name
    T_NUMPINS = 1;                                          // total, with no allowance for keying
    T_PINBCD = 2;                                           // tube pin circle diameter
    T_PINOD = 3;                                            //  ... diameter
    T_PINLEN = 4;                                           //  ... length (overestimate)
    T_HOLEOD = 5;                                           // nominal panel hole from various sources
    T_PUNCHOD = 6;                                          // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7;                                           // envelope or base diameter
    T_PIPEOD = 8;                                           // light pipe from LED to tube base
    T_SCREWOC = 9;                                          // mounting screw holes
    
    //    Name      pins     BCD   dia  length   hole            punch       env  pipe screw
    TubeData = [
        ["Mini7",     8,    9.53, 1.016,   7.0,  16.0,        11/16 * inch,  18.0,  5.0, 22.5],
        ["Octal",     8,   17.45, 2.36,   10.0,  36.2,    (8 + 1)/8 * inch,  32.0, 11.5, 39.0],
        ["Noval",    10,   11.89, 1.1016,  7.0,  22.0,          7/8 * inch,  21.0,  5.0, 28.0],
        ["Duodecar", 13,   19.10, 1.05,    9.0,  32.0,         1.25 * inch,  38.0, 12.5, 39.0],
    ];
    

    Given that the tubes lack electrical connections, I omitted the base keying: plug them in for best visual effect.

    The hole through the middle passes light from a knockoff Neopixel on a 10 mm OD PCB:

    Vacuum Tube LEDs - Octal base - top
    Vacuum Tube LEDs – Octal base – top

    Seen from the bottom, each base traps a pair of 6-32 nuts for chassis mounting and has a Neopixel press-fit in the middle:

    Vacuum Tube LEDs - Duodecar base - bottom
    Vacuum Tube LEDs – Duodecar base – bottom

    Those recesses require support structures:

    Vacuum Tube Bases - solid models - support
    Vacuum Tube Bases – solid models – support

    The Miniature 7-pin socket has the least space for the 10 mm OD Neopixel PCB and shows the thin layer between the bottom of the pin holes and the top of the openings.

    Vacuum Tube Base - Mini7 - solid model section
    Vacuum Tube Base – Mini7 – solid model section

    You see half of the eight holes in the “7 pin” socket, because it has the eighth hole where a standard socket has a gap between pins 1 and 7.

    Somewhat to my surprise, punching the support spiders out with a 6-32 stud (grabbed in the drill press) worked perfectly:

    Vacuum Tube Base - nut trap overhang - detail
    Vacuum Tube Base – nut trap overhang – detail

    They look like I intended to build tiny decorations:

    Vacuum Tube Base - support structure - detail
    Vacuum Tube Base – support structure – detail

    The cookies held on tenuously, then released with a loud bang! as I gradually increased the pressure. A PETG support structure in a blind recess wouldn’t pop out nearly so well.

    The OpenSCAD source code as a GitHub gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU January 2016
    Layout = "Sockets"; // Cap LampBase USBPort Socket(s)
    Section = true; // cross-section the object
    Support = true;
    //- Extrusion parameters must match reality!
    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);
    //———————-
    // Dimensions
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (overestimate)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch env pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 11/16 * inch, 18.0, 5.0, 22.5],
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 39.0],
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 7/8 * inch, 21.0, 5.0, 28.0],
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, 1.25 * inch, 38.0, 12.5, 39.0],
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    Nut = [3.5,8.0,3.0]; // socket mounting nut recess
    BaseShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 2.0; // rim around socket below punchout
    PanelThick = 2.0; // socket extension through punchout
    //———————-
    // Useful routines
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 3.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 6*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides)),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,28.0])
    rotate([90,0,0])
    linear_extrude(height=28.0)
    polygon(points=[
    [0,0],
    [8.0,0],
    [8.0,4.0],
    // [4.0,4.0],
    [4.0,6.5],
    [-4.0,6.5],
    // [-4.0,4.0],
    [-8.0,4.0],
    [-8.0,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [0.107 * inch, // 6-32 mounting screws
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    # cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = "Mini7") {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[ID][T_PUNCHOD]," mm = ",TubeData[ID][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[ID][T_SCREWOC]," mm =",TubeData[ID][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + BaseShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight)
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2*Nut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides);
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(Nut[OD],(Nut[LENGTH] + Protrusion),6);
    PolyCyl(Nut[ID],(OAH + 2*Protrusion),6);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(Nut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*Nut[OD],(Nut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    }

     

  • Knurled Metric Inserts

    These seem like they ought to come in handy for fastening things to 3D printed objects:

    Kurled Inserts - M2 M3 M5
    Kurled Inserts – M2 M3 M5

    The assorted screws come from the Small Can o’ Small Screwlike Things, all harvested from various dead bits of consumer electronics:

    Kurled M3 Inserts
    Kurled M3 Inserts

    These would benefit from a heated staking tool that slides them into the hole parallel to the axis and flush with the surface. Such things are commercially available, of course, but for my simple needs something involving a cartridge heater, a wall wart, and a drill press may suffice.

    It would be better if the inserts had actual knurls, rather than splines. So it goes.

    For the record (thread x length x Knurl OD x Body OD):

    • M2 x 4 x 3.5 x 2.8
    • M2 x 6 x 3.5 x 2.7
    • M3 x 4 x 4.5 x 3.8
    • M3 x 8 x 5.0 x 3.9
    • M5 x 10 x 7.5 x 6.9

    The actual measurements seem to vary within ±0.02 of nominal and I doubt the manufacturing consistency justifies any assumption tighter than ±0.1 mm.

    The M3 inserts really do have two different ODs.

    The M5 insert was listed as “7 mm OD” and measures 7.5 mm, which suggests a typo in the description.

    The polygonal hole adjustment I use produces dead-on diameters for small vertical holes:

    HoleWindage = 0.2;
    
    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);
    }
    

    So an ordinary cylinder() with the nominal knurl OD or a PolyCyl() with the nominal body OD should suffice. Horizontal holes can probably use a plain old cylinder() with the nominal body OD, because they need reaming anyway.

    Perhaps a dab of epoxy would bond better with the plastic around a nominal-size hole than forcing the insert into an undersized hole or heat-bonding the insert. Some experimentation is in order.

    Ten bucks for the entire collection (five bags of 50 inserts each = 250 little brass doodads = 4¢ each), shipped free halfway around the planet, seemed reasonable, given that inch size knurled brass inserts run anywhere from 50¢ to upwards of $2 a pop and a Genuine Helicoil 4-40 insert sets you back just shy of a buck.

    An Amazon vendor offers 4-40 inserts for $0.24 each in single quantities, but with $9.25 shipping. [le sigh]

    Inch-size inserts with knurled rings intended for ultrasonic bonding seem to be 5¢ to 15¢ on eBay. I think the straight-side versions will work better than the tapered ones for heat or epoxy bonding.

    It knurls my knuckles that we here in the US haven’t gone solidly metric. Yes, I have a goodly assortment of metric hardware in addition to the harvested fasteners shown above, but it definitely wasn’t cheap & readily available.