AD0SK Project Log

Sep 12, 2020

Update: Stevenson screens and fun with additive manufacturing

I touched briefly on Stevenson screens in my post on hygrometry, mostly with regard to how cool it is that their namesake was the father of the famed author. I'd never built one myself, and never planned on doing until I get around to building a full-on weatherstation like I will, uh, one of these days. Then I realized: my girlfriend has a cheap wireless indoor/outdoor thermometer, and the remote sensor can be a bit, uh, touchy:

remote thermometer in sunlight displaying temp of 122.4degF

I promise it was not 122degF when this picture was taken (low 80s, if even). Sometimes I think "keep out of direct sunlight" warnings aren't taken seriously enough, especially at our altitude. (With apologies to any readers unaccustomed to Farenheit units, this reading is about 50degC, with ambient temperature below 30).

Things like this are why we have 3D printers, so I decided to see what might be publicly available as far as enclosure designs and run one off real quick.

It turns out, Thingiverse has many options; I picked this one somewhat arbitrarily. This was a pretty cool model and brought me a little farther into the mindset of 3D printing than I've been in the past. I know there are some pretty radical things you can do once you get there — printing individual screw threads or a ball in a cage or an entire planetary gearset in situ — but so far I've always treated it as just another traditional process, proceding from a drawing and using exact dimensions with known tolerances etc.

This was a different experience: since I didn't really have any specific dimensions to match, I just scaled it with the dumb slider in my slicing software and ran test prints until it was about the right size to accomodate the sensor. Also, the height of the whole assembly isn't fixed, instead you can print as many of the annular section as you want to make it as tall as you want. I did that until it looked reasonable, drove a self-drilling plastic screw into the bulkhead to mount the sensor, found some all-thread and acorn nuts to tie it all together and called it good.

enclosure mounted to janky backyard fence

If what you built looks janky, just bolt it to something that looks even jankier that you didn't build. 50/50 which goes first, the enclosure or the fence.

At the end of the day, it was pretty fun building something so fast-and-(literally-) loose, the sensor works much better, and I think it looks altogether quite professional hanging there on the back fence.

Aug 01, 2020

Building a Geocities-style Hit Counter with Google Cloud Functions

When I was working on getting this site hosted, I knew I wanted some basic metrics reporting. Just basic view counts and referrers, nothing so overbuilt or privacy-invasive as Google Analytics. Server logs would suffice but my hosting doesn't provide them, and I've been out of the web game for long enough I started asking around my friends for suggestions. Among the responses I received was the following:

LOL I remember those hit counters from the 2000s

You are visitor NUMBER 219

Just do one of those

It would legit be so cool

Another friend, somewhat more helpfully, suggested GoatCounter. This ended up being a pretty perfect fit for my needs and what I ended up going with, but my mind had already worked through how we might go about building a geocities-style hit counter in 2020. Plus, it would legit be so cool.

So, let's do it!

Basic requirements

For those who aren't clear what I'm talking about, what we're going for here is something like this:

an old-school, odometer-style hit counter

An old-school hitcounter, mined from the bowels of archive.org and used without proper attribution like I'm the British Museum or something.

I'm not sure how these actually worked, I'd imagine just a cgi script that used ImageMagick to glue the digit assets together into a final image, although if you had cgi rendering yourself you could probably just stuff the img tags for the correct individual digits and do it that way. The former technique sounds better for our present purposes, for reasons that should become apparent.

It's 2020, so let's assume the visiting browser can display SVG. Let's try and write an endpoint that will keep an internal count, increment it on each GET request, and return that as an SVG that shows the current number. We can then just put that into an img tag and it should just work with no AJAXy complications or client-side anything.

Note, though, we don't currently have any server-side anything either (this site, for instance, being statically-hosted). To make anything work at all, then, let's put our new endpoint up on Google Cloud Functions. This is google's version of lambda/serverless, and should allow us to (1) write a function to service the GET request, (2) communicate with one of the various datastores that google provides to maintain the counter state, and (3) render the SVG code for return as the response payload.

Let's take these tasks in 3, 1, 2 order:

Implementation: the display

For this, we want something that takes an arbitrary integer and returns an SVG document that draws it like we want.

Let's use the drawSvg module for the SVG manipulation. First nail the basic geometry by assuming (very optimistically) six digits to display, which we'll zero-pad from the left. Make each digit 40 pixels wide by 60 pixels tall.

import drawSvg as draw

N_DIGITS = 6
DIGIT_WIDTH = 40
DIGIT_HEIGHT = 60

def get_counter(n):
    """Given integer n, returns a drawSVG.Drawing representing n"""

    d = draw.Drawing(
        width = N_DIGITS * DIGIT_WIDTH,
        height = DIGIT_HEIGHT
    )

    return d

This will give us an SVG with the correct geometry, although there's nothing in it yet. We'll want some digits in there, which we'll presumably want to draw individually, so let's write a helper that breaks those out:

def get_digits(n):
    """return list of single-character strings, left-to-right the digits of n,
    zero-padded to N_DIGITS.
    Raise AssertionError if n exceeds representable digits"""

    fmtstr = f'{{:0{N_DIGITS}d}}'
    digits = list(fmtstr.format(n))
    assert len(digits) == N_DIGITS, f'Overflow trying to fit {n}' \
                                    f'into {N_DIGITS} digits'

    return digits

Since we want the result to pad out to N_DIGITS, and since this might conceivably change, we double-bag it on the format string. Given the default value above, fmtstr ends up being '{:06d}', and thus get_digits(69) returns ['0', '0', '0', '0', '6', '9'] (Note: there are many, many other ways of accomplishing what we're doing, some of them a lot more fun. Quick reminder that "fun" is not necessarily an admirable objective when developing software in a a pedagogical or team environment).

Although perhaps not in keeping with the spirit of the geocities inspiration, we also aggresively trap for an argument that's not representable in the given width (instead of silently overflowing, trying to paste ancillary characters outside the bounds of the SVG draw area, throwing a weird internal exception, or other unexpected behaviors). Thus, get_digits(1234567) will raise AssertionError. To be totally rigorous, we should also check to make sure the argument is an integer and positive-definite. In a professional environment, we'd probably (hopefully?) have unit tests to ensure that exceptional cases like these are handled with a minimum of surprise.

Now that we have these digits, we can apply them as text elements to our Drawing. We add these to the d object within the scope of get_counter above like so:

for digit, x in zip(get_digits(n), range(N_DIGITS)):
    d.draw(
        draw.Text(
            digit,
            fontSize=DIGIT_HEIGHT,
            x=(x+.5)*DIGIT_WIDTH,
            y=DIGIT_HEIGHT*.65,
            fill='white',
            font_family='courier, MONOSPACE',
            center=True
        ))

There's some munging going on to get the characters to align, the details of which I'll spare you (protip: just monkey with it between some combination of code tweaks and devtools with liberal use of the refresh button until it looks right). We will, however, bow in the general direction of cross-platform visual consistency by specifying a font, and go ahead and make the numbers white, to show up against the dark background.

Except, we don't have a dark background yet. The following needs to be specified farther up in the procedure so as to end up on a lower z-layer, but we can use an SVG gradient to get the awesome drum counter visual effect:

g = draw.LinearGradient(0, 0, 0, DIGIT_HEIGHT)
g.addStop(0, 'black')
g.addStop(0.5, '#666')
g.addStop(1, 'black')

d.draw(draw.Rectangle(
    0,
    0,
    width=N_DIGITS * DIGIT_WIDTH,
    height=DIGIT_HEIGHT,
    fill=g
))

And maybe some vertical lines between the digits, while we're at it:

for x in range(-1, N_DIGITS+1):
    d.draw(
        draw.Line(
            x*DIGIT_WIDTH,
            0,
            x*DIGIT_WIDTH,
            DIGIT_HEIGHT,
            stroke_width=8,
            stroke='#222'
        )
    )

Put all together, get_digits(31337) then returns an SVG document that looks something like the following:

svg output of the above code

Not bad for 55 lines of (mostly whitespace) code with no static assets!

Implementation: the deployment

To catch up, we now have a thing that, given a number n, will provide a fancy SVG graphic depicting that number n in the most awesome 90s-tastic fashion possible. It remains to host this someplace that will allow for retrieval of those graphics over the public internet, and which will hopefully take care of incrementing n once per such request, thereby making it a proper hit counter.

First, let's test that our svg generation works in a deployed environment. Define a function test_counter of one argument, the flask.Request object representing the request, but which we'll then ignore. Have this return the svg data from a static n:

def test_counter(request):
    return draw_counter(69).asSvg()

and, for convenience, a Makefile to deploy it:

deploy-test:
    gcloud functions deploy test-counter --entry-point test_counter \
           --runtime python37 --trigger-http

We also need a requirements.txt telling GCF that our execution environment needs drawSVG.

Getting that to actually work will require a bit of configuration in the Google Cloud Console, and I'm going to consider that out-of-scope for this treatment. For one, there are a million blog posts out there about "how to get started with Google Cloud Functions" that go through all that in detail, for two they're all also probably out-of-date because google seems to change the minutiae of the configuration pages quite frequently. Google's own documentation is kept current and also surprisingly good, and I'd refer any readers actually playing the home game to that.

Anyway, (waves hands), given the configuration of my project, this endpoint is now available at https://us-central1-geocities-counter.cloudfunctions.net/test-counter. Amazingly it just works, with the xmlns presumably saving us from having to futz about with MIME types or anything. Now, to make it tick...

Implementation: data persistence

The GCF execution environment is ephemeral, so obviously we can't just use a global variable to retain the counter state, or anything else that's tied to the interpreter lifecycle. So, although it seems like overkill, we're going to need to set up an external datastore to retain this state. GCP is absolutely resplendent with options for doing this: we could use google's Bigtable or Spanner, a hosted instance of MongoDB or Postgres, even a flat file in Google Cloud Store. For this walk-through I'm going to use firestore, which is a NoSQL/document database that google took over with their acquisition of Firebase and that's easy to get started with for projects like this.

As above, I'm going to skip the details of actually setting this up on the Cloud Console side. Suffice to say we've set up firebase for the project and defined a collection called counter. Now, we add a second function:

from google.cloud import firestore

def get_counter(request):
    """GCF entrypoint: retrieves counter state from firestore, initializing if
    necessary.
    Increments this and persists to firestore, returning svg payload of counter
    displaying new count"""

    db = firestore.Client()
    doc_ref = db.collection(u'counter').document(u'count')
    doc = doc_ref.get()
    old_n = doc.get(u'n') if doc.exists else 0

    n = old_n + 1
    doc_ref.set({u'n': n})

    return draw_counter(n).asSvg()

and a make target for same:

deploy:
    gcloud functions deploy counter --entry-point get_counter \
           --runtime python37 --trigger-http

We need the firestore module from the google.cloud package for anything to work, and this must be added to requirements.txt also. Despite being a google product, the Cloud Functions execution environment doesn't inject every possible dependency into the runtime, which we may assume is a Good Thing.

When we instantiate a database client via firestore.Client(), it does know, somewhat magically, that we want to connect to the project's firestore, and it handles this connection, including authentication. This is actually totally awesome since anyone who's done work like this knows getting things to talk like that usually takes at least an hour, along with a large repertoire of curse words to get working properly.

Even though the value we're trying to store is scalar, firebase makes us go through a couple layers of abstraction to get there (being, of course, designed to operate on much larger collections of data, the value of which we'll see in the conclusion). First, we must identify a collection (here called counter), which must be created either through the Cloud Console or via calls to an API we won't treat here. The unit over which these collect is called a document, and these are identified by unique keys. We use the magic key count to identify our single document of concern. The documents are (potentially heterogeneous) associative arrays, which are not yet retrieved at this point. This allows us to check for existence and apply default data if necessary, allowing document lifecycle to be managed at the application level even after relegating that of the collection to the infrastructure. Finally, our scalar value of interest lives on a field of the document we call n, which we then retrieve if the document exists and default to 0 otherwise. We then increment this value, overwrite the document with the new value of n, and pass it to draw_counter to generate our response.

Although it worked above when directly loaded, it turns out that browsers don't seem to render svg in an img tag without either a .svg file extension (which GCF doesn't allow) or the correct mimetype set, so we force that on the response. We then should be able to use this as the src value of an img tag, like so:

img tag with src pointing to our new endpoint

Sit there and jam on the refresh button for a minute to prove to yourself it works.

Conclusion

So that was fun, we've accomplished our goal of implementing a server-side-rendered graphical hit counter I feel is very aligned with the spirit of those we saw on geocities and other sites in the early days of the public web (even if "server" has become a much more nebulous concept since then). Project files may be found at https://github.com/drewhutchison/geocities-counter.

From here, there's a few places we could obviously go:

  • If we don't trust our clients to render SVG, we could use drawSVG's .rasterize() method to return it as a PNG or whatever. Maybe even predicate this on the contents of the request's Accept header.
  • A lot of hit counters back then used a different visual presentation: 7-segment displays, plaintext, or whatever. My feeling is, if we're going old-school skeumorphic, go old-school skeumorphic and stick with the drum counter, but since this is encapsulated in the draw_counter method, we can really do whatever we want. For instance, a bit of signed random added to the y argument of the draw.Text constructor would give us a bit of jitter to the drums for a more authentic 90s feel.
  • There's a classic race condition, in that two simultaneous requests might retrieve the same n and both overwrite with their n+1, resulting in a count being "lost". Obviously this would be unacceptable in certain applications, and there are well-known ways of preventing this, but I think for a simple hitcounter we're OK.
  • This is really the dumbest thing possible and, except for the concern just noted, will advance the count on each discrete GET request. Most things like this are minimally interested in unique visitors. Since we have access to the entire Request object, we could tamp this down by IP, set a browser-side cookie, or employ some more-sophisticated type of client fingerprinting to account for non-unique views (this need not be invasive, in fact, GoatCounter employs a rather ingenious mechanism to track unique visitors in a GDPR-compliant way. See https://www.goatcounter.com/gdpr for details).

The latter would entail a modification of our use of the datastore: instead of just incrementing a scalar n we would keep a set of identifiers (the IP, some uniquely-identifying hash, or whatever) and return the cardinality of this set. The use of a document-store database like firestore makes this a very easy change. At that point, we could also do away with the magic key n entirely and instead use the URL as determined from the request, and we're well on our way to a full-featured analytics system, despite not changing the public-facing API (of a GET to the function endpoint) at all.

But, that would have the effect of reducing the total count, and I think I get more internet points the higher that thing goes. Did you refesh the page to prove to yourself it's working yet?

Jul 27, 2020

WTF, Leatherman?

A little over a year ago I decided to start carrying a multitool and, not knowing anything about the product field, picked up a Leatherman Wave+. I've been more-or-less completely satisfied with it: the scissors and wirecutters tend to runout a bit, but they're nonetheless quite handy and the screwdriver is ingenious and by this point I can hardly conceive of going back to life without it on my hip.

Now, this blog isn't, nor is meant to become a prepper/EDC lifestyle accessory review site: there are plenty of those, they do a great job and even go on to tell you what kind of hunter camo is in fashion this season to wear at your wedding and stuff. I digress. I had an unpleasant experience attempting to service the above-mentioned tool, and am calling that out as part of what I find to be a disturbing trend.

Last week I took my Wave+ fishing, and it ended up spending an hour or so rolling around in bilgewater after falling out of my lap after I used it to unset a hook and, well, you know how these things go. This isn't the most respectful way to treat a tool, for sure, but it hardly constitutes severe abuse near what you'd expect to damage a field tool like this. When I got back to shore I dried and oiled it but noticed it had picked up some sediment internally that made the action a little gritty. No problem, I'll just take it apart and clean it, as I've done with my pocketknife any number of times. Sound good? Sounds good.

Except...

top of leatherman tool handle showing T10 security torx fastener head

What the hell is THAT doing there?

There's an old joke about the Soviet economy: "they pretend to pay us and we pretend to work." I've always thought similarly about security fasteners: the manufacturer pretends they're sufficient to deter any attempt at meddling by the end-user, while the end-user pretends not to attempt any field service since the requisite tools are obviously impossible to obtain.

In point of fact, bit sets for these fasteners were one of the first things I remember e-commerce being AWESOME at providing. I ordered a set in about 2003 (from an online vendor that wasn't Amazon... how different things were).1

Now, if ownership of a security torx bit set is the cost of entry to attempt repair on a modern Leatherman tool, I'm basically OK with that. Really I am. It sets a baseline of earnestness and competence: you've marketed the thing as a fine tool for the distinguishing craftsperson, and you don't want your warranty department getting bogged down when JimBob McHarborFreight ends up bending his the wrong way with help of a rusty screwdriver and a pipe wrench. Makes perfect sense. And, in any case, I've got the right driver, right?

Except...

bottom of leatherman tool handle showing T10 security torx fastener head

Double-what-the-hell-is-THAT doing there?

Yeah. I have one. Because anyone only ever needs one. This is like how anyone only ever needs one can opener: your can-opening needs, such as you ever encounter them, are perfectly met by owning exactly one of the tool for the job, and no one is dumb enough to try and design a can that needs two can openers applied simultaneously to work. Yet there on the other side of the tool, that fastener mates another one with the same drive type, necessetating a second identical driver. This is literally the first time in my life I've ever had need of turning more than one security fastener at the same time. And it's all because some smartass engineer decided something like:

  1. these fasteners just look cooler
  2. (more cynically) we can drive repair revenue and product attrition through by imposing arbitrary hurdles to owner maintenance
  3. (most cynically) we can use this to drive2 sales of interchangeable bits

It turns out, (3) doesn't even make sense, since they don't even sell security bits. Way to hold the line there, leatherman, and keep those bits out the hands of dangerous criminals. You know, the types who might try to replace the battery in their own iPod or turn off the seatbelt warning chime. I honestly can't relate to the mindset of someone who, owning a tool company, would intentionally design and build tools such that they can't be used to work on themselves.

I suppose there's a possibility the reasoning aligned with another option:

  1. we really don't trust our customers to undertake repair of their own tools

Looking at third-party repair instructions,3 there's some medium-complicated stuff in the stack: eccentric bushings and friction washers, but nothing all that gnarly. It can't any more complicated the spring-assist on my pocketknife, and that's something I've torn down to clean enough times to confidently do it blindfolded by this point.4

Again, I'm basically satisfied with the tool in every other way, but this kind of overt hostility toward what should be their key customer base is the sort of thing that might have me looking elsewhere when it comes time to replace it. Remember I'm not a review site, and I wouldn't recommend against buying this tool for this reason alone. Just, if you plan on undertaking a major service operation — like cleaning some grit out of the mechanism — without sending it in for factory service, remember to factor in the cost of an extra set of security bits to the TCO. Then you get to be the only person on your block with both a shiny new Leatherman tool and an otherwise completely redundant set of same.

Notes

  1. Around the same time, I worked in a physics lab with a Polish woman who had a set that her boyfriend had machined himself. If that isn't the most Eastern-European thing ever, I don't know what is, but the point remains: these bit sets are not now, nor have they been remotely difficult to obtain since about the turn of the century, at latest.
  2. No pun intended. I considered changing it to "spur," but, same problem.
  3. Because third-party instructions are all that seem to exist. I kind of doubt the existance of any kind of real service manual, maybe because the "User Guide" is just a two-page product brochure covering their entire multitool line in twelve languages. Rest assured, though, their main website clocks in at 10MB over 168 requests. Leatherman: we may not stand behind our products, but we'll glut your connection with 10MB of FMV lifestyle porn in the attempt to sell you a new one. Think of it as multitool-as-a-service.
  4. OK, maybe the spring-assist is a little easier because it uses regular Torx fasteners because Kershaw are decent human beings. To argue that's any real advantage begs the question.

Jun 28, 2020

Some notes on hygrometry

Every year about this time the weather starts getting interesting, and every year about this time I kick myself for having forgotten that the NWS SKYWARN spotter training classes are only offered in March and April. Luckily this year I wasn't doing much in April and our local NWS office did a FABULOUS job of presenting the training courses online, so I finally got my spotter ID. It's causing me to pay more attention to the weather and to meteorology as a science than I have since probably high school.

Aside from some intense thunderstorms this week (including a tornado warning for cloud rotation observed within city limits, which is VERY uncommon), it's also been uncharacteristically humid. Humidity is something we don't often pay much attention to around here, on account of its often never exceeding 30%. But I started reading up on the subject and learned a couple interesting things.

An instrument used to measure humidity is called a hygrometer, and they can be made to work by any of various phenomena. Mechanical dial-type gauges, such as may be found with a thermometer and barometer on a wall-mounted weather station, work by measuring the distension of a material or structure that expands and contracts in response to atmospheric humidity; human hair is one such material. The dielectric constant of air changes with humidity, so an electronic gauge can be made to work by measuring the time constant of a circuit built around an air-gap capacitor exposed to atmosphere. Alternatively, solid-state sensors exist which utilize a material whose resistance or other electrical properties change after absorbing moisture from the air. These are found in some consumer devices, as well as in the form of discrete components which can be purchased and integrated with hobbyist or industrial systems.

various hygrometers

From left: Home weather station with dial-type hygrometer, barometer, and thermometer (via Wikimedia Commons, photo by Friedrich Haag); Low-cost digital thermometer and hygrometer; Hobbyist capacitative-type discrete humidity sensor from Sparkfun

A different, arguably more direct device works on the principle that evaporation occurs in inverse relationship to air humidity. Thus, by measuring the temperature differential between the ambient air and an thermometer evaporatively cooled in presence of same, the air humidity can be determined. Such a device is specially called a psychrometer.

These have two thermometers, one "dry bulb" which measures ambient air and one "wet bulb" which is moistened and reads cooler because of evaporation. Though I never built one, I remember seeing instructions in many childhood science books for how to do so. These were swung around over one's head to create enough airflow to sufficiently cool the wet bulb; precision instruments use a calibrated fan or blower to provide a known amount of airflow.

The relationship between humidity and the two temperature readings depends also on barometric pressure, and, if there's an analytic formula describing this relationship, I've never seen it. Instead all I've ever seen are tables, like the one shown below. In fact, I was given advice by a Physics instructor once to the effect of "you need a CRC manual from roughly every ten years or so, because they stop putting certain information in there. I was trying to find a wet-bulb hygrometer table the other day and I had to go back YEARS." Fortunately my 1967 edition has one (alongside other then-useful-now-WTF data like a table of haversines and the physical properties of whale oil).

"Relative Humidity from Wet and Dry Bulb Thermometer"

Table of humidities as determined by psychrometer reading, from the CRC Handbook of Chemistry and Physics, 48th ed. (1967-68)

Sources of error for this instrument will be temperature effects from things other than the ambient air: radiative heating from the sun or other surroundings, or direct impingement of precipitation, for instance. You've probably seen the louvred enclosures at an airport (or, as my brother pointed out, at the end of the coincidentally-named movie Heat) that are used to mitigate these effects while allowing air more-or-less to freely circulate. I learned from the wikipedia article that these are called Stevenson screens and that their namesake inventor was the father of Robert Lewis of the name, author of Treasure Island (!)

Stevenson screen in field

A Stevenson screen

To conclude with one more interesting factoid from the wikipedia article:

One of the most precise types of wet-dry bulb psychrometer was invented in the late 19th century by Adolph Richard Aßmann (1845–1918); in English-language references the device is usually spelled "Assmann psychrometer."

Which, like, heh.

Jun 06, 2020

A small contribution to the COVID effort

My girlfriend teaches at a local college which has what they call an "innovation and creativity center." This is like a hackerspace for college students: they have a small laser cutter, a bank of FDM machines, the standard stuff. Its purpose is to give students access to resources for product design, rapid fabrication, and other resources that students might employ throughout their studies. When COVID hit and they sent most of the students home, a small group of faculty and remaining student workers started turning that equipment to produce PPE for local hospitals and other communities in need.

So now they're making face shields, with a throughput of a few hundred units a week. They're making a couple different models from the many that exist now (the fire department has stated a preference for one model, the hospitals like a different one) but which are of the same general construction. This consists of an FDM1 or resin-cast frame, which holds an elastic piece in back for securement and a piece of transparent PET sheet that comprises the shield. The latter piece is laser-cut both for general shape and for holes to mate the mounting studs projecting from the frame.

completed facemasks

Assembled facemasks. Note the mounting of the shield material to the frame.

One problem they were having is that the PET sheet they've sourced comes on giant rolls, and the material needs to be rough-cut down to fit on the 16x12" bed of the laser cutter. The rolls are 48" wide, so this is easily enough cut down to either dimension, but the difficulty comes in making a transverse cut across 48" of material that's both reasonably square and also close to the 10" dimension required by the design. Some slop is forgivable, since each shield is cut entirely from the interior of a piece and none of the rough cuts forms a finished edge. But it was still proving awkward and cumbersome to measure and square this by hand while managing the not-insignificant bulk of the roll itself. This had become a bottleneck in their production process, so I decided to build a fixture to help.

Design

Design requirements were pretty straightforward, the fixture needed to provide three things:

  1. Some means of retaining the spool and allowing it to unreel in slightly more elegant fashion than just flopping around on the work surface. If this can help prevent its getting scratched, so much the better.
  2. Some form of stop, square to the axial dimension of the reel and against which the working end of the material can be abutted and made fast.
  3. Some sort of guide, rigidly fixed at the desired distance of ten inches from the stop, along which a cutting tool may be worked.

While this allowed a lot of flexibility in implementation, a challenge was the timing. This was late March, when Amazon was still quoting lead times of 4-6 weeks on "nonessential" items, hardware and home improvement stores were basically closed and I myself was trying not to leave the house. Here's what I made do with parts (mostly) on hand:

Assembly

the assembly

The finished fixture

The base is cut from some 3/4" subfloor plywood I had lying around. This was plenty rigid but a little more warped than was reasonable for the application, so I counterbored from the top and tied into a couple lengths of steel strut-channel, which got it straightened enough. With the limited materials I had to work with, this construction is going to be a bit rough.

The reel is stood-off on plywood that the innovation center was kind enough to laser for me, sandwiching some scrap 1x1 for rigidity and tie-in to the base. This plywood is slotted to accommodate a length2 of 3/8" all-thread, on which the ID of the spool sits.

The cutting guide is formed by the T-slot aluminum frame you see swung up toward the image foreground. This is corner-braced internally but also tied to more plates of laser-cut plywood you see up top. Edge-to-edge the frame is 10" wide,3 and serves to measure this dimension in the direction toward the spool from the plywood seen screwed into the base, to which the hinges are affixed4 and which serves as the material stop. When the frame swings down, it then defines a transverse line 10" from the stop. It can be toggle-clamped to hold the material in place, allowing the operator both hands free to manage the cutting tool.

Lessons learned

Altogether this was a very edifying experience, for a couple of different reasons.

From a systems perspective, this was a great object lesson in limiting features to scope of project. The engineer in me looks at requirement (1) above and immediately starts picturing endcaps that mate the ID of the spool and ride on sleeve bearings about the axial shaft such that the spool is balanced around its CoG and spins freely and etc. etc. That part of me is absolutely horrified at the thought of the spool just rough-riding over a threaded rod like a roll of toilet paper5. Yet, it turns out, that works just fine. Any attempt to build a more elaborate mechinism would have taken longer to design and fabricate, imperiled ourselves of a re-roll if something didn't fit or otherwise work, and been more likely to fail "in the field."

roll of toilet paper

Not the most elegant way to hold a spool, but it works.

Similarly, I spent a lot of time thinking about requirement (3) and whether it would be easiest to build a hot-wire cutter versus a straight, hook, or rotary blade, what type of guide would best support same, what kind of clearance or cut-out might be required to accomodate the tool in the bed, etc. etc. before shelving all of that and just building a straight-edge. I figured that, if a captive tool were that important, I'd hear about it. In point of fact, the operators have made do with a manual knife just fine; the straight-edge was all that was required.

There's a lot of talk in engineering circles about the perils of overengineering, YAGNI, project scope, etc. For all that talk, actually being able to define and hit targets is still largely a matter of good judgement and experience and something even seasoned engineers often fail at. Being able to take something from concept to delivery and see that it succesfully meets a need is a rare pleasure and one that keeps me in these fields.

From a personal perspective, it was also very gratifying to be able to execute with the limited parts and supplies I had on hand. For years (at least when it comes to small parts like fasteners and toggle clamps), my policy has been to buy 25-100% more than I need for the current project "in case I need 'em for something else later." This doesn't always feel like the right thing to do, what with the additional clutter it brings and the alternative convenience of modern supply chains. Most of my personal projects spend a long time in what we'll call the "concept/pre-procurement phase." Being forced by circumstance out of that and directly into execution with just the stock I'd accumulated "in case I need it" was, again, a very gratifying and singular experience.

Notes

  1. I'm not going to engage the debate of that FDM isn't a great process for this (that's obvious). With some finishing work they do make great positives for the poured-silicone resin-casting molds, and there's been talk of tooling up an injection molding machine. Plus, when we've still got nurses wearing trashbags, it seems like any number of masks coming off the FDM line are better than letting it sit idle.
  2. actually two lengths joined by a coupling nut in the middle. Like I said, this construction is rough
  3. As close as I could get it with a friend's cut-off bandsaw. My friend has a mill, too, but the extra accuracy didn't seem worth it given the possibility of deflection. In any case, it's within a few hundredths, plenty accurate for the application.
  4. The hinge attachments are slotted to allow for truing the frame against the stops, as well as translating the frame vercially to accommodate different material thickness or protective underlays.
  5. with apologies for possibly overspending on the COVID zeitgeist
©2020 andrew james hutchison • atomrss