Dovetail Storage Grid

I tackled the Spent Spool Storage System (R2S4) project to answer a prompt/challenge “what can we do with spent spools”. Pleased with the result, I was given another prompt: the drawers of a rolling toolbox storing fasteners are filled with boxes of various sizes, exactly as shipped from McMaster-Carr. When we want to grab a fastener from the drawer, it gets to be a pain to read through different label formats to find the right box, then opening the box to verify the fastener is as expected, before removing whatever quantity is required for the project at hand. It’s also not very space efficient, since many of the cardboard boxes are much larger than they need to be.

Can I do better?

Existing Solutions

While I sometimes take on projects just for the sake of learning how to do it myself (I’m currently in the “it is good CadQuery practice” phase) I still think it’s worth spending time to look around and see if a solution exists. Several web searches pointed to Gridfinity, a 3D-printable storage system design built around a standardized unit. Larger bins can be created as multiples of the basic dimension and each tray is held in place with a matching grid base plate. Optional magnets allow the bases to be held more even securely, and the bins can stack vertically on top of each other. Gridfinity is really cool and I like a lot of its concepts except for two particular aspects that make it a less ideal fit for the goal at hand:

Fixed Cell Size: Gridfinity cell size is a feature but also a bug. If we wanted to use Gridfinity in this drawer of fasteners, we would inevitably end up with some space on the sides because this drawer’s internal dimensions are not a clean multiple of Gridfinity cell size.

Filament Use: Gridfinity is designed to be sturdy enough for general purpose, including being strong enough for vertical stacking. This robustness comes at a cost of filament efficiency. Even “thin” trays have walls several millimeters thick. This is overkill if we have a specific application in mind that is light duty and does not require stacking. Such as displaying an array of small nuts, and washers.

My favorite approach to reduce material consumption is to print in “vase mode”, where the print head makes a single loop around shape perimeter leaving a thin wall only as thick as the print nozzle output diameter. (Typically 0.4mm.) Vase mode was the approach used by a few 3D printable storage trays published online. For example this particular design (which I heard about from Alpenglow Industries.)

Making Different Tradeoffs

I started designing a storage system based on a different set of tradeoffs than those made by Gridfinity.

Adjustable Cell Size: I still want my system to be laid out on a grid, but the size of individual cells on that grid can be adjusted to make full utilization of any given space. The idea is that I should be able to turn any given container into a “tackle box” of fully subdivided storage units. Unlike Gridfinity, it means trays printed to one grid size won’t fit with trays made to another, but I’m OK with that for my project.

Minimize Filament Use: I wanted to design a system that I can print in vase mode for minimal filament use in light duty applications. I don’t think I’m giving up the option of stronger trays because I expect heavier duty trays can be printed with thicker walls while still conforming to the same grid.

Interlocking: As part of reducing filament use, I wanted to skip Gridfinity’s base plate. The solution came to me as I was experimenting with vase mode printing. Since vase mode only prints a single perimeter, long straight walls tend to buckle and sag unless we add some structural support features. I had been adding ribs of either triangular or hexagonal profile, but it occurred to me I can solve two problems at once: I can design dovetails into the sides of the tray, and those dovetails will serve as both vase mode structural reinforcement as well as a functional way to lock adjacent trays together.

I will design a storage system that uses interlocking dovetails to keep trays together, and those trays will be sized to fit on a grid of these dovetails. Hence the name of the project: Dovetail Storage Grid.

Prototype

Once I had established my project objectives, it was time to start prototyping.

I started with tiny trays to verify my dovetail interlock concept works in a vase mode printed shape, and to find the optimal size for these dovetails. It looks like the ideal grid size for vase mode printing with 0.4mm nozzle is in the ballpark of 15-20mm. Any smaller and they have trouble locking together. Any larger, and the flat sides become big enough to distort while printing. I can vary grid size within that range in order to fully utilize a specific space.

My first test of this concept is to fill an Adafruit shipping box, because Adafruit is where I buy a lot of my little electronic bits and bobs that need organizing with so I might as well make use of the boxes. The gaps visible all around the trays in this picture show this first attempt failed to fully utilize space. Some of the lessons learned were:

  • When choosing a grid size by diving up a space, don’t round to the nearest millimeter. I rounded down and that little difference added up to a bad fit when multiplied by the number of grid cells.
  • If the goal is for a completely full fit, I need an option to skip printing dovetails for trays destined to sit around the outside because they’re not interlocking with an adjacent tray. The slots across the back and right sides took away usable volume, and the tabs across the front and left sides actually protrude out of the grid and ruin the fit.

At least the height worked out as expected!

I also learned that, despite the dovetails acting as reinforcement ribs, I will still have distortion as the trays grow beyond around three cells wide. The exact limit will vary on different printers using different filament. This particular four-wide tray bent enough during printing that the handle/label area in front couldn’t hold together.

Production

After incorporating lessons from the Adafruit box test run, I felt confident enough to start cranking them out for deployment to the fastener drawer.

Since the light duty trays are printed in vase mode, they had to be printed one at a time on a 3D printer bed. Despite this limitation it is still possible to print multiple trays in a single print job by utilizing PrusaSlicer’s Sequential Printing capability. (UPDATE: PrusaSlicer 2.9.1 included features that made sequential printing a lot easier to use.)

What about the heavier duty trays with thicker walls? They could be printed simultaneously and, because there’s a gap between trays (adjustable, default is 0.2mm) it is possible to print trays in their interlocked position in order to maximize print volume utilization. Here’s a picture taken during an interlocked print. The picture is slightly blurry because the print bed is moving and my camera couldn’t focus precisely.

And here’s a picture after my Pulse XE has completing printing 19 trays together. I tried printing 20 but there was a persistent print adhesion issue with the front right corner that I couldn’t solve so I deleted that corner unit from my print job.

Success of this interlocked print technique is highly dependent on a printer’s dimensional precision. (Versus accuracy, link to Wikipedia review.) Here are some data points ranked by success:

  • Prusa MK4 interlocked print separated with only minor effort.
  • Prusa Mini+ took slightly more effort.
  • Pulse XE took significant effort. Much twisting and shoving and pounding and generous application of bad words.
  • A low mileage Creality Ender 3 V2 took even more effort than Pulse XE. It wasn’t fun but after our effort we did get usable trays out of the print.
  • A high mileage Creality Ender 5 could not do it. All the trays fused together into a single blob. Trays would be distorted to be unusable or outright destroyed by the time they were separated from their neighbors.

Deployment

Once we had the collection of trays on hand, it was time to put them to work organizing the collection of fasteners in the big sliding drawer.

Initial signs are very promising! Thanks to that tiny gap making interlocked print possible, individual trays can be easily lifted out for use. In its absence, its neighbors will maintain formation thanks to their interlocking dovetails, maintaining the space so it’s easy to later slide a tray back in position.

The dovetails also prevent the trays from sliding all over the place when the drawer is opened and closed, which was a problem with cardboard boxes. Speaking of which, here’s a picture of the drawer partway through the conversion process. To the left are fasteners in their new trays, and to the right are fasteners still in their varied boxes and bags.

Future Evolution

One of the goals of this storage system was to increase space efficiency in this fastener drawer. The variable tray size accomplished this very well. Allowing us to use trays just big enough to hold what’s needed instead of a generic cardboard box with a lot of empty unused space inside. It looked like we might have doubled or tripled the capacity of this drawer, but that was only in theory. In practice we ran into a new problem: packing things tightly meant a lot of weight and now we are weight constrained. Meaning we would exceed the weight limit for the drawer before we run out of space packing it with tiny bits of steel. Oops. Well, at least the space efficiency of this system meant we have room to spare that we can use to improve user experience in other ways.

One potential evolution is to add visual distinctiveness. Using this tray system meant we lost the visual cue of different packages and it would help to add some visual cues to help us find the fastener we seek. The easiest approach is to place some one-grid-wide and/or one-grid-long trays and leave them empty purely to serve as visual dividers. Another idea is to print trays using different color filament so we can, for example, use one color for M3 fasteners/washers/nuts/etc. Then a different color for all M4, another for all M5, etc.

While those ideas are being discussed, these uniform orange trays will at least make finding things a little easier than rummaging through a pile of assorted boxes and bags.

Publishing V1

With a better-organized fastener tray, I declare this project a successful version 1.0. I cleaned up the code and made it publicly available on GitHub: https://github.com/Roger-random/storage_grid

The tray generator is written in CadQuery. If you are comfortable setting up a command line Python virtual environment to install CadQuery, you should have no trouble running my script.

For people who are not comfortable with such things, as an alternative my GitHub repository includes a notebook file that can be opened in Google Colab. A web-based Python virtual environment and Google’s variation on a Jupyter notebook. My repository README has more details on how to use it. Anyone with a Google account can load up my script in a Colab notebook, install CadQuery on that Colab virtual machine, run my script, and generate STL files for 3D printing. All without having to install CadQuery on their own computer.

If you are not comfortable with the Python command line and anti-Google, it should be possible to use my notebook on another Jupyter provider (Amazon SageMaker, GitHub Codespace, Kaggle, or your own private Jupyter Lab.) but you’ll have to figure out the details for yourself.


This project is publicly available on GitHub: https://github.com/Roger-random/storage_grid

Tidying Up R2S4 CadQuery Code

Once I was satisfied my spent spool storage system design covered the basics and offered some neat features, it was time to tidy up my CadQuery code. I had been experimenting in a free-wheeling fashion and now I need to apply the Python code organization best practices I’ve learned to date. This is also the point where I graduated this project from my “CadQuery Tests” repository into its own project repository.

I’m not great with names so I struggled with what to name the repository. I had been calling it the spent spool storage system but its acronym SSSS is unwieldy. I thought about calling it S4 but that’s far too close to Amazon S3 for my taste. I decided to pre-pend my GitHub handle “Roger Random” in front, so now it is “Roger Random’s Spent Spool Storage System” or R2S4.

R2S4 sounds like a Star Wars astromech droid, and I’m perfectly OK with that. A quick search online found no canonical R2-S4 sibling for the famous R2-D2, so I could be the first. I can stack several of these spent spools together, add a domed head and two stubby legs, and call the result R2-S4. That might be a fun future project.

In the meantime, I ran into a few snags trying to organize my CadQuery code. Syntactically it is Python but conceptually it’s very different from any previous Python project I’ve undertaken. At least it felt that way to my head, so I had a hard time organizing the code in a way that makes me happy. I suspect I’ll have more of an opinion on how to organize CadQuery code as I gain experience with writing more.

The final problem I struggled with was the fact I’m asking any prospective users to install CadQuery on their computer so they could configure their own storage system and generate STL files for printing. That seems like a big ask, especially given the Python library situation that now requires virtual environments to keep our different Python projects from stepping on each other’s toes. Not ideal!

Sometime after I moved on from this project (but before I got around to writing it down here) I learned of a pretty good answer: we can run small CadQuery projects inside web-hosted Jupyter notebooks available online. Specifically, I was able to run my storage system code inside Google Colab. I thought this was good enough for me to recommend to others, and just one of many lessons I learned while designing a follow-up project: my storage grid.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

Build-As-You-Go Storage System with Tray Labels

I’ve designed a 3D-printable storage system using spent 3D printing filament spools as its central structure. I’ve made the design configurable to fit different size spools and generate different size trays. The parameters to fit a specific spool size are fairly straightforward if we already have one such empty spool in hand: get out some measuring tools and maybe do some math. But the parameters for the storage tray size will depend on what it is storing, and that might not be known yet. So I’ve added a provision to build-as-you-go.

The problem I encounter with many configurable systems is that it assumes I have the full picture when I start. But sometimes I don’t! And I either make some educated guesses that later turn out to be wrong, or I fall into analysis paralysis and don’t do anything at all. This is why I avoid making such an assumption here, and added the provision of printing placeholder segments.

A placeholder segment is a tray base with only the inner locking tab-and-slot ring so it prints with very little filament. So a particular storage bin can start with a single tray to hold what we want to store. The remaining spaces remain empty, with only placeholder segments to keep the whole thing together. It is valid to start with a single 15-degree tray and fill the rest with placeholders until we know what we want to keep there, as shown here:

The tray base visibly extends beyond the rim of the spool because I needed some room to allow the retention loop to flex. Being able to push the lip of the ring downwards makes tray removal easier. While designing the tray to match, I added a 45-degree sloped area at the tray’s top lip which will serve nicely as an area to put a label explaining what’s in that tray. This label area is most visible in a side view like this picture:

Also visible in this picture is that my tray does not occupy the full available vertical volume, there’s a few millimeters left above the tray. Top gap visible in this picture is the same height as the retention ring, so we could lift the tray up and over the ring to remove the tray without bending the ring. If the gap is any smaller, we’d have to push the retention ring down further to create the clearance, or we have to deform the tray to pull it out. (Easier with flexible single-wall vase mode printed trays than thicker stronger trays.)

I debated how big this gap should be. Leaving it large makes the tray easy to remove but risk the tray falling out if the whole spool is jostled. Decreasing the size of the gap reduces the risk of spills, but makes tray removal more of a hassle. I ultimately decided there is no right answer. An user has to make the tradeoff that is right for their scenario, and they can get their ideal tray fit by adjusting the height parameter.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

Storage Trays Configurable To Fit Different Object And Spool Sizes

I thought it would be fun to turn spent 3D printing filament spools into the core of a storage system, and my CadQuery script for generating individual elements can generat a base module matching a removable storage tray using the same parameters. There are two categories of dynamic configuration involved: that to match the spent spool, and that to match user need.

To match a particular spool, the variables I expect to see are the inner radius of the spent spool, the outer radius, and the thickness of a spool. (Which dictates the height of a storage tray.) These are the variables I saw when trying it on two different types of one kilogram spools. (MatterHackers Build, and Filament PM.) I suspect there are additional variables that I have not yet encountered, and they will require future revision. I’m also curious to see if this design will scale well both above and below the popular single kilogram size. Some of the more expensive materials are sold in spools less than one kilogram, and filament can be bought in bulk on large spools 2kg, 3kg, and maybe more. I await a chance to try those in the future, or get feedback from someone else who has tried.

To match a user need, the size of the tray can be varied in terms of angles of degrees of the pie wedge. The low end is constrained by the size of the handle, which is fixed size regardless of the size of the tray because it is meant to fit human fingers and finger size doesn’t change as the tray size changes. For one kilogram spools of 20cm outside diameter, the practical lower limit is a 15 degree wedge. On the high end, I suppose the geometric limitation is 180 degrees because anything larger would start to wrap around the center of the spool making the tray difficult to remove. As a practical matter, though, 120 degrees appear to be the limit and even then we’d probably want a tray with thicker sizes instead of single-wall vase mode. My test prints of 120 degree wedges in vase mode were unacceptably flimsy, though this may be a matter of filament selection.

While testing various sized wedges, I was amused by a happy coincidence. For testing purposes I printed trays of various angles in degrees: 15, 30, 45, 60, 90, and 120. When I put them all together, I discovered that list add up to a full circle 360 degrees filling up a spool. Convenient! Another happy coincidence with my design is the need for a little bit of clearance below the retention ring also gave me a nice label area for my tray.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

Tray Mounting Base To Fit Within Spent Spools

I thought it would be cool to reuse spent 3D printing filament spools as the core of a storage system. The storage trays can be configured to be within a range of sizes. Each could be printed either with filament-efficient single-wall vase mode for light duty use, or with thicker walls for heavier duty use. I explored a few ideas on how I might design a self-contained system using interlocking trays, but I quickly gave up on that idea. Fundamentally, these trays form a circle around a spool. And when one tray is removed in use, something has to keep the rest of the trays in place. In my design, that something is the tray base which will stay behind when the tray is removed.

To keep the storage system modular, every tray will have its own corresponding base. All the bases will clip together into a complete ring that encloses the inner diameter of the spool.

How will that base keep the tray in place? My first attempt used small clips that reach from the inner ring out to the outer radius to keep its tray in place. This worked for a small tray, but it failed for larger trays. Each little clips has a fixed amount of holding power and it wasn’t enough for the larger trays. And even when it worked, it was not very satisfactory. Ideal CAD doesn’t translate to real world 3D print all the time, and such small errors meant the clips were unreliable and difficult to use.

Those clips then evolved into a small loop to keep the tray in place. The loop helps keep the base in the intended shape, and the loop also has holding power directly proportional to the size of a tray because a loop gets bigger as its corresponding tray does. I’m still trying to be frugal with filament, so I started with thin loops (pictured) that didn’t work before increasing their thickness until I had a reliable and satisfying tray retention mechanism.

The inner ring also underwent several revisions. It may look like a simple tab and slot mechanism, but it took more fine-tuning than I had expected before I had a satisfactory system. The main theme was the fact I wanted to minimize filament usage, so the inner ring was as small as I could make it. Which meant it didn’t leave much room for the tab and the slot. Tabs that are too small tend to break off rather than hold as intended. This design is the smallest tab size that worked reliably, and the smallest ring that I can fit around the tab and slot of that size. Since these factors are a function of strength of 3D-printed plastic, I think it’s better to keep them the same size instead of dynamically changing in response to configuration parameters.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

Storage Trays To Fit Within Spent Spools

Prompted by a fellow 3D printing user’s comment “I wish there’s something we could do with these spent spools” I decided to investigate a project idea that’s been sitting in the back of my head: a dynamically reconfigurable storage system which uses a spent 3D printer filament spool as its structural core. After setting a few goals to give me a direction, I designed the trays that I wanted to fit within these spent spools.

As a starting point I quickly whipped up a pie-shaped wedge solid shape in CadQuery, then I fed it into my 3D printing slicer to be printed in vase mode. Also called spiral mode, or sometimes single-wall mode. Whatever the name given by a particular slicer program, the result is a shape with a single-width wall the thickness of our nozzle diameter. It is very frugal with filament use (one of my goals of this project) but the thin wall raise other challenges. The first is structural. It may be hard to see in the above picture, but the flat sides of the first test tray is bowed instead of flat. The inner and outer curved sides fared better because the tiny bit of curvature gave them enough strength to maintain shape better than the flat sides. To solve this problem, I needed to add small ribs along the flat sides to give it a similar level of structure, and beveled edges top and bottom for additional strength.

Once I had flat sides that could stand on their own, I investigated the second vase mode challenge: strength. There just isn’t very much plastic to hold together, and it is possible to rip a vase mode print apart with our hands. I estimate the strength to be roughly the amount of force we’d need to rip a hole in a sturdy plastic bag. So they’re not tremendously strong, but I think they’re strong enough for light duty use.

But I didn’t want to restrict my system to light duty use. We all have our mental organization models, and it would be inconvenient to have a system that breaks down when something is a little to heavy to be stored alongside similar items. To address this possibility, I modified my CadQuery script so it could generate trays with thicker walls for more strength. The exterior dimensions remain the same. The idea is that we try the frugal vase mode tray first (lower tray in picture) and, if that should fail, we can print a replacement tray with thicker walls (upper tray in picture) which can sit in the same slot.

Another subject of evolution is the front handle. The test tray has no front handle, which obviously won’t work. (Side note: in above picture, the supposed-to-be-flat sides are more visibly bowed.) I tried a few handle design ideas, keeping in mind the shape had to be amenable to vase mode printing. After a few iterations I settled on a small handle in the middle of a spherical depression, almost like a small stack of coins, that my fingers could easily pinch to hold and manipulate the tray.

While I evolved the tray design, I was also evolving the mechanism to hold these trays in place.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

New Project: Spent Spool Storage System

While I was spending much of my free time playing MechWarrior 5: Mercenaries, I still worked on my usual project ideas. I just wasn’t writing them down on this blog! I am already starting to forget important details so I’ll try to catch up the best I can. First up for documentation: the spent spool storage system project.

Everyone using 3D printing to turn ideas into reality will quickly collect a number of spent filament spools. They are intended to be thrown away into landfill, and many do, but every time I hold an empty spool in my hand I wish I had some way to repurpose it. I’ve seen published projects turning a spent spool into a storage tray. (Here is one of many examples.) This is a neat idea, but I feel there’s room for me to offer some meaningful improvements.

The biggest problem with existing solutions is that they are fixed in size. Spool dimensions are not all identical. From my experience spools for one kilogram of filament have some typical dimensions. They usually have an outside diameter of 20cm and a center hole of 5.5cm, with small variations on each value. However, the actual inner diameter of the filament spool area varies a lot more. For the sake of reducing curvature of filament (to make it easier to feed into a printer) the inner spool is usually several centimeters larger than center hole diameter. To accommodate the same amount of filament wound around a larger inner diameter, the thickness of the spool may increase. Different manufacturers make different diameter/thickness tradeoffs.

As a result, STL files published online would fit one specific size of filament spool but would not fit one with different inner diameter or thickness. What we need is a design that can be configured to fit different spool dimensions. There are several configurable OpenSCAD scripts out there, but I’m going to take this as an opportunity to practice CadQuery which will also allow such configurability.

Another disadvantage of published STL is that the storage bin size is fixed. Some of the published designs include small partitions to subdivide a bin, but I’ve used small partitions in commercial storage bins and they tend to slide out of place making a mess. What I really want is to have custom configurable storage bin sizes, and again CadQuery can make the idea practical.

Unrelated to fixed nature of STL, I wanted to reduce filament usage. The storage systems I’ve seen online are built to be sturdy, but I felt they tend to go overboard and use wasteful amounts of filament to accomplish their goal. I’m not trying to store gold bars here! I’m willing to go with lighter construction in order to reduce filament usage.

With these goals in mind, I started by experimenting with storage tray designs.


Source code for this project is publicly available at https://github.com/Roger-random/r2s4

Observations After My First Budibase App

I’ve converted my personal finance tracking information from a spreadsheet to a database. And while I can input new data with a PostgreSQL command line tool (psql) I’m learning how to build a friendlier interface with Budibase. I’ve run into a few problems but since I’m such a beginner it’s too early to say if they are Budibase problems or if they’re just beginner problems. One thing I can say for sure: Budibase made it extremely easy to get up and running with a table-based interface. So my “minimum viable product” is basically what I had before (table in a spreadsheet) and it’s all upside from here.

One thing I’ve noticed about myself is that I have an easier time taking my idea and figure out the PostgreSQL operations needed to make it happen. I have a much harder time figuring out which Budibase tools exist to solve the same data manipulation problems. As a result I have a nice long list of custom SQL queries registered in my Budibase app, trusting Budibase to display the results in a sensible table which it has always been able to do. I think a big part of this is because I have a better idea what keywords to search for when looking to do something with SQL, reinforced by the fact there’s significantly more information on the web about SQL than about Budibase. A smaller part is that I’m still having a hard time understanding certain details in Budibase documentation. For one example: Budibase data binding helpers for array data has two very similar methods: equalsLength and lengthEqual. Budibase documentation explains two of them using very similar language and I can’t figure out how they are different. Why do they both exist? When should I use one versus the other? Budibase documentation does not say, leaving me frustrated.

I also went poking around for entry point/hooks for external code. I guess custom SQL queries technically count as “external code” but I was looking for a general programming language. Earlier I found that Budibase data binding supported snippets of JavaScript for data operations. I found a fairly similar mechanism available for creating Budibase automation. Both cases are intended for fairly small snippets of code, restricted only to standard JavaScript operators. The only input are Budibase binding syntax and the only output is a return statement. In contrast to this limited API, the deep end is a REST API. I didn’t find anything in between these two extremes.

I appreciate how easy it was to in Budibase to build a table-based interface on top of database tables. It is perfectly suited to help people use a real database if they would have otherwise abused a spreadsheet as a database. If they don’t know (or care about) relational database concepts, or SQL query syntax, or writing code, it’s a great tool. But I know all of those things and I’m already itching to do more with them outside of Budibase constraints. Such a project would have to wait, though, because I got distracted by a video game called MechWarrior 5: Mercenaries.

Budibase Relationship Problems: Create

Budibase aims to make solving common problems very easy, and I can understand the design decision to not letting more complex scenarios clutter up their UI. But it still causes headaches for me. I ran into problems trying to display data across multiple tables, and then I learned creating a multi-table record in Budibase can get convoluted for related reasons. The first restriction is the built-in relationship picker tool, which helpfully presents the display column text (Target) and when selected enters the foreign key (476) into the table. But relationship picker has a hard coded ceiling of 100 entries. I don’t have 100 accounts or 100 categories so those will work, but I have definitely shopped at more than 100 vendors! I had to create my own UI to manage the vendor relationship.

I want to write down the next problem here even though it is not yet well understood. Since Budibase couldn’t help me create these multi-table entries directly, I wrote my own interface using Budibase form objects, button objects, and actions associated with button click. The idea is that each form would be a row in one table, and when I click a button, I would use several “Update Field Value” actions to update multiple forms with data generated by my data binding. Then I would use the “Save Row” action on each of those forms to submit my multi-table entry. The idea is to have a mechanism to handle regularly occurring repetitive tasks with a single click.

In practice, my single button would intermittently fail with “Null not allowed” error. If I click my button again, it would always succeed, but then I might have duplicate entries in my database due to my multi-table hack failing partway. This feels like an asynchronous race condition with “Save Row” being executed before all previous “Update Field Value” actions had completed. When I click again a second later, all of the fields are already updated so save row works.

Many databases have a mechanism to designate set of operations as a single “transaction” unit. The database is responsible for guaranteeing “all or nothing”. Either every part of the transaction is successful and written to the database (“commit”) or if something goes wrong, it should be as if nothing had happened. (“revert”) I looked for transaction support via Budibase but didn’t find anything.

Looking for a workaround, I searched for a “Wait for previous action to complete before proceeding” action but didn’t find one, nor did I find anything like a “Wait X second” action. So my workaround is to use two buttons: one has all the “Update Field Value” actions, and a second button with all “Save Row” actions. I click one, then I click the other. Two button clicks is double the amount I thought it should take, but it is still better than doing all the repetitive steps by hand. Still, it doesn’t mask the fact the whole situation is pretty silly and I think reflects a bug in Budibase. But since I’m a beginner it could be my own fault for reasons I don’t understand yet. I’ll have to play with Budibase some more.

Budibase Relationship Problems: Read

Once I got a working knowledge of Budibase data binding, I feel like I’m past the “I have no idea what I am doing” beginner phase and entered into the “I can probably figure out what key words to search for an answer” intermediate phase. The road from here to “I know how to do this” experienced phase is likely paved with a lot of hands-on time. I think Budibase will be able to cover 90% of what I want with very little effort on my part, fulfilling its promise. But that last 10% could motivate me to examine other tools to add to my toolbox.

One example is how Budibase handles table relationships. For every database table, we have to designate one column as a special display column. When displaying tables with defined relationships to other tables, Budibase can automatically replace an foreign key reference with the display column from that column table. My “vendors” table has a “name” column as its display column. Each row in my “transactions” table has a “vendor_id” column that points to a row in “vendors” table. Budibase can combine them so when it shows a row from my “transactions” table, instead of showing the vendor ID (476) it queries my “vendors” table and shows the “name” for that ID instead (Target). This is great and it covers majority of what people would need.

But if I want to go deeper, I’m on my own. My example is my “payments” table, which was my way of implementing classic double-entry bookkeeping and each row has two transaction IDs. One is a deduction transaction from my checking account, the other a credit transaction for the same amount to my Target card account. I wanted to create Budibase dashboard showing a payment amount from “Checking” to “Target”, but those names are two levels away (“payments” to “transactions” to “vendors“) and I couldn’t find a way to do it within Budibase. I ended up solving my problem with a custom PostgreSQL query that performed multiple table joins, then returned the selected results to Budibase, which then dutifully displayed it as a table. I may find a more elegant way to accomplish what I want in the future, but for today this is good enough while I struggle with other problems.

Budibase Data Binding

After migrating from a spreadsheet into a PostgreSQL database, I could get to work building an interface for my data with Budibase. The big sales pitch for Budibase is that I could be up and running in minutes. And that is indeed true as long as the interface I want is a big spreadsheet-like table. If I want something more specifically tailored to my data, it’s going to take more than a few minutes. Laying out a page with data input/output elements is a pretty straightforward drag-and-drop WYSIWYG affair, but bringing that page to life with real data requires Budibase data binding.

Immediately following introduction of data binding, Budibase documentation jumps into discussing the two supported languages. (“Handlebars” and “JavaScript”.) I felt they should have covered data binding scope first. In hindsight understand that would have been a useful mental boundary before diving into syntax details of the two methods.

Handlebars

The syntax more commonly seen in Budibase documentation is referred to as “handlebars”, and it can be recognized by the two curly braces that open and close a handlebar expression. Judging by a package.json file, this was built on top the HandlebarsJS library alongside a Budibase helper library. My first exposure to it in Budibase documentation was this example from the Design/Components/Table page:

{{ toFixed (sum (pluck Table Block 1.Selected Rows 'KGS')) 2 }} KG

This extracts data from the “KGS” column of all selected rows in Table Block 1, add those values together, round the sum to two decimal places, then append a “KG” suffix for display. Handlebars syntax allowed specifying such common operations quickly, a really powerful tool but completely inscrutable to a beginner not versed in the compact syntax. It also has its limitations. A significant one is that output of every handlebar expression is always a string. (Called out at the end of the formula page.) Plus, its extensive helper library can’t cover everything. There would inevitably come a time when we need access to a general-purpose language for the job.

JavaScript

If I want to create a binding that can’t be done in handlebars (either fundamental limitation or just I don’t know how) I can click a button to switch to JavaScript mode. This opens up a small text window for a JavaScript code snippet editor and my first step from “no-code” Budibase to “low-code” Budibase. Input comes from an expression that starts with a jQuery-like “$”. The counterpart to the handlebars example above would be something like $('Table Block 1.Selected Rows'). That expression returns a JavaScript array (one element is one row) of dictionaries (key/value pairs of column name and value). From there, standard JavaScript syntax and libraries apply, and my JavaScript code runs until a return statement at the end with a processed value for Budibase to use.

Since the pattern is to consume an array and produce a single output, many of the JavaScript data binding examples use reduce(). That seems to be the most popular one, though other JavaScript array iterators are applicable. I think it’s because handlebars can do most of the rest. In contrast, reduce() takes a snippet of custom logic and applies it. Generally speaking there are times not to use reduce() but as far as I can tell a Budibase binding function is unlikely to touch upon those scenarios.


To venture beyond Budibase default data presentation of a big spreadsheet-like table, I needed a bare minimum working knowledge of data binding in order to build my own interfaces. It didn’t take long before I encountered problems, as expected for a beginner diving in for the first time.

Budibase With PostgreSQL

I now have PostgreSQL running in a Docker container, and it will host my personal finance tracking database while I learn how to build a user-friendly interface front end using Budibase. I decided against using Budibase’s built-in database because, as the documentation stated, “Budibase DB is a NoSQL database and not generally ideal for extensive relationship functionality“. Having a separate data structure has another benefit: if I decide Budibase won’t suit my needs, I can keep the PostgreSQL database and start a new interface project with different technology. There’s a chance this will happen as I’m starting to think perhaps the rest of Budibase aren’t ideal for extensive relationship functionality either.

But I haven’t used it enough to make that declaration, so onward with my project. First I will need to send my normalized data into PostgreSQL. Since I already had everything in a Jupyter notebook, I searched for a PostgreSQL interface library and found psycopg. Thanks to the quickly iterative nature of Jupyter it didn’t take long before all my tables went into a PostgreSQL database. If all goes well, I won’t need my Jupyter notebook anymore.

During these iterations, I would connect my Budibase app to the partially-migrated database to verify what I’ve done so far worked. I add more data with each iteration and I found that if I update PostgreSQL table structure (add/remove column) Budibase starts failing left and right. I guess this isn’t a supported scenario, either. I found the workaround is to go into Budibase “Data” section and click “Fetch tables” again so it can update its own internal handling of new database structure.

Once I migrated all of my data, I can see them within Budibase’s PostgreSQL connector under the “Tables” tab. Pretty straightforward. I could then use “Relationships” table to declare the foreign keys in each table, something I couldn’t do for CSV-imported data tables in Budibase DB. So that’s a win! The third tab “Queries” is interesting. I could figure out custom queries in psql then copy the result into this section for use elsewhere in my Budibase app. Creating an entry in this list is a simple button click, as is editing an existing entry. But if I decide I didn’t need a custom SQL query… I couldn’t find any way to delete an entry from the list of queries. How odd! Well, no matter, I will edit its title to be “ABANDONED” and if I need a custom query in the future, I’ll recycle the slot and edit the abandoned query instead of creating a new one. And there’s a lot of creation ahead, as I learn the division of labor between PostgreSQL queries and Budibase data binding.

PostgreSQL Docker Container Has Tools I Need

I’m learning to build data apps with Budibase, and I have decided to use PostgreSQL as my database. It should let me do things I can’t do with the default database built-in to Budibase. I am not a complete PostgreSQL beginner thanks to a Codecademy course some time ago, but I found that course a bit too beginner-friendly as it didn’t go into as much depth as I liked. This time as a refresher I tried the W3Schools course and didn’t finish it, finding it to be too shallow as well. In the quest for something with more meat, I went to the source: PostgreSQL official documentation. The tutorial section itself is just about perfect for what I want today, and the remainder of the reference is there when I’m ready for more detail.

I’m running Ubuntu Server in my Budibase VM, and PostgreSQL is available as a part of Ubuntu’s application distribution package. For security partitioning purposes, PostgreSQL uses the “postgres” user account instead of the logged-in user and I had trouble logging in with the correct credentials. After a bit of unsuccessful fiddling, I tried an alternative approach of running PostgreSQL as a Docker container. Now I can log in by specifying -u postgres at the docker exec command line.

Once logged in, I verified the Docker image included two tools that would greatly help with my experiments. I had thought I would need to write similar tools myself via PostgreSQL API but reading PostgreSQL tutorial I learned they already existed.

The first is psql, a text command-line interface to PostgreSQL. It allows me to experiment and iterate through my queries at a low level before I try to make them fancy and easy to use with Budibase. The second tool is pg_dump, which backs up my database data in plain text format. The text file is a list of psql commands to recreate my database. I always have the fear that I would accidentally make a hugely destructive change to my database and not even realize it until much later. Now I can periodically take snapshot backups with pg_dump so I can recover from such mistakes. And more than that, I can check the text file diff between these snapshots. I can’t always mentally parse every line of difference but I can rest assured scope of my changes were not more drastic than I had intended as I work with my data.

Choosing PostgreSQL As Budibase External Database

I’m learning how to build data apps with Budibase and thought it would be a good idea to start with its default database so I minimize the number of moving parts as I learn. Unfortunately I ran into limitations on data imported in JSON (JavaScript Object Notation) format, and that was after I ran into entirely different limitations on data imported in CSV (comma-separated values) format. I think the default database approach is a bust.

While disappointing, I have decided not to fault Budibase for my problems. As I learn about Budibase I see it is focused on solving relatively simpler problems where people would be tempted to use a spreadsheet. The target audience is unlikely to care about table joins or database normalization so it’s no surprise Budibase has not prioritized such things with their default tools. If someone really cares, they can use an external relational database. I’ve decided that is me so I will need to choose one.

SQLite is a great choice for small-scale database projects like mine, available in a wide variety of ways including as part of the Python standard library. Unfortunately part of its lightweight design is for SQLite to be built into the client application, and that’s not a pattern Budibase supports. Next step up in the complexity scale is MySQL, which used to be a darling of the open-source world until it was acquired. Now they’re owned by Oracle and I am not fond of their business practices so I will stay clear of MySQL. I don’t need big iron like Oracle or competitor like Microsoft SQL Server. My project is certainly not worth spending money on such databases.

There’s an open-source fork of MySQL called MariaDB that formed in response to MySQL acquisition. I had considered MariaDB until I researched its governance. The people behind MariaDB wants to build a business to sustain the project, which is laudable, but they want to do it as a publicly-traded company and that gives me pause. Open source software financials rarely align with Wall Street desires and I don’t think that will end well for its users.

So that leaves PostgreSQL, which i briefly looked at earlier as subject for a Codecademy course and it’s been on my “I should try building something with it” list for some time. Reading up on PostgreSQL history, I learned it started as a university research project and is now receiving corporate sponsorships in addition to continued university involvement. I don’t know if this is necessarily more financially stable than MariaDB, but at least I don’t have to worry about PostgreSQL doing anti-consumer things to maximize shareholder value. I will use PostgreSQL as my external relational database for my Budibase project, and I will learn more about both PostgreSQL and Budibase as I go.

No Budibase Support For Imported Nested JSON

I took my personal finance tracking spreadsheet and reorganized it into a set of normalized database tables each in a CSV (comma-separated values) file. They imported into Budibase fine but I could not define declare logical relationships between them. Looking for another entry point for Budibase built-in database, I found JSON was another supported format so I had my Python code output my spreadsheet data as JSON. Each entry looks something like this:

{
  "Date": "12/18/2016",
  "Vendor": "Target",
  "Total": 41.24,
  "Items": [
    {
      "Price": 26.27, 
      "Category": "Food"
    },
    {
      "Price": 14.97,
      "Category": "Durable"
    }
  ]
}

This is no longer a set of normalized database tables. It’s closer to how things were laid out in my spreadsheet. A date, a vendor name, and a total that may be divided across multiple items each in a different category. Except each category had a column in my spreadsheet and now it is a nested array in my JSON.

I don’t like this layout as much as my normalized tables. The vendor name (“Target”) is repeated over and over in this JSON format, instead of split off into a dedicated vendor table and referenced via ID. This is vulnerable to data entry errors like a misspelling, which could be found all over my original spreadsheet. The nested item categories are likewise repeated instead of sitting in their own table of categories.

Despite my dislike, I was willing to give it a shot. This data schema is vaguely familiar from the time I briefly played with MongoDB, another NoSQL database. Now I built this table with my Python processing code to see what CouchDB (what Budibase built-in database is based upon) can do with this data. The answer: CouchDB can handle this just fine, but building interactions for this data is too far off the beaten Budibase path.

To Budibase’s credit, it was very easy to build UI to interact with all the top-level items: date, vendor, and total. But then I ran into a brick wall with those nested Items remaining opaque. I could only get my list of two items to show up as:

Items: [ (object), (object) ]

Which is not very useful at all! Searching around Budibase GitHub discussion for help, I found #3394 How to create form to accept an array of items which pointed to #2568 JSON (Key / Value) type. It is possible to define a schema for JSON data that includes nested arrays. However, there’s a catch: this option is only available for JSON data tables newly created within Budibase. This is not available for existing data that was imported. Darn! This is probably on Budibase to-do list for the future, but today I will have to abandon JSON approach and try something else.

No Budibase Support For CSV Data Relationships

I want to migrate my personal finance tracking from a spreadsheet to a database. I knew my spreadsheet had accumulated errors over the years, but I didn’t really know how big of a mess it was. Not until I tried to perform database normalization where I brought up a Jupyter notebook to quickly iterate through this side quest. It handled all the repetitious fixes, some search/replace rules to fix inconsistency, and split the data into multiple tables as per normalization rules. While slicing the data up for multiple tables, my code is also responsible for generating unique identification numbers in a table (primary keys) to be used in references from other tables (foreign keys).

I had used Python default CSV (comma-separated value) library to read data exported from Excel into dictionary objects. I performed my error correction and data normalization with Python dictionaries and at the end I wrote those processed dictionaries back out as CSV files. One file per dictionary representing one database table. I thought I was in good shape as I went into Budibase data import menu to upload these CSV into the Budibase built-in database. The file uploads were uneventful, but then I tried to define relationships between those tables and got stuck. It seems Budibase does not allow relationships to be defined for tables of data uploaded from CSV files, which feels like an odd oversight.

Defining relationships between tables is definitely supported in Budibase. Almost every supported data source type has a “Define existing relationships” section. There’s one for MySQL/MariaDB, there’s a section for Oracle, another for Microsoft SQL Server, and a section for PostgreSQL. There is no such section for CSV Import, but I had thought that was merely a documentation omission. Surely there is a way to define existing relationships between tables! But the option is absent from Budibase user interface, so it wasn’t an omission after all. I probably should have tested this assumption earlier, before I put in all the work for data cleanup and normalization. Now I have to figure out some other way forward. Next candidate: JSON data import.

Jupyter Notebook For Spreadsheet Clean-Up

Reading through Budibase documentation and playing around with the quick start tutorial app, I learned many important details I will need to know as I embark on my own project: converting my personal finance tracking spreadsheet into a database app. Before I start working on the app, though, I’ll need to migrate my data over to a Budibase-friendly format.

Budibase accepts uploading data in CSV (comma-separated values) or JSON (JavaScript Object Notation) format. Excel can export a spreadsheet into CSV file format so I thought it would be easy, but once I loaded up my spreadsheet and took a good close look I realized it won’t be that simple. My multi-sheet Excel file has built up many idiosyncrasies. Most of it was because I evolved my system over time but I didn’t go back and change my older entries to match. My brain can parse the inconsistencies within, because it’s the same brain that created them, but they’d trip up a computer database.

I made a copy of my spreadsheet and started editing it to prepare for migration. I plan to create new analysis tools around my migrated database, so I deleted all of my analysis cells leaving just raw data. I removed all empty rows and columns I used for visual separation, because they’d just be noise in the migration. I exported these results to a CSV, imported into Budibase, and it was still a mess. I need even more cleanup.

My next step was a Jupyter notebook. Data cleaning is a pretty common task for data scientists and Panda is a common tool for the job. I don’t think I need that level of complexity, though. Python has a built-in class to parse CSV into a dictionary and that was sufficient for my needs. It was enough to let me iterate through my records and write rules to repair my data entry errors. Typos in names, inconsistent date formats, stuff like that.

I started updating structure of data as well, at first just to maintain data consistency between old and new data. But then I started changing things more drastically because my brain shifted gears from a spreadsheet table to a database. I started normalizing my data to the best of my ability (I think I got to 4NF but I’m not sure) because I understood that allows more efficient database operation. I’m pretty pleased at how it turned out! I’m sure I could have created spreadsheet macros to enforce data consistency, but a properly normalized database makes many of my old mistakes outright impossible. This was the point when I really felt I am making the right move: my personal finance tracking system should have been a database all along. I like my set of CSV files, one per table, and the next step is to upload them into my Budibase project.

Resurrecting Project Idea: Personal Finance Software

I took a quick look under the hood of Budibase to get a rough idea of its capabilities. Before I start reading Budibase documentation in detail, I also want to designate a “north star” project. I’ve found this helps organize my thoughts when I explore a new piece of technology. Without a goal, I risk wandering aimlessly and losing interest. Keeping a project idea in mind helps me stay focused and absorb information in terms of how it relates to my project. For Budibase, I decided to dust off my idea to build personal finance software tailored exactly for myself.

I’ve used personal finance software to track my money ever since I had money to track. I started with Microsoft Money. After Microsoft shut it down, I switched to Intuit Quicken. As the world became more online, I switched to Mint. It didn’t take long before I decided I didn’t like doing my personal finances that way. (Well before Intuit bought and eventually shut down its competitor.)

When I started with Money, the usage pattern is for me to enter my financial transactions into my PC software. Then when I received my bank statements, reconcile what I entered with my statement to make sure everything matched up. By the time of Mint, such usage was still possible but no longer the focus. I was encouraged to connect to my financial services online to download account activity. Everything is entered and reconciled automatically. I totally understand why this was done. Most people don’t want to spend the time to manually enter their transactions, then manually reconcile them. The modern usage pattern takes no time. But to my mind, such automation took away the first layer of awareness of my own finances. It’s easy for small fraudulent transactions to slip through unnoticed, for example. I didn’t like it.

After transactions are entered and reconciled, all personal finance software include analysis tools to help the user better understand where their money has gone. But as a data nerd, such graphs and charts were never good enough. I can bring up a chart, click to see more details about what contributed to that graph, and the drill-down always ends before I am satisfied with the level of detail. I understand that most people wouldn’t care to spend time for such analysis, but I do! Spending reports. Flow of money. Account activity. More, I crave more!

I have come to accept the fact my own desires are not aligned with the general populace. Which meant I will have to roll up my sleeves and do it myself. Microsoft Excel seemed like an obvious choice as spreadsheets are intrinsically linked to finance. I started a spreadsheet to track my expenses and things worked well enough at the beginning. As my records grew, though, I started hitting limits of my Excel skills. It was good motivation for me to learn more Excel as I go, but eventually I recognized my mistake. Yes, Excel is the king of finance, but I’m not doing any financial calculations. (No compound interest, etc.) What I’m really doing is record keeping, which is a database job! I can continue learning Excel techniques to compensate for the fact it isn’t a database, or I can switch to using a database.

I’ve approached this project idea many times. I had this in the back of my mind when I took Codecademy’s PostgreSQL and MongoDB database classes, for example. I even got as far as creating a GitHub repository called “rogmoney”, but I didn’t do anything beyond setting up a Node.js environment. Looking at the timestamp I believe my thought was to build a personal finance app on Angular but I never even got as far as committing an empty Angular boilerplate app. Now I will make another attempt. It will help me learn Budibase whether the attempt results in my ideal personal finance tool or not.

Solar Lawn Light Functional Evaluation

Several years ago I bought a box of eight solar lawn lights and installed them in my backyard. At the same time (or soon afterwards) I bought a second box of identical units and stored it away for replacement. I guess I expected their lifetime under the southern California sun to be short, and that I expected difficulty in finding exact replacements later. It turns out I was right on both counts and I’m happy my past self left me a box of replacements. My back yard has been dark for a while, but now I can try to bring these lights back online.

The functional evaluation steps were:

  1. Fully charge a NiMH AAA battery cell for testing.
  2. Remove a lawn light’s solar+LED module with a 1/4 turn counter-clockwise. The module may break apart if plastic has turned brittle.
  3. Open battery compartment door. The latch may break if plastic has turned brittle.
  4. Remove old tired NiMH AAA battery cell for proper recycling.
  5. Inspect battery compartment and clean any corrosion built up on terminals.
  6. Insert charged NiMH AAA battery cell.
  7. Cover solar cell, the LED should turn on and illuminate.
  8. If the LED does not illuminate, the circuit board is dead and the entire unit must be replaced. If the battery compartment door is in good shape, keep the door for use with another unit.
  9. Expose solar cell to sunlight, the LED should turn off.
  10. If LED does not turn off after 5-10 seconds of direct sunlight exposure, the solar cell is dead and the entire unit must be replaced. Again the battery compartment door, if in good shape, can be used in another unit.
  11. If all tests pass up until this point, install one of the salvaged NiMH battery cells and reassemble so the battery can charge via solar power.

Using these steps I determined out of the eight original units, half of their solar cells had failed. A fifth unit had a functioning solar cell, but its structure was brittle and broke apart when I removed it from the light. That leaves three of the original units still functioning well enough to get replacement NiMH batteries, one of them also getting a replacement battery compartment door from one of the dead units. New units replaced the failed lights. Now I have back yard night illumination again, and I still have a few new-in-box replacement units ready to go in the years ahead. I’ll reevaluate my lawn light situation once they run out.

Solar Lawn Light Old and New

I have solar lawn lights in my back yard that has failed after sitting in the harsh outdoor environment for several years. Their weakest point appear to be their NiMH batteries which I’m willing to replace. But some of them also have failed solar cells or crumbling internal structure, and replacing a battery won’t bring those back. I’ll have to buy new replacements. I went online shopping for solar lawn lights and failed to find an exact match. I expected this but it was worth a few minutes to look. If I wanted to maintain a consistent appearance, I should have bought a second box at the time and stash it away.

As soon as I had that thought, an echo of memory came up in the back of my head. I went looking in the cabinet where I might keep such things and… yes! I had that same thought earlier, bought a second box, and forgot about it until now.

Putting an old sun-beaten unit next to a fresh one out of the box provided quite the contrast. Both in their painted metal surfaces and in the condition of their solar cells.

Here’s a close-up view of a fresh solar cell. I now know the clear top section is not glass, but I don’t know much else beyond that. Someone more knowledgeable about solar cell construction can probably look at this and foresee all the ways it will break down with age and exposure.

What I have now reflects real world long duration outdoor exposure. The clear top layer has yellowed and developed a rough surface texture. And I can’t tell if the outer edges have eroded or if the clear layer material has shrunk. I don’t know if the white visible patches are in the clear top material or in the solar cell underneath. I do know this solar cell stopped producing power some time back and will have to be replaced by a new one.