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

Using and tweaking a Makergear M2 3D printer

  • Octal Tube Base Clamp

    One of the octal tubes in my collection has a broken spigot / key post that lets some light in through the bottom of the normally opaque Bakelite base:

    Octal socket in CD - LED diffraction
    Octal socket in CD – LED diffraction

    Perhaps drilling out the base would let more light pass around the evacuation tip, but that requires a shell drill to clear the tip. Some doodling suggested a drill with 12 mm OD and 8 mm ID, which was close enough to one of the smaller homebrew drills in my collection that I decided to see how it worked:

    Shell drill assortment
    Shell drill assortment

    You (well, I) can’t freehand such a hole, particularly with a glass tip in the middle, so I needed a way to clamp the tube in either the drill press or the Sherline. A pad for the clamp screw in a V-block seemed appropriate:

    Vacuum Tube Lights - Octal base clamp - Slic3r preview
    Vacuum Tube Lights – Octal base clamp – Slic3r preview

    The screw hole sits at the 1/3 point to put more pressure near the pin end of the base. Maybe that matters.

    The setup looks like this, with a small red laser dot near the front of the base:

    Octal tube clamped on Sherline mill
    Octal tube clamped on Sherline mill

    The tube rests on a random scrap of plastic, with the hope that the drill won’t apply enough pressure to break the glass envelope.

    In normal use, the V-block would be oriented the other way to let you cross-drill the cylinder. In this end-on orientation, drilling torque can rotate the tube; compliant padding for more traction may be in order.

    The OpenSCAD source code as a GitHub Gist now includes a module that spits out the clamp:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU February … September 2016
    Layout = "TubeClamp"; // Cap LampBase USBPort Bushings
    // Socket(s) Cap (Build)FinCap Platter[Base|Fixture]
    // TubeClamp
    DefaultSocket = "Mini7";
    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
    // CD = 15 mm ID with raised ring at 37 mm, needs screw head clearance
    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 (must also clear evacuation tip / spigot)
    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 (clear evac tip / spigot)
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch tube 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, 11.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 47.0], // screw 39.0 OC
    ["Octal", 8, 17.45, 2.36, 11.0, 36.2, 25.0, 32.0, 11.5, 42.0], // platter + 4 mm screws
    ["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
    ["Magnoval", 10, 17.45, 1.27, 9.0, 29.7, (4 + 1)/4 * inch, 46.0, 12.4, 38.2], // similar to Novar
    // ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, (4 + 1)/4 * inch, 38.0, 12.5, 47.0], // screw was 39.0 OC
    ["Duodecar", 13, 19.10, 1.05, 9.0, 25.0, 25.0, 38.0, 12.5, 42.0], // fit un-punched drive platter
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    SocketNut = // socket mounting: threaded insert or nut recess
    // [3.5,5.2,7.2] // 6-32 insert
    [4.0,6.0,5.9] // 4 mm short insert
    ;
    NutSides = 8;
    SocketShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 1.5; // 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])];
    USBPCB =
    // [28,16,6.5] // small Sparkfun knockoff
    [36,18 + 1,5.8 + 0.4] // Deek-Robot fake FTDI with ISP header
    ;
    Platter = [25.0,95.0,1.26]; // hard drive platter dimensions
    //———————-
    // 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(d=(FixDia + HoleWindage),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] + 2.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 8*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) + HoleWindage),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,USBPCB[0]])
    rotate([90,0,0])
    linear_extrude(height=USBPCB[0])
    polygon(points=[
    [0,0],
    [USBPCB[1]/2,0],
    [USBPCB[1]/2,0.5*USBPCB[2]],
    [USBPCB[1]/3,USBPCB[2]],
    [-USBPCB[1]/3,USBPCB[2]],
    [-USBPCB[1]/2,0.5*USBPCB[2]],
    [-USBPCB[1]/2,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Insert = [3.5,5.2,7.2]; // 6-32 brass insert to match standard electrical screws
    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 = [Insert[OD], // insert for socket 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);
    }
    }
    }
    //———————-
    // Base for hard drive platters
    module PlatterBase(TubeName = DefaultSocket) {
    PCB =
    [36,18,3] // Arduino Pro Mini
    ;
    Tube = search([TubeName],TubeData,1,0)[0];
    SocketHeight = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN] – PanelThick;
    echo(str("Base for ",TubeData[Tube][0]," socket"));
    Overhang = 5.5; // platter overhangs base by this much
    Bottom = 4*ThreadThick;
    Base = [(Platter[OD] – 3*Overhang), // smaller than 3.5 inch Sch 40 PVC pipe…
    (Platter[OD] – 2*Overhang),
    2.0 + max(PCB[1],(2.0 + SocketHeight + USBPCB[2])) + Bottom];
    Sides = 24*4;
    echo(str(" Height: ",Base[2]," mm"));
    Insert = // platter mounting: threaded insert or nut recess
    // [3.5,5.2,7.2] // 6-32 insert
    [3.9,5.0,8.0] // 3 mm – long insert
    ;
    NumStuds = 4;
    StudSides = 8;
    Stud = [Insert[OD], // insert for socket screws
    2*Insert[OD], // OD = big enough to merge with walls
    Base[LENGTH]]; // leave room for retainer
    StudBCD = floor(Base[ID] – Stud[OD] + (Stud[OD] – Stud[ID])/2);
    echo(str("Platter screw BCD: ",StudBCD," mm"));
    PCBInset = Base[ID]/2 – sqrt(pow(Base[ID]/2,2) – pow(PCB[0],2)/4);
    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 PCB on foam tape
    rotate(0)
    USBPort();
    }
    for (a = [0:(NumStuds – 1)]) // platter mounting studs
    rotate(180/NumStuds + a*360/(NumStuds))
    translate([StudBCD/2,0,0])
    rotate(180/StudSides)
    difference() {
    cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=2*StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),StudSides);
    }
    intersection() { // microcontroller PCB mounting plate
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    translate([-PCB[0]/2,(Base[ID]/2 – PCBInset),0])
    cube([PCB[0],Base[OD]/2,Base[LENGTH]],center=false);
    }
    difference() {
    intersection() { // totally ad-hoc bridge around USB opening
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    translate([-1.25*USBPCB[1]/2,-(Base[ID]/2),0])
    cube([1.25*USBPCB[1],2.0,Base[LENGTH]],center=false);
    }
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount PCB on foam tape
    rotate(0)
    USBPort();
    translate([0,-(Base[ID]/2 – 2.0 + 1*ThreadWidth),Bottom – 3*ThreadThick]) // legend
    rotate([90,0,180])
    linear_extrude(height=1*ThreadWidth + Protrusion) {
    translate([0,(Base[LENGTH] – 5.5),0])
    text(text=TubeName,size=4,font="Arial:style=Bold",halign="center");
    // translate([0,(Base[LENGTH] – 8.5),0])
    // text(text=str("BCD ",StudBCD),size=2,font="Arial",halign="center");
    translate([0,(Base[LENGTH] – 11),0])
    text(text="KE4ZNU",size=3,font="Arial",halign="center");
    }
    }
    }
    }
    //———————-
    // Drilling fixture for disk platters
    module PlatterFixture() {
    StudOC = [1.16*inch,1.16*inch]; // Sherline tooling plate screw spacing
    StudClear = 5.0;
    BasePlate = [(20 + StudOC[0]*ceil(Platter[OD] / StudOC[0])),(Platter[OD] + 10),7.0];
    PlateRound = 10.0; // corner radius
    difference() {
    hull() // basic block
    for (i=[-1,1], j=[-1,1])
    translate([i*(BasePlate[0]/2 – PlateRound),j*(BasePlate[1]/2 – PlateRound),0])
    cylinder(r=PlateRound,h=BasePlate[2],$fn=4*4);
    for (i=[-1:1], j=[-1:1]) // index marks
    translate([i*100/2,j*100/2,BasePlate[2] – 2*ThreadThick])
    cylinder(d=1.5,h=1,$fn=6);
    for (i=[-1,1], j=[-1,0,1]) // holes for tooling plate studs
    translate([i*StudOC[0]*ceil(Platter[OD] / StudOC[0])/2,j*StudOC[0],-Protrusion])
    PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
    translate([0,0,-Protrusion]) // center clamp hole
    PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
    translate([0,0,BasePlate[2] – Platter[LENGTH]]) // disk locating recess
    linear_extrude(height=(Platter[LENGTH] + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] + 1),$fn=8*4);
    circle(d=Platter[ID],$fn=8*4);
    }
    translate([0,0,BasePlate[2] – 4.0]) // drilling recess
    linear_extrude(height=(4.0 + Protrusion),convexity=2)
    difference() {
    circle(d=(Platter[OD] – 10),$fn=8*4);
    circle(d=(Platter[ID] + 10),$fn=8*4);
    }
    }
    }
    //———————-
    // 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[Tube][T_PUNCHOD]," mm = ",TubeData[Tube][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[Tube][T_SCREWOC]," mm =",TubeData[Tube][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight) // base outline
    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*SocketNut[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(SocketNut[OD],(SocketNut[LENGTH] + Protrusion),NutSides);
    PolyCyl(SocketNut[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]);
    }
    for (i=[-1,1]) // cable retaining slots
    translate([i*(Pixel[OD] + TubeData[Tube][T_SCREWOC])/4,0,(Pixel[LENGTH] – Protrusion)/2])
    cube([Pixel[LENGTH],TubeData[Tube][T_SCREWOC],(Pixel[LENGTH] + Protrusion)],center=true);
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(SocketNut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*SocketNut[OD],(SocketNut[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);
    }
    }
    }
    //———————-
    // Greenlee punch bushings
    module PunchBushing(Name = DefaultSocket) {
    PunchScrew = 9.5;
    BushingThick = 3.0;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," bushing"));
    NumSides = 6*4;
    difference() {
    union() {
    cylinder(d=Platter[ID],h=BushingThick,$fn=NumSides);
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=(BushingThick – Platter[LENGTH]),$fn=NumSides);
    }
    translate([0,0,-Protrusion])
    PolyCyl(PunchScrew,5.0,8);
    }
    }
    //———————-
    // Tube clamp
    module TubeClamp(Name = DefaultSocket) {
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," clamp"));
    ClampWidth = 37.0; // inside of clamp arch
    ClampLength = 20; // along tube base
    ClampScrew = [6.0,7.8,6.0]; // nose of clamp screw
    ClampArc = 3*45; // angle subtended by block bottom
    ClampChordWidth = 2 * (TubeData[Tube][T_TUBEOD]/2) * sin(ClampArc/2);
    ClampChordDepth = (ClampChordWidth / 2) * tan(ClampArc/4);
    echo(str("Chord width: ",ClampChordWidth," depth: ",ClampChordDepth));
    ClampBlock = [ClampWidth,(ClampChordDepth + 2*ClampScrew[LENGTH]),ClampLength];
    difference() {
    translate([0,(ClampBlock[1]/2 + TubeData[Tube][T_TUBEOD]/2 – ClampChordDepth),0])
    intersection() {
    translate([0,0,ClampLength/2])
    cube(ClampBlock,center=true);
    translate([0,(ClampBlock[1]/2 – ClampWidth/2),0])
    cylinder(d=ClampWidth,h=ClampLength);
    }
    translate([0,0,-Protrusion]) // remove tube base (remains centered)
    cylinder(d=TubeData[Tube][T_TUBEOD],h=(ClampLength + 2*Protrusion));
    translate([0,(TubeData[Tube][T_TUBEOD]/2 + ClampScrew[LENGTH]),ClampBlock[LENGTH]/3])
    rotate([-90,0,0])
    PolyCyl(ClampScrew[ID],2*ClampScrew[LENGTH],6);
    }
    }
    //———————-
    // 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 == "PlatterBase")
    PlatterBase();
    if (Layout == "PlatterFixture")
    PlatterFixture();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "TubeClamp")
    TubeClamp("Octal");
    if (Layout == "Bushings")
    PunchBushing();
    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");
    translate([0,-85,0])
    Socket("Magnoval");}
  • Vacuum Tube LEDs: Aligning the Plate Cap Leads

    The original plate cap, even without fins, seemed entirely too large for the 21HB5A tube.  There’s not much wasted space inside and, after trimming the outside a bit, this is about as small as seems possible:

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

    PETG doesn’t bridge well and, after cleaning out the wire hole, the remaining shell didn’t hold the brass tube very securely. Epoxying tubes into two caps at once, with a longer brass tube holding them in alignment, worked well:

    Black PETG Plate Caps - brass tube alignment
    Black PETG Plate Caps – brass tube alignment

    The tube eliminates vertical tilt and you (well, I) can eyeballometrically align the caps and tubes in azimuth. The thin ring of JB Kwik epoxy around the brass tube isn’t visible, so it’s all good:

    21HB5A - Black PETG base - flash
    21HB5A – Black PETG base – flash

    This project may eventually force me to try epoxy coating, high-build primer, and good paint…

  • Hard Drive Platter Drilling Fixture

    After drilling the platter for a Noval tube, I finally made a fixture to hold the platters firmly, but gently, in the proper position for drilling:

    Hard drive platter - drilling fixture
    Hard drive platter – drilling fixture

    The platter sits more-or-less flush with the surface, where credit-card plastic pads work fine. Thinner platters may require compliant padding.

    The solid model has locating pips at ±50 mm from the center and airspace below the platter for the drill bit:

    Vacuum Tube Lights - hard drive fixture - solid model
    Vacuum Tube Lights – hard drive fixture – solid model

    The 1.16 inch hole spacing matches the Sherline’s tooling plate. The center hole seemed like a Good Idea, although it has no purpose right now.

    The OpenSCAD source code is the same as before; just set Layout = PlatterFixture; and it’ll produce the right thing.

  • Smithsonian’s Apollo 11 Command Module

    An old friend asked for a copy of the Smithsonian’s Apollo 11 Command Module. I started with a tiny 1:80 version to check feasibility:

    Apollo 11 CM - 1-80 scale
    Apollo 11 CM – 1-80 scale

    It’s obviously not printable in one piece without a ton of support, so I chopped off the heatsink and printed the parts separately in the obvious orientation:

    Apollo 11 CM - 1-80 scale - split - Slic3r preview
    Apollo 11 CM – 1-80 scale – split – Slic3r preview

    And glued them back together:

    Apollo 11 CM - clamping
    Apollo 11 CM – clamping

    That worked well enough, even without locating pins, to give me confidence that it’d come out all right.

    There’s plenty of gimcrackery surrounding the upper airlock:

    Apollo 11 CM - 1-40 scale - top - Slic3r preview
    Apollo 11 CM – 1-40 scale – top – Slic3r preview

    Most of which simply vanished at 1:80 scale:

    Apollo 11 CM - 1-80 scale - top detail
    Apollo 11 CM – 1-80 scale – top detail

    I made another cut just below the top of the capsule and ran off a 1:40 scale version that came out somewhat better, but it was still ugly:

    Apollo 11 CM - 1-40 scale - mortar detail
    Apollo 11 CM – 1-40 scale – mortar detail

    Much to my astonishment, the grab rail over the side hatch, between the two parachute motars, came out well every time.

    The giant 1:20 scale version would require something over 24 hours of printing, so I went with 1:30 in three pieces:

    Apollo 11 CM - 1-30 scale - sections
    Apollo 11 CM – 1-30 scale – sections

    The top had pretty good detail:

    Apollo 11 CM - 1-30 scale - top section - 1
    Apollo 11 CM – 1-30 scale – top section – 1

    Another view:

    Apollo 11 CM - 1-30 scale - top section - 2
    Apollo 11 CM – 1-30 scale – top section – 2

    Gluing the parts together made it ready for cleanup / finishing / painting:

    Apollo 11 CM - 1-30 scale - assembled
    Apollo 11 CM – 1-30 scale – assembled

    Which he’s better at than I ever will be…

    Natural PETG probably isn’t the right plastic for that kind of model, but it’s what I had on hand.

    Enjoy!

  • Miniblind Cord Caps

    After smashing one of the cord pulls between the sash and the frame:

    Miniblind cord caps - installed
    Miniblind cord caps – installed

    The glittery PETG looks surprisingly good in the sunlight that will eventually change it into dullness. The black flecks come from optical effects in the plastic, not the usual burned PETG snot.

    The solid model is basically a hull around two “spheres”, truncated on top & bottom:

    Miniblind cord cap - solid model
    Miniblind cord cap – solid model

    The interior has a taper to accommodate the knot, but they’re chunky little gadgets:

    Miniblind cord cap - solid model - bottom
    Miniblind cord cap – solid model – bottom

    I thought the facets came out nicely, even if they’re mostly invisible in the picture.

    Each pull should build separately to improve the surface finish, so I arranged five copies in sequence from front to back:

    Miniblind cord cap - 5 sequential - Slic3r preview
    Miniblind cord cap – 5 sequential – Slic3r preview

    If you’re using an M2, the fans hanging off the front of the filament drive housing might come a bit too close for comfort, so rotate ’em upward and out of the way.

    If you remove the interior features and flip ’em upside down, they’d work well in Spiral Vase mode. You’d have to manually drill the top hole, though, because a hole through the model produces two shells.

    The OpenSCAD source code as a GitHub Gist:

    // Cap for miniblind cord
    // Ed Nisley KE4ZNU – August 2016
    //- Extrusion parameters – must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    Protrusion = 0.1;
    HoleWindage = 0.2;
    //——
    // Dimensions
    OD1 = 0;
    OD2 = 1;
    LENGTH = 2;
    Cap = [9.0,16.0,25.0];
    Cord = [2.5,7.0,Cap[LENGTH] – 5];
    NumSides = 8;
    //———————-
    //- Build it
    difference() {
    hull() { // overall shape
    translate([0,0,Cap[LENGTH] – Cap[OD1]/2])
    sphere(d=Cap[OD1],$fn=NumSides);
    translate([0,0,0.5*Cap[OD2]/2])
    sphere(d=Cap[OD2],$fn=2*NumSides); // round the bottom just a bit
    }
    translate([0,0,-Cap[LENGTH]/2]) // trim bottom
    cube([2*Cap[OD2],2*Cap[OD2],Cap[LENGTH]],center=true);
    translate([0,0,Cap[LENGTH] + 0.8*Cap[OD1]]) // trim top (arbitrarily)
    cube([2*Cap[OD1],2*Cap[OD1],2*Cap[OD1]],center=true);
    translate([0,0,-Protrusion])
    cylinder(d=Cord[OD1],h=(Cap[LENGTH] + 2*Protrusion),$fn=NumSides);
    translate([0,0,-Protrusion])
    cylinder(d1=Cord[OD2],d2=Cord[OD1],h=(Cord[LENGTH] + Protrusion),$fn=NumSides);
    }
  • Kenmore Progressive Vacuum Tool Adapters: First Failure

    I picked up a horsehair dust brush from eBay as a lightweight substitute for the Electrolux aluminum ball, discovered that an adapter I’d already made fit perfectly, did the happy dance, and printed one for the brush. That worked perfectly for half a year, whereupon:

    Dust Brush Adapter - broken parts
    Dust Brush Adapter – broken parts

    It broke about where I expected, along the layer lines at the cross section where the snout joins the fitting. You can see the three perimeter shells I hoped would strengthen the part:

    Dust Brush Adapter - layer separation
    Dust Brush Adapter – layer separation

    That has the usual 15% 3D Honeycomb infill, although there’s not a lot area for infill.

    There’s obviously a stress concentration there and making the wall somewhat thicker (to get more plastic-to-plastic area) might suffice. I’m not convinced the layer bonding would be good enough, even with more wall area, to resist the stress; that’s pretty much a textbook example of how & where 3D printed parts fail.

    That cross section should look like this:

    Dust Brush Adapter - Snout infill - Slic3r preview
    Dust Brush Adapter – Snout infill – Slic3r preview

    Anyhow, I buttered the snout’s broken end with JB Kwik epoxy, aligned the parts, and clamped them overnight:

    Dust Brush Adapter - clamping
    Dust Brush Adapter – clamping

    The source code now has a separate solid model for the dust brush featuring a slightly shorter snout; if when the epoxy fails, we’ll see how that changes the results. I could add ribs and suchlike along the outside, none of which seem worth the effort right now. Fairing the joint between those two straight sections would achieve the same end, with even more effort, because OpenSCAD.

    The OpenSCAD source code as a GitHub Gist:

    // Kenmore vacuum cleaner nozzle adapters
    // Ed Nisley KE4ZNU August 2016
    // Layout options
    Layout = "DustBrush"; // MaleFitting CoilWand FloorBrush CreviceTool ScrubbyTool LuxBrush DustBrush
    //- Extrusion parameters must match reality!
    // Print with +1 shells and 3 solid layers
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes end cleanly
    //———————-
    // Dimensions
    ID1 = 0; // for tapered tubes
    ID2 = 1;
    OD1 = 2;
    OD2 = 3;
    LENGTH = 4;
    OEMTube = [35.0,35.0,41.7,40.5,30.0]; // main fitting tube
    EndStop = [OEMTube[ID1],OEMTube[ID2],47.5,47.5,6.5]; // flange at end of main tube
    FittingOAL = OEMTube[LENGTH] + EndStop[LENGTH];
    $fn = 12*4;
    //———————-
    // 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);
    }
    //——————-
    // Male fitting on end of Kenmore tools
    // This slides into the end of the handle or wand and latches firmly in place
    module MaleFitting() {
    Latch = [40,11.5,5.0]; // rectangle latch opening
    EntryAngle = 45; // latch entry ramp
    EntrySides = 16;
    EntryHeight = 15.0; // lower edge on *inside* of fitting
    KeyRadius = 1.0;
    translate([0,0,6.5])
    difference() {
    union() {
    cylinder(d1=OEMTube[OD1],d2=OEMTube[OD2],h=OEMTube[LENGTH]); // main tube
    hull() // insertion guide
    for (i=[-(6.0/2 – KeyRadius),(6.0/2 – KeyRadius)],
    j=[-(28.0/2 – KeyRadius),(28.0/2 – KeyRadius)],
    k=[-(26.0/2 – KeyRadius),(26.0/2 – KeyRadius)])
    translate([(i – (OEMTube[ID1]/2 + OEMTube[OD1]/2)/2 + 6.0/2),j,(k + 26.0/2 – 1.0)])
    sphere(r=KeyRadius,$fn=8);
    translate([0,0,-EndStop[LENGTH]]) // wand tube butts against this
    cylinder(d=EndStop[OD1],h=EndStop[LENGTH] + Protrusion);
    }
    translate([0,0,-OEMTube[LENGTH]]) // main bore
    cylinder(d=OEMTube[ID1],h=2*OEMTube[LENGTH] + 2*Protrusion);
    translate([0,-11.5/2,23.0 – 5.0]) // latch opening
    cube(Latch);
    translate([OEMTube[ID1]/2 + EntryHeight/tan(90-EntryAngle),0,0]) // latch ramp
    translate([(Latch[1]/cos(180/EntrySides))*cos(EntryAngle)/2,0,(Latch[1]/cos(180/EntrySides))*sin(EntryAngle)/2])
    rotate([0,-EntryAngle,0])
    intersection() {
    rotate(180/EntrySides)
    PolyCyl(Latch[1],Latch[0],EntrySides);
    translate([-(2*Latch[0])/2,0,-Protrusion])
    cube(2*Latch[0],center=true);
    }
    }
    }
    //——————-
    // Refrigerator evaporator coil wand
    module CoilWand() {
    union() {
    translate([0,0,50.0])
    rotate([180,0,0])
    difference() {
    cylinder(d1=EndStop[OD1],d2=42.0,h=50.0);
    translate([0,0,-Protrusion])
    cylinder(d1=35.0,d2=35.8,h=100);
    }
    translate([0,0,50.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Samsung floor brush
    module FloorBrush() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=32.4,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=32.4,d2=30.7,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=28.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Crevice tool
    module CreviceTool() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=32.0,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=32.0,d2=30.4,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=28.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Mystery brush
    module ScrubbyTool() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=31.8,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=31.8,d2=31.0,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=26.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // eBay horsehair dusting brush
    module DustBrush() {
    union() {
    translate([0,0,40.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=31.8,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=31.6,d2=31.8,h=30.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=26.0,d2=24.0,h=100);
    }
    translate([0,0,40.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Electrolux brush ball
    module LuxBrush() {
    union() {
    translate([0,0,30.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=30.8,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=30.8,d2=30.0,h=20.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=25.0,d2=23.0,h=30 + 2*Protrusion);
    }
    translate([0,0,30.0 – Protrusion])
    MaleFitting();
    }
    }
    //———————-
    // Build it!
    if (Layout == "MaleFitting")
    MaleFitting();
    if (Layout == "CoilWand")
    CoilWand();
    if (Layout == "FloorBrush")
    FloorBrush();
    if (Layout == "CreviceTool")
    CreviceTool();
    if (Layout == "DustBrush")
    DustBrush();
    if (Layout == "ScrubbyTool")
    ScrubbyTool();
    if (Layout == "LuxBrush")
    LuxBrush();

     

  • FG085 Function Generator

    The topic of function generators came up at Squidwrench a while ago (Sophi was tinkering with LCD shutters) and I finally picked up one of those JYE Tech FG085 DDS function generators to see how they work:

    FG085 Fn Gen - in case
    FG085 Fn Gen – in case

    Short answer: adequate, if you’re not too fussy.

    The board arrived with a bizarre solder defect. It seems a solder stalk yanked one terminal off a ceramic SMD caps:

    FG085 - Solder stalk - C26
    FG085 – Solder stalk – C26

    The schematic and adjacent parts suggested the victim was a 10 uF cap, so I replaced it with one from my stash that worked fine.

    However, after soldering enough of the switches to do something useful, the board wouldn’t power up. With a bit of poking around, I discovered the power jack had +15 V from the wall wart, but the center terminals on the DPDT power switch that should have been connected to the jack showed maybe 0.3 V. Jumpering around the failed via and a short trace on the bottom surface let the board power up correctly:

    FG085 - Jumpered power trace
    FG085 – Jumpered power trace

    If you’re building one of these, solder one pin of each switch, push all the switch caps in place, shove the faceplate over all of them, tape it to the PCB, make sure all the switches are push-able, then solder the remainder of the switch pins. If you do them one by one, you’re certain to end up with a few mis-aligned switches that will either prevent the faceplate from sliding over them or wedge firmly against the side of their assigned hole. Just sayin’.

    It lives in a case from Thingiverse:

    FG085enclosure - 1268379
    FG085enclosure – 1268379

    I tweaked the dimensions slightly to fit the (slightly larger, possibly new, maybe tolerance-eased) front panel, but the bottom mounting screw hole spacing depends on the front panel size, not a specific set of dimensions, leading me to relocate those holes by abrasive adjustment. I didn’t bother with the lid (which doesn’t clear the BNC jack anyway) or the printed plastic feet (having a supply of silicone rubber feet).

    The fancy vent gridwork along the sides printed surprisingly well, even in PETG. I’d have gone with larger slots, although I doubt the thing really needs vents in the first place.

    The DDS sine wave output is rough, to say the least:

    FG085 Fn Gen - 60 kHz sine
    FG085 Fn Gen – 60 kHz sine

    The spectrum shows oodles of harmonic content:

    FG085 Fn Gen - 60 kHz sine - spectrum
    FG085 Fn Gen – 60 kHz sine – spectrum

    A closer look:

    FG085 Fn Gen - 60 kHz sine - spectrum - detail
    FG085 Fn Gen – 60 kHz sine – spectrum – detail

    Stepping back a bit shows harmonics of (and around) the 2.5 MHz DDS sampling frequency:

    FG085 Fn Gen - 60 kHz sine - spectrum - 10 MHz
    FG085 Fn Gen – 60 kHz sine – spectrum – 10 MHz

    For comparison, my old Fordham FG-801 analog function generator has nice smooth harmonics:

    FG-801 Fn Gen - 60 kHz sine - spectrum
    FG-801 Fn Gen – 60 kHz sine – spectrum

    Closer in:

    FG-801 Fn Gen - 60 kHz sine - spectrum - detail
    FG-801 Fn Gen – 60 kHz sine – spectrum – detail

    Of course, that crusty old analog dial doesn’t provide nearly the set-ability of a nice digital display.