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.

Category: Science

If you measure something often enough, it becomes science

  • Quilting Hexagon Template Generator

    Quilting Hexagon Template Generator

    Mary took on the task of finishing a hexagonal quilt from pieced strips, only to discover she’ll need several more strips and the myriad triangles required to turn hexagons into strips. The as-built strips do not match any of the standard pattern sizes, which meant ordinary templates were unavailing. I offered to build a template matching the (average) as-built hexagons, plus a triangle template based on those dimensions.

    Wikipedia has useful summaries of hexagon and equilateral triangle geometry and equations.

    Quilters measure hexes based on their finished side length, so a “1 inch hex” has sides measuring 1 inch, with the seam allowance extending ¼ inch beyond the sides. It’s difficult to measure finished sides with sufficient accuracy, so we averaged the side-to-side distance across several hexes.

    Some thrashing around produced a quick-and-dirty check piece that matched (most of) the stack of un-sewn hexes:

    Quilting Hexagon Cutting Template
    Quilting Hexagon Cutting Template

    That one came from a knockoff of the circle template, after some cleanup & tweakage, but failed user testing for not withstanding the side force from the rotary cutter blade. The inside and outside dimensions were correct, however, so I could proceed with some confidence I understood the geometry.

    Both the pattern width (the side-to-side distance across the inside of the hex) and the seam allowance appearing in the Customizer appear in inches, because that’s how things get measured outside the Basement Laboratory & Fabrication Facility:

    FinishedWidthInch = 2.75;
    FinishedWidth = FinishedWidthInch * inch;
    
    SeamAllowanceInch = 0.25;
    SeamAllowance = SeamAllowanceInch * inch;

    You feed in one side-to-side measurement and all other hex dimensions get calculated from that number; quilters default to a ¼ inch seam allowance. Remember, standard quilt hexes are measured by their side length, so just buy some standard templates.

    This is one of the few times I’ve needed triangle graph paper:

    Hex Quilting Template - geometry doodles
    Hex Quilting Template – geometry doodles

    After I gave up trying to get it right on square-grid paper, of course.

    Solidifying those relations:

    Quilting Hex Template - build layout
    Quilting Hex Template – build layout

    Then math got real:

    Hex Quilting Templates - on strips
    Hex Quilting Templates – on strips

    Both templates have non-skid strips to keep the fabric in place while cutting:

    Hex Quilting Template - grip strips
    Hex Quilting Template – grip strips

    I should have embossed the size on each template, but this feels like a one-off project and YAGNI. Of course, that’s how I felt about the circle templates, so maybe next time I’ll get it right.

    As it turned out, Mary realized she needed a template for the two half-triangles at the end of each row:

    Quilting Hex Template - half-triangle
    Quilting Hex Template – half-triangle

    It’s half of the finished size of the equilateral triangle on the right, with seam allowance added all around. The test scrap of fabric on the left shows the stitching along the hypotenuse of the half-triangle, where it joins to the end-of-row hexagon. Ideally, you need two half-triangle templates, but Mary says it’s easier to cut the fabric from the back side than to keep track of two templates.

    The family portrait now has three members:

    Quilting Hex Template - family
    Quilting Hex Template – family

    The OpenSCAD source code as a GitHub Gist:

    // Quilting – Hexagon Templates
    // Ed Nisley KE4ZNU – July 2020
    // Reverse-engineered to repair a not-quite-standard hexagon quilt
    // Useful geometry:
    // https://en.wikipedia.org/wiki/Hexagon
    /* [Layout Options] */
    Layout = "Build"; // [Build, HexBuild, HexPlate, TriBuild, TriPlate, EndBuild, EndPlate]
    //——-
    //- Extrusion parameters must match reality!
    // Print with 2 shells
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleFinagle = 0.2;
    HoleFudge = 1.00;
    function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    inch = 25.4;
    //——-
    // Dimensions
    /* [Layout Options] */
    FinishedWidthInch = 2.75;
    FinishedWidth = FinishedWidthInch * inch;
    SeamAllowanceInch = 0.25;
    SeamAllowance = SeamAllowanceInch * inch;
    TemplateThick = 3.0;
    TriKnob = true;
    EndKnob = false;
    /* [Hidden] */
    FinishedSideInch = FinishedWidthInch/sqrt(3);
    FinishedSide = FinishedSideInch * inch;
    echo(str("Finished side: ",FinishedSideInch," inch"));
    CutWidth = FinishedWidth + 2*SeamAllowance;
    CutSide = CutWidth/sqrt(3);
    echo(str("Cut side: ",CutSide / inch," inch"));
    // Make polygon-circles circumscribe the target widths
    TemplateID = FinishedWidth / cos(180/6);
    TemplateOD = CutWidth / cos(180/6);
    /* [Hidden] */
    TriRadius = FinishedSide/sqrt(3);
    TriPoints = [[TriRadius,0],
    [TriRadius*cos(120),TriRadius*sin(120)],
    [TriRadius*cos(240),TriRadius*sin(240)]
    ];
    echo(str("TriPoints: ",TriPoints));
    EndPoints = [[TriRadius,0],
    [TriRadius*cos(120),TriRadius*sin(120)],
    [TriRadius*cos(120),0]
    ];
    echo(str("EndPoints: ",EndPoints));
    TipCutRadius = 2*(TriRadius + SeamAllowance); // circumscribing radius of tip cutter
    TipPoints = [[TipCutRadius,0],
    [TipCutRadius*cos(120),TipCutRadius*sin(120)],
    [TipCutRadius*cos(240),TipCutRadius*sin(240)]
    ];
    HandleHeight = 1 * inch;
    HandleLength = (TemplateID + TemplateOD)/2;
    HandleThick = IntegerMultiple(3.0,ThreadWidth);
    HandleSides = 12*4;
    StringDia = 4.0;
    StringHeight = 0.6*HandleHeight;
    DentDepth = HandleThick/4;
    DentDia = 15 * DentDepth;
    DentSphereRadius = (pow(DentDepth,2) + pow(DentDia,2)/4)/(2*DentDepth);
    KnobOD = 15.0; // Triangle handle
    KnobHeight = 20.0;
    //——-
    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=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
    }
    //——-
    // Hex template
    module HexPlate() {
    difference() {
    cylinder(r=TemplateOD/2,h=TemplateThick,$fn=6);
    translate([0,0,-Protrusion])
    cylinder(r=TemplateID/2,h=(TemplateThick + 2*Protrusion),$fn=6);
    }
    for (i=[1:6/2])
    rotate(i*60)
    translate([0,0,TemplateThick/2])
    cube([HandleLength,HandleThick,TemplateThick],center=true);
    }
    module HexHandle() {
    difference() {
    rotate([90,0,0])
    scale([1,HandleHeight/(TemplateOD/2),1])
    rotate(180/HandleSides)
    cylinder(d=HandleLength,h=HandleThick,center=true,$fn=HandleSides);
    translate([0,0,-HandleHeight])
    cube([2*TemplateOD,2*TemplateOD,2*HandleHeight],center=true);
    translate([0,HandleThick,StringHeight])
    rotate([90,090,0])
    rotate(180/8)
    PolyCyl(StringDia,2*HandleThick,8);
    for (j=[-1,1]) {
    translate([0,j*(DentSphereRadius + HandleThick/2 – DentDepth),StringHeight])
    rotate(180/48)
    sphere(r=DentSphereRadius,$fn=48);
    }
    }
    }
    module HexTemplate() {
    HexPlate();
    HexHandle();
    }
    //——-
    // Triangle template
    module TriPlate() {
    linear_extrude(height=TemplateThick)
    intersection() {
    offset(delta=SeamAllowance) // basic cutting outline
    polygon(points=TriPoints);
    rotate(180)
    polygon(points=TipPoints);
    }
    }
    module TriTemplate() {
    union() {
    if (TriKnob)
    cylinder(d=KnobOD,h=KnobHeight,$fn=HandleSides);
    TriPlate();
    }
    }
    //——-
    // End piece template
    module EndPlate() {
    linear_extrude(height=TemplateThick)
    intersection() {
    offset(delta=SeamAllowance) // basic cutting outline
    polygon(points=EndPoints);
    rotate(180)
    polygon(points=TipPoints);
    }
    }
    module EndTemplate() {
    union() {
    if (EndKnob)
    translate([0,(TriRadius/2)*sin(30),0])
    cylinder(d=KnobOD,h=KnobHeight,$fn=HandleSides);
    EndPlate();
    }
    }
    //——-
    // Build it!
    if (Layout == "HexPlate")
    HexPlate();
    if (Layout == "HexBuild")
    HexTemplate();
    if (Layout == "TriPlate")
    TriPlate();
    if (Layout == "TriBuild")
    TriTemplate();
    if (Layout == "EndPlate")
    EndPlate();
    if (Layout == "EndBuild")
    EndTemplate();
    if (Layout == "Build") {
    translate([1.5*TriRadius,-TriRadius,0])
    rotate(180/6)
    TriTemplate();
    translate([-1.5*TriRadius,-TriRadius,0])
    rotate(180/6)
    EndTemplate();
    translate([0,TemplateOD/2,0])
    HexTemplate();
    }

  • Medium Turtle Teleportation: Rail Trail

    Medium Turtle Teleportation: Rail Trail

    Perhaps this is a relative of the tiny turtle I teleported two years ago in the same section of the Dutchess Rail Trail:

    Turtle Teleportation - DCRT near Lagrange Trailhead - 2020-06-19
    Turtle Teleportation – DCRT near Lagrange Trailhead – 2020-06-19

    Such fancy patterns!

    I’m pretty sure box turtles don’t grow fast enough for this to be the same one …

  • OXO Pepper Grinder: Inadvertent Abuse

    OXO Pepper Grinder: Inadvertent Abuse

    Being that type of guy, I’m reasonably sure I would not have bought what’s now clearly labeled as an OXO Radial Pepper Grinder for use as a salt mill:

    OXO Salt Mill - corrosion
    OXO Salt Mill – corrosion

    Mary recalls we got it at Target, back when one could go places and buy things, and I vaguely recall contemplating a wall of OXO gadgets. It’s been a while and I neglected to save the packaging for future reference.

    Obviously not stainless steel, but not lethal, so we’ll continue abusing it.

  • Spiders Discover Radio Astronomy

    Spiders Discover Radio Astronomy

    A view of the middle of the Very Large Array of the National Radio Astronomy Observatory:

    A view of our back yard, one foggy morning:

    Sheet spider webs on lawn - 2020-06-29
    Sheet spider webs on lawn – 2020-06-29

    Coincidence? Ha!

  • Maple Samaras

    Maple Samaras

    Today I Learned that maple seed spinners are properly called samaras:

    Triple and Quad Maple Seeds
    Triple and Quad Maple Seeds

    The Norway Maple near the road produced a bumper crop of triples this year, along with the first quad spinner samara I’ve ever seen.

    Looks like the odds are ever in my favor

  • Monthly Science: COVID-19 Lagged CFR

    Monthly Science: COVID-19 Lagged CFR

    In round numbers, a nasty COVID-19 infection ramps up for a week before you develop enough symptoms to finally get tested. Various states report various combinations of test results as confirmed / probable / tested “cases”, with “tested” including any possible combination (or lack thereof) of viral / antibody presence. As a result, the number of “daily cases” doesn’t mean much, but it’s the only number we’re likely to get. With that in mind, about 6% of those tested have a positive result for whatever they’re being tested for. Got that?

    At some point within a week or two of being infected, tested, and found positive, about 2.8% of all cases will be hospitalized. That’s 2.4% of cases in the 18-49 age bracket and 4.3% of my decade (64-75):

    COVID-19-NET_Image - Weekly Hospitalizations - 2020-06-24
    COVID-19-NET_Image – Weekly Hospitalizations – 2020-06-24

    You get the Weekly Rate chart from the CDC’s weekly data by drilling into the Hospitalization block to reach the summary chart (through Additional Rate Data), dinking with the controls to show the Weekly Rate and COVID-NET Surveillance Area, then turning off the overlapping age ranges. Most of us seem to have an Underlying Medical Condition or two affecting the outcome.

    Roughly a week (more or less, kinda-sorta) after hospitalization, 15% of all patients and 28% of those over 65 will die:

    COVID-19 - Weekly Hospitalization Outcomes by Age - 2020-06-24
    COVID-19 – Weekly Hospitalization Outcomes by Age – 2020-06-24

    You get that chart from the Lab-Confirmed Hospitalizations page by dinking around with the controls for the lower-right pane. The Overall column represent 5800 patients and, as it happens, each column represents about 2000 patients.

    Because it takes about three weeks to go from “infected” to “dead”, the ratio of [daily deaths today] to [daily positive test results from three weeks earlier] gives (In My Opinion) a better indication of the expected outcome than the simpler ratio of [today’s deaths] to [today’s test positives]. Because the news headlines always feature cumulative numbers, these numbers aren’t at the tip of anyone’s awareness.

    Fetch the daily data as a CSV from the COVID Tracking Project’s Historical Data, compute the day-to-day values from the appropriate columns, then slam the columns into a graph:

    COVID-19 - Lagged Daily CFR - 2020-06-23
    COVID-19 – Lagged Daily CFR – 2020-06-23

    The strong weekly component is surely a combination of data aggregation (no weekend reports?) and actual death events (nobody dies on Sunday?), but there’s no way to know from here. There’s plenty of noise in April which I decided to completely ignore; consult the raw data and draw your own conclusion.

    Eyeballometrically, the lagged CFR has been declining linearly by 1% every 3 weeks since mid-May and should be around 2% in July. If you’re under 50 and in reasonable health, the news is even better, because you’re very unlikely to either need hospitalization or die from it. Again, work the numbers out for yourself from the raw data.

    However, AFAICT, those results depend on a relatively unloaded healthcare system, because little of the US has (yet) to experience the catastrophic overload seen during the early onset in Washington state and NYC. This chart of ICU occupancy suggests the worst is yet to come for folks in states where expectations don’t match up to the reality of exponential growth:

    COVID-19 - All-patient ICU occupancy - 2020-06-23
    COVID-19 – All-patient ICU occupancy – 2020-06-23

    It seems having the ICUs tick along at 50% occupancy is about right, so the states with 70+% occupancy don’t have much surge margin.

    Right now, COVID-19 is burning through the US population at about 30,000 confirmed new cases per day, which means 840 people will require hospitalization every day next week (in addition to all the usual hospitalizations for other causes) and, in another week, 126 people will die every day. Maybe 40 people under age 50 will die, so the human herd will develop immunity by killing off we Olde Fartes.

    After I ran those numbers, the rate passed 40,000 cases per day, with no sign of slowing down and indications it’s getting worse faster. Scale my numbers up by 30%: 1100 hospitalizations and 170 deaths per day in a few weeks.

    However, if you live in one of those dark purple states already showing 70+% ICU utilization, don’t do anything starting with “Hold my beer. Watch this!” because you will not get a welcoming Emergency Room reception. The CNN synoptic view of new cases continues to be informative.

    One of Mary’s cronies is married to a guy who knows this whole COVID-19 thing is a hoax: “They’d all have died of something else, anyway.” Plotting all-cause fatalities vs. age (2020 in red, last five years in gray) shows tens of thousands of people are dying from something new this year:

    All-cause deaths by age - current vs historical - 2020-06-26
    All-cause deaths by age – current vs historical – 2020-06-26

    Before you do the happy dance about the downward slope toward the right, read the disclaimer:

    Data are incomplete because of the lag in time between when the death occurred and when the death certificate is completed, submitted to NCHS and processed for reporting purposes. This delay can range from 1 week to 8 weeks or more, depending on the jurisdiction and cause of death.

    Some of the decline is real, because NYC hospitals aren’t running out of body bags nowadays, but much of it seems due to the paperwork not catching up with reality.

    Judging from the slope of the Johns Hopkins summary of daily cases in the US, corroborated by the CNN projections, the doubling time (before the most recent increases) runs around four weeks: five million cases by the end of July and ten million by the end of August. Later this year, we’ll know how well saying “It’ll be gone by April summer Election Day 2021″ without doing anything has worked out for us.

    The overall death rate should decline in a few years, because those (of us?) who died early will reduce the later rate, but it’s not something to look forward to.

    Back to the Basement Laboratory … and, on good days, the rail trail.

  • Glass Tiles: USB Charger Current Waveforms

    Glass Tiles: USB Charger Current Waveforms

    Looking at what comes out of various USB chargers, with the Tek current probe monitoring the juice:

    USB Current-Probe Extender - in action
    USB Current-Probe Extender – in action

    First, a known-good bench supply set to 5.0 V:

    Tiles 2x2 - bench supply - 50 mA-div
    Tiles 2×2 – bench supply – 50 mA-div

    The yellow trace is the Glass Tile Heartbeat output, which goes high during the active part of the loop. The purple trace shows the serial data going to the SK6812 RGBW LEDs. The green trace is the USB current at 50 mA/div, with the Glass Tile LED array + Arduino drawing somewhere between 50 and 100 mA; most of that goes to the LEDs.

    The current steps downward by about 10 mA just after the data stream ends, because that’s where the LEDs latch their new PWM values. The code is changing a single LED from one color to another, so the current will increase or decrease by the difference of the two currents.

    A charger from my Google Pixel 3a phone (actually made by Flextronics and, uniquely, UL listed), with Google’s ever-so-trendy and completely unreadable medium gray lettering on a light gray plastic body:

    Google Pixel charger - dataplate
    Google Pixel charger – dataplate

    The current waveform looks only slightly choppy:

    Tiles 2x2 - Google Flextronics charger - 50 mA-div
    Tiles 2×2 – Google Flextronics charger – 50 mA-div

    An AmazonBasics six-port USB charger from tested by Intertek:

    AmazonBasics charger - dataplate
    AmazonBasics charger – dataplate

    The waveform:

    Tiles 2x2 - Amazon Basics Intertek Basics charger - 50 mA-div
    Tiles 2×2 – Amazon Basics Intertek Basics charger – 50 mA-div

    A blackweb (their lack of capitalization) charger, also made tested by Intertek:

    blackweb charger - dataplate
    blackweb charger – dataplate

    The current:

    Tiles 2x2 - blackweb charger - 50 mA-div
    Tiles 2×2 – blackweb charger – 50 mA-div

    Finally, one from a lot of dirt-cheap chargers from eBay:

    Anonymous white charger - dataplate
    Anonymous white charger – dataplate

    Which has the most interesting current waveform of all:

    Tiles 2x2 - anon white charger - 50 mA-div
    Tiles 2×2 – anon white charger – 50 mA-div

    A closer look:

    Tiles 2x2 - anon white charger - pulse detail - 50 mA-div
    Tiles 2×2 – anon white charger – pulse detail – 50 mA-div

    From the 75 mA baseline, the charger is ramming 175 mA pulses at 24 kHz into the filter cap on the Arduino Nano PCB! The green trace has a few seconds of (digital) persistence, so you’re seeing a lot of frequency jitter; the pulses most likely come from a voltage comparator controlling the charger’s PWM cycle.

    It’s about what one should expect for $1.28 apiece, right?

    They’re down to $1.19 today: who knows what the waveform might be?

    Update: Having gotten a clue from a comment posted instantly after I fat-fingered the schedule for this post, I now know Intertek is a testing agency, not a manufacturer.