<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.danicabrown.com</id>
    <title>Danica Brown</title>
    <updated>2025-08-05T17:41:20.302Z</updated>
    <generator>Feed + NextJS</generator>
    <link rel="alternate" href="https://www.danicabrown.com"/>
    <link rel="self" href="https://danicabrown.com/atom.xml"/>
    <subtitle>I love designing products, writing software, and bringing ideas to life.</subtitle>
    <rights>All rights reserved 2024, Danica Brown</rights>
    <entry>
        <title type="html"><![CDATA[CNC Coolant Filter (0.2)]]></title>
        <id>https://danicabrown.com/posts/cnc-coolant-filter-2</id>
        <link href="https://danicabrown.com/posts/cnc-coolant-filter-2"/>
        <updated>2025-08-04T17:55:00.000Z</updated>
        <summary type="html"><![CDATA[Modifications made to the original filter for easier operation.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Four months ago I made a couple changes to my <a href="/posts/cnc-coolant-filter"><span class="hover:underline text-blue-500 hover:text-blue-700">CNC coolant filter</span></a> to improve operations; I added a slider shelf for easier chip basket access, changed to a lower mesh count (100µm / 140 mesh count) for quicker coolant draining in the chip basket, changed the catch basin to a smaller container with a sealable lid to prevent coolant evaporation when not in use, and made the coolant reservoir more accessibility for checking coolant levels and PH.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Slider shelf with catch basin and chip filter" loading="lazy" width="2000" height="1503" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-open-chips.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Drawbacks of v0.1 Setup</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The <a href="/posts/cnc-coolant-filter"><span class="hover:underline text-blue-500 hover:text-blue-700">v0.1 setup</span></a> filtered chips from the coolant with no spills, so that alone was a huge improvement over the prototype, but beyond that, it left a lot to be desired for ease of use. It was a major pain to empty chips after a job was complete, because there wasn&#x27;t enough room to remove the chip basket — the entire assembly (reservoir + filter catch basin) had to be pulled-out, from under the CNC table, in order to empty the chip basket. My original plans for v0.1 was to cut the front off of the laundry basin and add sliders so the filter could be pulled out, but I couldn&#x27;t bring myself to make the cut.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The awkward coupling of the reservoir and catch basin (laundry tub) also meant it was difficult to check coolant levels and measure PH in the reservoir. The filter mesh (50µm / 270 mesh count) I was using started to clog after 2-3 jobs, and then eventually during a single job. Even after a thorough cleaning of the mesh, it was clogging consistently, and I was feeling anxious about high coolant levels in the chip basket, again.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Updates</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The changes are minor, but they have been a real delight during operation. These minor iterations have made life more enjoyable when using the machine.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-xl"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Slider shelf, catch basin, chip filter and drain viewed from the back" loading="lazy" width="2500" height="1620" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-back.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Ditch the Laundry Tub</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Decoupling the laundry tub <a href="/posts/cnc-coolant-filter"><span class="hover:underline text-blue-500 hover:text-blue-700">(see v0.1 post)</span></a> from the reservoir was the first step in tackling the updates. No matter where I moved the laundry tub it took up too much space and I kept bumping into constraints. Due to it&#x27;s height and vertical location, I&#x27;d have to add a small pump at the drain to get coolant back into the reservoir, which meant adding a float switch for coolant height in the catch basin, or changing the coolant return to be located on the side of the reservoir. All of which I didn&#x27;t want to do. Very quickly I decided that I had to ditch the laundry tub — it felt messy trying to move forward with it.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The simplest idea at the time was to put the chip basket inside a smaller plastic bin and as long as that bin was located higher than the reservoir, it would drain.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Gasket box with chip basket inside" loading="lazy" width="2000" height="1344" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fgasket-box.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I found a plastic container by Sterlite called a Gasket Box with dimensions of 47cm x 37.8cm x 28.3cm / 18.5&quot; x 17.875&quot; x 11.125&quot; and 30L capacity. This had a nice foam gasket around the perimeter and four snap latches to seal the lid, to prevent evaporation when not in use. The latches weren&#x27;t necessary, but the design and shape of the container worked well for me, and most importantly it fit the chip basket nicely.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.homehardware.ca/en/30l-clear-watertight-storage-box/p/4410854"><span class="hover:underline text-blue-500 hover:text-blue-700">Home Hardware</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Hanging Chip Basket</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The chip basket hangs just slightly below the top interior edge of the catch basin (gasket box). I used scrap pieces 5052 aluminum sheet and bent wide S-shaped hangers for each edge of the container.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="S-hook hanger on catch basin interior rim" loading="lazy" width="2000" height="1125" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fs-hook-basin.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I kept the chip basket (seedling plant trays) from v0.1 because they worked very well. I still need to better secure the filter mesh to the tray, but it&#x27;s a minor asthetic issue.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.amazon.ca/dp/B0BF5BGWWZ"><span class="hover:underline text-blue-500 hover:text-blue-700">Amazon</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Empty chip basket" loading="lazy" width="2500" height="1406" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fempty-chip-basket.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Bulkhead Fitting</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I added a bulkhead fitting (drain) to the back of the filter catch basin (gasket box) so coolant would flow back to the reservoir.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Unfortunately I couldn&#x27;t put the fitting for the drain on the bottom of the container due to height restrictions and the coolant return slope. I positioned the drain as low as possible on the side to minimize the coolant remaining in the bottom of the filter catch basin. Not ideal in my mind to have a small amount of coolant always in the bottom of the container, but it would allow me to visually inspect the colour of the coolant easily. I entertained the idea of having a small pump in the bottom of the container to circulate the coolant, but in the end I abandoned that idea.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I also added a ball valve to the reservoir return drain, so the flow could be shut off if I had to move the filter catch basin, or the reservoir.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.homehardware.ca/en/34-poly-bulkhead-tank-adapter/p/3246801"><span class="hover:underline text-blue-500 hover:text-blue-700">Home Hardware</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-xl"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><div class="aspect-w-2 aspect-h-1"><img alt="Empty catch basin on slider shelf" loading="lazy" decoding="async" data-nimg="fill" class="object-cover opacity-0 duration-[2000ms]" style="position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-empty-basin.png&amp;w=3840&amp;q=75"/></div></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Slider Shelf</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The slider shelf allows me to pull-out the filter catch basin and chip basket for easy access. Being able to conveniently empty the chip basket has been the best change by far. There&#x27;s not much to the slider shelf, it&#x27;s just a couple 2x4s, plywood and heavy-duty drawer sliders.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.rona.ca/en/product/richelieu-accuride-full-extension-steel-drawer-slide-silver-100-lb-capacity-20-in-to-40-in-l-2-per-pack-t46322g20-1552776"><span class="hover:underline text-blue-500 hover:text-blue-700">Rona - drawer sliders</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Slider hardware" loading="lazy" width="2000" height="1500" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fslider-hardware.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Filter Mesh</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Lastly, I changed the filter mesh count from 50µm (270 mesh count) to 100µm / 140 mesh count for better coolant flow. Previously a shallow pool of coolant was starting to form and not drain through the basket fast enough, as shown below. After an hour of operation, this pool of coolant would be near the top of the chip basket.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Old mesh clogged and pooling coolant in the chip basket" loading="lazy" width="2000" height="1125" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fpooling-coolant-basket.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The 100µm / 140 mesh count works much better. It filters chips and the coolant drains through immediately. I might try 70µm filter mesh in the future to compare the filtration qualities, but at this time I&#x27;m very happy with 100µm.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.amazon.ca/dp/B09VC48X47"><span class="hover:underline text-blue-500 hover:text-blue-700">Amazon</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-xl"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><div class="aspect-w-3 aspect-h-2"><img alt="Shelf pulled out showing catch basin with empty chip basket" loading="lazy" decoding="async" data-nimg="fill" class="object-cover opacity-0 duration-[2000ms]" style="position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter-2%2Fbasin-empty.png&amp;w=3840&amp;q=75"/></div></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I&#x27;ve only used this new filter setup for 4 months, but it is a significant improvement over the <a href="/posts/cnc-coolant-filter"><span class="hover:underline text-blue-500 hover:text-blue-700">v0.1 setup</span></a>. These minor updates have made life very enjoyable when using the machine. I&#x27;m really glad I took the plunge and ripped out that massive laundry tub.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Wish List</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">There&#x27;s only a couple items I would change to improve operations further.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Dual filter baskets for quick change during operation.</strong> I would love the ability to easily empty the filter basket without stopping operations. I currently have to shut off the coolant pump, shut off the CNC table drain, slide out the filter chip drawer, pull out the full chip basket, swap in an empty chip basket, and try to not drip coolant on the floor from the full chip basket. Once swapped, then I open the CNC table drain and turn the pump back on. It&#x27;s not as smooth and efficient as it could be. Having two baskets with a way to automatically divert the drain flow direction would be lovely.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Coolant level indicator.</strong> This is a wish list item that has carried over from v0.1. Having the ability to see coolant levels during operation is really helpful. I had attached a fuel sending unit on the reservoir, but never got around to hooking up a circuit for it. Having an analog and digital read out would be ideal. Digital to signal a stop if levels are too low, and analog for a quick casual glance.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">A rigid filter catch basin drain pipe.</strong> The flexible hose I currently have attached to the filter catch basin and reservoir allows the drawer to be pulled out. The flexible hose is excessively long to accommodate the full extension of the drawer, and therefore takes up a lot of room, and also has to be supported so the hose doesn&#x27;t sag and stop draining. It works, but it&#x27;s a failure point that I&#x27;d like to make more robust.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Inline filter with high mesh count.</strong> In the original prototype I used an inline filter, and I&#x27;d like to bring that back but with a different setup, possibly on a separate filter circuit that&#x27;s independent of CNC operations. Then I can run the inline filter to clean the coolant a bit better.</p></div></div>
</li>
</ul></div></div>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[SBT Aliases]]></title>
        <id>https://danicabrown.com/posts/sbt-aliases</id>
        <link href="https://danicabrown.com/posts/sbt-aliases"/>
        <updated>2025-02-27T21:47:00.000Z</updated>
        <summary type="html"><![CDATA[Conveniently execute multiple commands with a single string alias in the Scala Build Tool.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If you want to create an alias for frequently used commands, group multiple commands together, or even rename a default command in your Scala project, you can create a Scala Build Tool (SBT) alias.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">For example, I have an alias called <strong class="font-semibold">&quot;ci&quot;</strong> and it runs multiple continuous integration commands in a project: <span class="p-2 bg-slate-100 rounded-sm font-medium">clean;coverage;ca;check;analyze;test;coverageReport</span>. I can easily type two characters in the SBT shell to execute a series of commands.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Aliases File</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In the root of your Scala project, create a file called <span class="p-2 bg-slate-100 rounded-sm font-medium">aliases.sbt</span>. In that file you can add a command alias like so:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">// aliases.sbt
addCommandAlias(&quot;&lt;alias&gt;&quot;, &quot;&lt;command&gt;&quot;)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Replace <span class="p-2 bg-slate-100 rounded-sm font-medium">&lt;alias&gt;</span> and <span class="p-2 bg-slate-100 rounded-sm font-medium">&lt;command&gt;</span> with the values you want to use. For example, to make an alias that compiles all sources, both src and test, the alias can be anything you want, such as <span class="p-2 bg-slate-100 rounded-sm font-medium">ca</span>, and the command will be <span class="p-2 bg-slate-100 rounded-sm font-medium">Test / compile</span>:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">// Compile all files; test and src
addCommandAlias(&quot;ca&quot;, &quot;Test/compile&quot;)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To execute this alias from the command line, you can run <span class="p-2 bg-slate-100 rounded-sm font-medium">sbt ca</span>, or in the SBT shell you can execute <span class="p-2 bg-slate-100 rounded-sm font-medium">ca</span></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I learned of this handy feature from someone&#x27;s tweet(?) on X a couple months ago, and then shortly after discovered a fantastic write-up on <a href="https://www.baeldung.com/scala/sbt-aliases"><span class="hover:underline text-blue-500 hover:text-blue-700">baeldung.com</span></a>. I&#x27;m not sure why the <span class="p-2 bg-slate-100 rounded-sm font-medium">addCommandAlias</span> isn&#x27;t in the SBT <a href="https://www.scala-sbt.org/1.x/docs/Getting-Started.html"><span class="hover:underline text-blue-500 hover:text-blue-700">v1</span></a> or <a href="https://www.scala-sbt.org/2.x/docs/en/"><span class="hover:underline text-blue-500 hover:text-blue-700">v2</span></a> documentation, but it should be. It&#x27;s a great feature. The command is hidden in the depths of the <a href="https://github.com/sbt/sbt/blob/1d16ca95106a11ad4ef0e3c5a1637c17189600da/main/src/main/scala/sbt/Defaults.scala#L4506"><span class="hover:underline text-blue-500 hover:text-blue-700">SBT GitHub repository</span></a>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Samples</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The following sample aliases are from a Scala 3 project I&#x27;m working on. I love the convenience of quickly calling a short alias to execute multiple commands.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">/** Compile all files; test and src */
addCommandAlias(&quot;ca&quot;, &quot;Test/compile&quot;)

/** Run tagged tests */
addCommandAlias(&quot;test-unit&quot;, &quot;testOnly -- -ignore-tags integration&quot;)
addCommandAlias(&quot;test-integration&quot;, &quot;testOnly -- -tags integration&quot;)

/**
  * Check all Scala files (src and test) for potential linting and code format issues. To fix
  * issues, see alias &quot;fix&quot;.
  */
addCommandAlias(&quot;check&quot;, &quot;lint;pretty&quot;)

/**
  * Fix all Scala files for linting and code format issues, should only be used for local
  * development purposes. Do NOT use this in CI.
  */
addCommandAlias(&quot;fix&quot;, &quot;lint-fix;pretty-fix&quot;)

/**
  * Continuous Integration (CI) should clean, compile all, lint check, style format check, run tests
  * and generate test coverage reports. If any of these steps generate warnings, errors, or the code
  * coverage drops below the threshold specified in `build.sbt` then the build should fail.
  */
addCommandAlias(&quot;ci&quot;, &quot;clean;coverage;ca;check;analyze;test;coverageReport&quot;)

/**
  * Check all Scala files (src and tests) for potential issues, this will not fix issues, see alias
  * &quot;lint-fix&quot;.
  *
  * Lint rules are listed in the `scalafix.conf` file, found in the root directory of the project.
  */
addCommandAlias(&quot;lint&quot;, &quot;scalafixAll --check&quot;)

/**
  * Fix all Scala files (src and tests) for linting issues. Do NOT use this alias when compiling in
  * the CI stage, prefer a lint check and fail if warnings/errors.
  *
  * Lint rules are listed in the `scalafix.conf` file, found in the root directory of the project.
  */
addCommandAlias(&quot;lint-fix&quot;, &quot;scalafixAll&quot;)

/**
  * Check sbt and scala files (src and tests) for proper code style formatting. This will ONLY check
  * if the files have been formatted with Scalafmt, it will not apply formatting, see alias
  * &quot;pretty-fix&quot;.
  *
  * Style rules are listed in the `scalafmt.conf` file, found in the root directory of the project.
  */
addCommandAlias(&quot;pretty&quot;, &quot;scalafmtSbtCheck; scalafmtCheckAll&quot;)

/**
  * Fix all sbt and scala files (src and tests) for proper code style formatting. This will update
  * files to match the style formatting.
  *
  * Style rules are listed in the `scalafmt.conf` file, found in the root directory of the project.
  */
addCommandAlias(&quot;pretty-fix&quot;, &quot;scalafmtSbt; scalafmtAll&quot;)

/** Static code analysis */
addCommandAlias(&quot;analyze&quot;, &quot;scapegoat&quot;)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">A big thanks to the <a href="https://github.com/sbt/sbt"><span class="hover:underline text-blue-500 hover:text-blue-700">SBT</span></a> maintainers, such as <a href="https://github.com/eed3si9n"><span class="hover:underline text-blue-500 hover:text-blue-700">Eugene Yokota</span></a>, for all of the amazing work on this incredible tool. Thank you!</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Dependencies</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The following dependencies were used in this post.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li><a href="https://scala-lang.org/download/3.5.2.html"><span class="hover:underline text-blue-500 hover:text-blue-700">scala (3.5.2)</span></a></li>
<li><a href="https://index.scala-lang.org/sbt/sbt/artifacts/collections/1.9.7"><span class="hover:underline text-blue-500 hover:text-blue-700">sbt (1.9.7)</span></a></li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[CNC Coolant Filter (0.1)]]></title>
        <id>https://danicabrown.com/posts/cnc-coolant-filter</id>
        <link href="https://danicabrown.com/posts/cnc-coolant-filter"/>
        <updated>2025-02-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Keep your coolant reservoir free of chips with a do-it-yourself filter setup using off the shelf supplies.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This is a quick breakdown of my current CNC coolant filter setup — what parts I&#x27;m using, what works well, and what needs to change for the next version. I&#x27;ll do a separate post about the coolant system at a later date.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Filter and Reservoir" loading="lazy" width="2000" height="1545" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-reservoir.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Setup</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I constructed a crude coolant system for my desktop hobby CNC machine to keep the stock/part and milling bit cool during operation. It&#x27;s overkill, but it solved a problem I had with the excessively high-speed router bits getting &quot;gummy&quot;, due to heat build-up while milling parts.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To clean the metal chips from the coolant before it returns to the reservoir, I needed a reliable catch basin and filter. My first prototype helped prove that the coolant system worked, but it was prone to backups, overflows, and caused a lot of anxiety during operation. I was constantly monitoring the coolant levels because the filters were prone to clogging often and unexpectantly — very stressful.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The current filter setup is a collection of mostly off-the-shelf parts, and the assembly works well enough that I can enjoy long milling operations with minimal supervision.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Diagram of filter components" loading="lazy" width="1583" height="1143" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fdiagram.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The setup consists of a plastic laundry tub (legs removed), a small tray rack inside the laundry tub, and two mesh-bottom plant grow trays lined with fine nylon screen. The coolant drains through the bottom of the CNC table into the laundry tub below, where the filter cleans out chips before draining into the reservoir.</p></div></div>
<div class=""><div class="mx-auto"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative"><img alt="Final setup of stacked laundry tub and reservoir under CNC table" loading="lazy" width="2000" height="1342" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fstacked-under-cnc.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Parts</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">You can find most of these parts at your local hardware store. I had to order the mesh filter and coolant online.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Laundry Tub</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The laundry tub is spacious with dimensions of 61cm x 56cm x 38cm / 24&quot; L x 22 W x 15&quot; D. The deep sides are great for containing splashes as the coolant drains through the CNC table onto the filters, and in the worst case scenario of a drain clog, the tub can hold a lot of liquid. I like that almost all coolant in the laundry tub will find it&#x27;s way back to the drain hole with the sloped tub bottom. The filter trays fit inside the basin with ample room around the perimeters.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.homehardware.ca/en/24-x-22-polypropylene-laundry-tub-black/p/3268743"><span class="hover:underline text-blue-500 hover:text-blue-700">Home Hardware</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Stock laundry tubs at the local hardware store" loading="lazy" width="2000" height="902" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Flaundry-tub-store.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Storage Bin</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The storage bin is the coolant reservoir and contains a small submerged fish tank pump. The bin has dimensions of 73.7cm x 50.8cm x 39.4cm / 29&quot; L x 20&quot; W x 15.5&quot; H, and has more than enough volume (102L) to hold all of the coolant. I actually only have the bin filled half way with coolant, because it&#x27;s quite heavy to move when I need to slide it out from under the table.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">A large hole drilled into the top of the storage bin lid was an easy way to secure the laundry tub and reservoir together via the drain. Multiple other holes were drilled in the lid for pvc pipe connections for pumps (spindle and circulation).</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.rona.ca/en/product/gsc-technologies-29-d-x-20-w-x-15-in-h-black-and-yellow-plastic-storage-box-102-l-capacity-292015-54205024"><span class="hover:underline text-blue-500 hover:text-blue-700">Rona</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Coolant reservoir interior with submersible fish pump" loading="lazy" width="2000" height="1125" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fbin-interior.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Seedling Plant Trays</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The mesh bottom plant trays are perfect for drainage and holding the filter screen. The dimensions are 38cm x 30cm x 8cm / 15&quot; x 12&quot; x 3&quot;. Prior to finding these trays, I had tried drilling holes in paint trays, and I even made a feeble attempt at making a tray, but both did not work as well as these seedling mesh bottom plant trays. These are great.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.amazon.ca/dp/B0BF5BGWWZ"><span class="hover:underline text-blue-500 hover:text-blue-700">Amazon</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Seedling mesh bottom plant tray" loading="lazy" width="2000" height="1168" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Fplant-tray.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Filter Mesh</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The main goal with the filter mesh is to clean as much metal from the coolant return as possible. My chip size varies from 20mm to 0.1mm, and probably smaller. When I use 3.18mm bits the metal chips produced are incredibly small. I decided to use a mesh that would filter anything I could see with the naked eye. I read somewhere (reference?) that <strong class="font-semibold">50µm (270 mesh count) filter will catch any visible particulate</strong>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Before I discovered that helpful tidbit, I tried many different filters with a wide variety of properties. Metal screens with larger weaves, smaller screens that didn&#x27;t drain, and numerous others. I found that a 50µm (270 mesh count) nylon filter drains well once saturated. A filter with a higher mesh count (smaller microns), doesn&#x27;t drain fast enough, and a filter with smaller mesh count (larger microns) lets too much particulate through. This filter screen size is also surprisingly easy to clean with a brush, and fresh water.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Cleaning filter with brush" loading="lazy" width="2000" height="1125" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-brush.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I still haven&#x27;t figured out the best way to secure the filter mesh to the tray yet, I&#x27;m just using tape for now. I might try sewing the filter with a drawstring so it can be secured around the tray and easily removed if needed.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><em><del>NOTE: I&#x27;m uncertain if this filter mesh is also cleaning the coolant lubricant out of the water. This thought crossed my mind recently when I noticed my coolant lines looking really clear, when they should be green. I&#x27;m not sure if they are clear because I haven&#x27;t added fresh coolant in over a year (probably), or if the filter is too good, or if the coolant is evaporating. I don&#x27;t know at this time, I will report back. TBD.</del></em></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Where to buy:</strong> <a href="https://www.amazon.ca/dp/B09VC48X47"><span class="hover:underline text-blue-500 hover:text-blue-700">Amazon</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Filter mesh tapped to plastic tray" loading="lazy" width="2000" height="1306" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2Ffilter-mesh.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Tray Rack</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To hold the filter trays, I used 2020 black aluminum extrusion I had laying around, and constructed a primitive tiered rack. Originally it was three tiers with varying filter screen sizes, but in the end settled on two tiers with the same filter screen size on both for redundancy.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I&#x27;d like to find an off-the-shelf alternative to the 2020 extrusion rack, perhaps a suspended sink drying rack. Something to hold the filter trays suspended in the air, or raised off the bottom of the laundry tub.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="2020 aluminum extrusion tray rack" loading="lazy" width="2000" height="1580" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcnc-coolant-filter%2F2020-rack.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Wish List</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I&#x27;ve only used this filter setup for a 3 months. It is significantly better than the filter prototype I was using previously — leaps and bounds better. Now that I&#x27;m not anxiously running my CNC worrying about coolant backups in the middle of a job, I&#x27;ve been taking mental notes of the minor drawbacks and annoyances I&#x27;ve noticed during operations.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Tight coupling between tub and reservoir.</strong> Having the laundry tub and reservoir linked together originally seemed like a great idea, but the more I try to maintain and operate the system I see that it is a hinderence and needs to be changed for easier operations. Most of the challenges below are related to this coupling. I would like to move the reservoir to another area.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Awkward to clean the filter.</strong> I need to pull both the laundry tub and reservoir forward to gain access to the filter trays. Then squeeze the trays out a narrow opening between the CNC table and the top of the laundry tub. I was originally planning to cut an opening in the front of the laundry tub for easier access, but it just didn&#x27;t feel right at the time. I also have to be cautious when pulling the entire assembly out, because the reservoir is connected to the coolant lines and electrical wires. Not ideal.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Difficult to monitor and adjust coolant PH levels.</strong> Opening the reservoir lid is really awkward and not practical at all, for all the reasons listed above. For the first time since I purchased <a href="https://cuttingfluid.online/greencut-cutting-fluid/"><span class="hover:underline text-blue-500 hover:text-blue-700">GreenCut cutting fluid</span></a>, I&#x27;ve noticed that my coolant lines are no longer green, so I ordered more coolant. When I received the new container of cutting fluid it had instructions for how to maintain the PH levels. I&#x27;m almost certain the original shipment from a year or two ago also had this, but I misplaced them and forgot. Now I&#x27;m trying to measure the PH levels in the tank and there&#x27;s no easy way to do it. I need to be better at maintaining the coolant PH, and before I can do that I need an easy way to mix, measure and check without pulling the entire assembly out. Not ideal.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Difficult to see coolant levels.</strong> I want to make a coolant level circuit to show what&#x27;s happening inside the reservoir, but haven&#x27;t got around to it yet. I have a fuel sending unit installed in the reservoir to determine levels from resistence changes, but I still need to make the actual circuit. At this time, I just hook up a voltmeter and measure the resistence during CNC jobs to periodically check on coolant levels. Not ideal.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Submerged pump is not easily serviceable.</strong> There is a fish tank pump in the reservoir, a remnant from the original coolant prototype, that sends coolant to the spindle. The pump worked well enough that I didn&#x27;t want to buy another pump when I made this new setup. I dislike having the pump inside the reservoir though. It&#x27;s not a huge deal, but similar to monitoring the PH levels, I need to open the lid to do any pump maintenance. Lately I&#x27;ve been thinking that with all the other challenges listed above, it might be time to move the pump out of the reservoir — rethink the entire setup.</p></div></div>
</li>
<li>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Drain plug or shut-off valve.</strong> When the system is not in use, I&#x27;d like to be able to completely close the reservoir to prevent evaporation.</p></div></div>
</li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Try</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I would like to try cutting fluid paper at some point, but I&#x27;ve only seen it available in large rolls. I would like a small amount to try before I go all in. It could potentially be a great alternative to the filter mesh listed above based on what I&#x27;ve read.</p></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Fix Mockito Agent Warning]]></title>
        <id>https://danicabrown.com/posts/mockito-java-agent</id>
        <link href="https://danicabrown.com/posts/mockito-java-agent"/>
        <updated>2024-12-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to fix the dynamic Java agent warning when using Mockito in your Scala projects]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Summary</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Add the following to your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file to resolve the dynamic loading Java agent warning:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">val mockitoVersion = &quot;5.14.2&quot;
Test / fork := true,
Test / javaOptions := Seq(
  s&quot;-javaagent:${csrCacheDirectory.value.getAbsolutePath()}/https/repo1.maven.org/maven2/org/
      mockito/mockito-core/${mockitoVersion}/mockito-core-${mockitoVersion}.jar&quot;,
),
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Mockito Path</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If you are using <a href="https://site.mockito.org/"><span class="hover:underline text-blue-500 hover:text-blue-700">Mockito</span></a> in your Scala tests (not <a href="https://github.com/mockito/mockito-scala"><span class="hover:underline text-blue-500 hover:text-blue-700">mockito-scala</span></a>) and you are on a recent JVM version (&gt;= 21), you may have seen the following warning:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work 
in future releases of the JDK. Please add Mockito as an agent to your build what is described
in Mockito&#x27;s documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (/Users/username/Library/Caches/Coursier/
  v1/https/repo1.maven.org/maven2/net/bytebuddy/byte-buddy-agent/1.15.4/byte-buddy-agent-1.15.4.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To fix this warning, you need to set the <strong class="font-semibold">javaOptions</strong> for the test JVM in the Scala <strong class="font-semibold">build.sbt</strong> file. You need to find where the Mockito jar file is located on your system. Use the byte-buddy-agent path in the warning message as a hint for where Mockito is probably located. This is the path mentioned in the warning:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">/Users/username/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/net/bytebuddy/
  byte-buddy-agent/1.15.4/byte-buddy-agent-1.15.4.jar
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In the <strong class="font-semibold">maven2</strong> directory, Mockito should be located at the path <span class="p-2 bg-slate-100 rounded-sm font-medium">org/mockito/mockito-core/&lt;version&gt;</span> which will match the dependency entry in your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file (<em>&quot;org.mockito&quot; % &quot;mockito-core&quot; % &quot;5.14.2&quot;</em>). For my system (macOS and SBT), the Mockito jar is located at:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">/Users/username/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/
  mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Options and Fork</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Open the <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file for your Scala project and add the following to your Test configuration:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">Test / javaOptions := Seq(
  &quot;-javaagent:/Users/username/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/
    org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar&quot;,
),
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">You will also need to add <span class="p-2 bg-slate-100 rounded-sm font-medium">Test / fork := true</span> to your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file. The result should look something like this:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">lazy val root = project
  .in(file(&quot;.&quot;))
  .settings(
    // Omitted all project settings for brevity.
    libraryDependencies ++= Seq(
      &quot;org.mockito&quot; % &quot;mockito-core&quot; % &quot;5.14.2&quot; % Test,
    ),
    Test / javaOptions := Seq(
      &quot;-javaagent:/Users/username/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/
        org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar&quot;,
    ),
    Test / fork := true,
  )
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In your SBT shell, <span class="p-2 bg-slate-100 rounded-sm font-medium">reload</span> the project, and re-run your tests. The warning message should no longer be visible. Woo hoo!!</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Let&#x27;s update the path so it&#x27;s not hardcoded to a specific Mockito version. In your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file, create a value for the Mockito version number (<span class="p-2 bg-slate-100 rounded-sm font-medium">val mockitoVersion = &quot;5.14.2&quot;</span>) and use the value in your dependencies:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">&quot;org.mockito&quot; % &quot;mockito-core&quot; % mockitoVersion % Test,
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Modify the javaOptions string to be an interpolated string (notice the &quot;s&quot; at the beginning), and add the Mockito version there too:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">Test / javaOptions := Seq(
  s&quot;-javaagent:/Users/username/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/
    org/mockito/mockito-core/${mockitoVersion}/mockito-core-${mockitoVersion}.jar&quot;,
),
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Let&#x27;s also update the cache path to work for various operating systems, so it&#x27;s not hardcoded to your system. To get the cache path we need to know which SBT <a href="https://github.com/sbt/sbt/blob/develop/main/src/main/scala/sbt/Keys.scala"><span class="hover:underline text-blue-500 hover:text-blue-700">key</span></a> the value is stored under — it&#x27;s helpful to know that SBT uses Coursier for <a href="https://get-coursier.io/docs/cache#sbt"><span class="hover:underline text-blue-500 hover:text-blue-700">managed dependencies</span></a>, and the key is <span class="p-2 bg-slate-100 rounded-sm font-medium">csrCacheDirectory</span>. In the SBT shell, enter <span class="p-2 bg-slate-100 rounded-sm font-medium">show csrCacheDirectory</span> to see the path value:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">/Users/username/Library/Caches/Coursier/v1
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">You may recognize that path as part of the path you discovered earlier. To get the absolute path from this, you will use <span class="p-2 bg-slate-100 rounded-sm font-medium">csrCacheDirectory.value.getAbsolutePath()</span> in your java options string. Your final <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file should look similar to this with all the changes you&#x27;ve made:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">val mockitoVersion = &quot;5.14.2&quot;

lazy val root = project
  .in(file(&quot;.&quot;))
  .settings(
    // Omitted all project settings for brevity.
    libraryDependencies ++= Seq(
      &quot;org.mockito&quot; % &quot;mockito-core&quot; % mockitoVersion % Test,
    ),
    Test / javaOptions := Seq(
      s&quot;-javaagent:${csrCacheDirectory.value.getAbsolutePath()}/https/repo1.maven.org/maven2/org/
         mockito/mockito-core/${mockitoVersion}/mockito-core-${mockitoVersion}.jar&quot;,
    ),
    Test / fork := true,
  )
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I hope you found this post helpful, and it resolved the dynamic Java agent warning message. If you have any questions, please reach out to me.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Dependencies</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The following dependencies were used in this post.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li><a href="https://scala-lang.org/download/3.5.2.html"><span class="hover:underline text-blue-500 hover:text-blue-700">scala (3.5.2)</span></a></li>
<li><a href="https://central.sonatype.com/artifact/org.mockito/mockito-core/5.14.2"><span class="hover:underline text-blue-500 hover:text-blue-700">mockito (5.14.2)</span></a></li>
<li><a href="https://index.scala-lang.org/sbt/sbt/artifacts/collections/1.9.7"><span class="hover:underline text-blue-500 hover:text-blue-700">sbt (1.9.7)</span></a></li>
<li><a href="https://projects.eclipse.org/projects/adoptium.temurin/"><span class="hover:underline text-blue-500 hover:text-blue-700">java (21.0.5-tem)</span></a></li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attributions</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://leonardo.ai/"><span class="hover:underline text-blue-500 hover:text-blue-700">Leonardo.AI</span></a>, using the prompt &quot;A rogue retro secret agent is spying and looking suspicious while sitting at an old fashioned computer terminal. No faces visible. Danger is lurking in the shadows&quot;</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Intro to Testing]]></title>
        <id>https://danicabrown.com/posts/getting-started-testing</id>
        <link href="https://danicabrown.com/posts/getting-started-testing"/>
        <updated>2024-11-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Presentation slides for getting started with automated testing.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This is presentation I put together to introduce automated testing to development teams that don&#x27;t currently practice writing tests. It is an updated version of a presentation I did years ago at <strong class="font-semibold">#DevKL</strong>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><a href="/pdf/Danica_Brown-Getting_Started_with_Testing.pdf"><span class="hover:underline text-blue-500 hover:text-blue-700">Download PDF</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="HTML Code Coverage Summary" loading="lazy" width="2034" height="1136" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fgetting-started-testing%2Fintro-slide.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attributions</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://leonardo.ai/"><span class="hover:underline text-blue-500 hover:text-blue-700">Leonardo.AI</span></a>, using the prompt &quot;retro futuristic apparatus for testing software, shows abstract code with checkmarks for passed tests and X for failed tests, probes, complex systems&quot;</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Code Coverage]]></title>
        <id>https://danicabrown.com/posts/code-coverage</id>
        <link href="https://danicabrown.com/posts/code-coverage"/>
        <updated>2024-11-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The Scoverage plugin generates code coverage reports for your Scala test suites. A quick rundown of how to use it.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">NOTE: I originally wrote this post to show how to fix incorrect 0% code coverage results. I was wrongly convinced that the <span class="p-2 bg-slate-100 rounded-sm font-medium">coverageFailOnMinimum</span> setting flag was the culprit, but it turned out to be SBT shell issues with Scoverage. Before publishing the post I rewrote it to be about basic Scoverage usage. Enjoy!</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><a href="https://github.com/scoverage/sbt-scoverage"><span class="hover:underline text-blue-500 hover:text-blue-700">Scoverage</span></a> is a code coverage tool in Scala. It generates coverage reports to show you what branches and statements have been executed in your tests. Using the <span class="p-2 bg-slate-100 rounded-sm font-medium">coverage</span> flag will gather data about what code pathways were traversed in your codebase.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I&#x27;d recommend NOT using the <a href="https://www.scala-sbt.org/1.x/docs/Running.html#sbt+shell"><span class="hover:underline text-blue-500 hover:text-blue-700">SBT shell</span></a> when executing the following steps. Assuming you have <a href="https://www.scala-sbt.org/"><span class="hover:underline text-blue-500 hover:text-blue-700">SBT</span></a> and <a href="https://central.sonatype.com/artifact/org.scoverage/sbt-scoverage"><span class="hover:underline text-blue-500 hover:text-blue-700">Scoverage</span></a> installed, from the command line run the following:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">sbt clean coverage test
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Clean</strong> removes any artifacts in the build directory (target), <strong class="font-semibold">Coverage</strong> enables the coverage flag for the compiler, and <strong class="font-semibold">test</strong> runs the test suites. The data collected during the tests isn&#x27;t friendly for human consumption and needs to be formatted in a more palatable way. To format the data, you need to generate a coverage report:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">sbt coverageReport
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">You should see the code coverage report summary in the terminal:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">[info] Waiting for measurement data to sync...
[info] Reading scoverage instrumentation […/target/coverage/scoverage-data/scoverage.coverage]
[info] Reading scoverage measurements...
[info] Generating scoverage reports...
[info] Written Cobertura report […/target/coverage/coverage-report/cobertura.xml]
[info] Written HTML coverage report […/target/coverage/scoverage-report/index.html]
[info] Statement coverage.: 41.36%
[info] Branch coverage....: 50.93%
[info] All done. Coverage was stmt=[41.36%] branch=[50.93%]
[success] Total time: 0 s, completed Nov 2, 2024, 8:51:35 a.m.
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">Awesome it generated a report!</strong> If you have limited, but not zero test coverage in your project, the summary should tell you the statement and branch coverage of your code base. If you have tests and you see 0%, make sure you are <em>NOT</em> running SBT shell. I find the coverage and coverageReports commands generate reliable results when not using SBT shell, eg. execute them direct from the terminal command line.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Minimum Coverage</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If your code base has decent code coverage, consider setting the <span class="p-2 bg-slate-100 rounded-sm font-medium">coverageFailOnMinimum</span> flag along with minimum coverage requirements. This prevents the coverageReport from succeeding if coverage drops below certain thresholds. In your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file, add the following line somewhere at the bottom of the file, you can move it elsewhere in the build file if you want:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">coverageFailOnMinimum := true
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Just adding this line wont do much without minimum values. Below are the default recommended values from <a href="https://github.com/scoverage/sbt-scoverage"><span class="hover:underline text-blue-500 hover:text-blue-700">Scoverage</span></a> documentation. Set the values to whatever works best for your project at this time.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">coverageMinimumStmtTotal := 90
coverageMinimumBranchTotal := 90
coverageMinimumStmtPerPackage := 90
coverageMinimumBranchPerPackage := 85
coverageMinimumStmtPerFile := 85
coverageMinimumBranchPerFile := 80
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Re-run the previous commands used earlier: <span class="p-2 bg-slate-100 rounded-sm font-medium">sbt clean coverage test</span> and <span class="p-2 bg-slate-100 rounded-sm font-medium">sbt coverageReport</span>. If you set really aggressive minimum values that are higher than your current code coverage, you&#x27;ll see an exception in your terminal.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">[info] Coverage reports completed
[error] Coverage is below minimum [50.23% &lt; 90.00%]: Branch:Total
[error] java.lang.RuntimeException: Coverage minimum was not reached
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This is helpful if your team is still adopting test driven development, and they have a bad habit of adding code without tests — you&#x27;ll know who broke the build.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">HTML Report</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If you need to consume the coverage reports in more detail, the HTML report allows you to browse line by line what code is covered. In your <span class="p-2 bg-slate-100 rounded-sm font-medium">build.sbt</span> file, near the previous line you added, add the following:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">coverageOutputHTML := true
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If you&#x27;d like, you can also set the output directory for the coverage reports by adding:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">coverageDataDir := target.value / &quot;coverage&quot;
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This will place all of the coverage information in the directory <span class="p-2 bg-slate-100 rounded-sm font-medium">target/coverage</span>. The default location buries the reports in <span class="p-2 bg-slate-100 rounded-sm font-medium">target/&lt;scala-version&gt;</span>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="my-7 md:my-9 lg:-ml-8 lg:-mr-8 relative"><div class="rounded-lg bg-blue-100 overflow-hidden"><div class="border-l-4 border-blue-400 pt-5 md:pt-8 pb-2 px-7"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><strong class="font-semibold">scoverage-report</strong> directory contains the HTML output of the generated report: <em>target/coverage/scoverage-report</em></p></div></div><div class="bg-blue-400 absolute -top-4 -left-5 w-12 h-12 rounded-full border-4 border-white"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-white w-full h-full" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Re-run the previous commands used earlier: <span class="p-2 bg-slate-100 rounded-sm font-medium">sbt clean coverage test</span> and <span class="p-2 bg-slate-100 rounded-sm font-medium">sbt coverageReport</span>. Open the directory <span class="p-2 bg-slate-100 rounded-sm font-medium">target/coverage</span>, there will be three new directories: coverage-report, scoverage-data and scoverage-report. <span class="p-2 bg-slate-100 rounded-sm font-medium">scoverage-report</span> contains the HTML output of the generated report. The <strong class="font-semibold">index.html</strong> report file shows a coverage breakdown of the files found in the project.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="HTML Code Coverage Summary" loading="lazy" width="2196" height="1320" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-report.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Similar to other code coverage reporting tools in different languages, <a href="https://github.com/scoverage/sbt-scoverage"><span class="hover:underline text-blue-500 hover:text-blue-700">Scoverage</span></a> allows you to click on individual files to see the line by line breakdown of what statements and branches are covered.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="HTML Line Coverage" loading="lazy" width="1206" height="340" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fcode-coverage%2Fhtml-file-report.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">As of <a href="https://scala-lang.org/download/3.3.4.html"><span class="hover:underline text-blue-500 hover:text-blue-700">Scala 3.3.4</span></a> most major language features are supported in code coverage data. For-comprehensions were recently fixed in <a href="https://contributors.scala-lang.org/t/coverage-broken-why-does-nobody-care/6262/46"><span class="hover:underline text-blue-500 hover:text-blue-700">Scala 3.3.3</span></a> so almost everything between Scala 2 and 3 should give similar code coverage results now.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Dependencies</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The following dependencies were used in this post.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li><a href="https://scala-lang.org/download/3.3.4.html"><span class="hover:underline text-blue-500 hover:text-blue-700">scala (3.3.4)</span></a></li>
<li><a href="https://github.com/scoverage/sbt-scoverage"><span class="hover:underline text-blue-500 hover:text-blue-700">sbt-scoverage (2.2.2)</span></a></li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attributions</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://leonardo.ai/"><span class="hover:underline text-blue-500 hover:text-blue-700">Leonardo.AI</span></a>, using the prompt &quot;An ornate, intricately detailed logo in the style of vintage gilt tarot cards, evoking a sense of mysticism and mystique, with a Steampunk twist, featuring a bold, cursive script with subtle, distressed textures, reading &quot;Scoverage&quot; in a flowing, art nouveau-inspired font, surrounded by delicate, filigree-like patterns and ornate, gothic-inspired flourishes, set against a rich, dark background with subtle, warm golden undertones, reminiscent of antique, worn leather, with hints of copper and brass accents, and subtle, eerie flickers of candlelight casting an air of mystery and otherworldliness.&quot;</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Domain IDs in Scala]]></title>
        <id>https://danicabrown.com/posts/scala-domain-ids</id>
        <link href="https://danicabrown.com/posts/scala-domain-ids"/>
        <updated>2024-09-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to implement domain ids in Scala with minimal overhead using opaque types and a companion trait.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">TLDR;
Scroll to the bottom of the page for a code snippet to make domain ids in Scala 3.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Using specific types for domain ids (eg. LocationId, EventId… etc) instead of generic UUIDs (Universally Unique Identifiers), is a great way to communicate intent to yourself, other developers, and leverage the compiler type checker. In Scala 3 there are several ways to implement domain ids and each has it&#x27;s tradeoffs. My preferred approach right now uses <a href="https://docs.scala-lang.org/scala3/reference/other-new-features/opaques.html"><span class="hover:underline text-blue-500 hover:text-blue-700">opaque types</span></a> and traits.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">opaque type LocationId = UUID
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">At compile time, the domain id (LocationId) will be type checked, and at runtime each domain id will actually just be a UUID. It&#x27;s a slick little feature that has no overhead. It&#x27;s more efficient than creating a case class that wraps the UUID. This approach does have a few hiccups that need to be addressed though — initialization and imports.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Companion Object</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To initialize an opaque type you need a companion object with an <strong class="font-semibold">apply</strong> method or constructor methods. For example:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">object LocationId {
  def apply(value: UUID): LocationId = value
  def init: LocationId = UUID.randomUUID()
  def fromBinary(data: Array[Byte]): LocationId = UUID.nameUUIDFromBytes(data)
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The companion object allows us to initialize the id by calling <span class="p-2 bg-slate-100 rounded-sm font-medium">LocationId.init</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">LocationId.fromBinary</span> or <span class="p-2 bg-slate-100 rounded-sm font-medium">LocationId(value)</span> and all methods will produce a valid LocationId. If there are more than a couple domain ids, this initialization code will be duplicated all over. To minimize code, the companion object can be rewritten as a trait and generalized.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">/** A &gt;: UUID is a lower type bound. It means that A is constrained to be a supertype of UUID. */
trait Methods[A &gt;: UUID] {
  def apply(value: UUID): A = value
  def init: A = UUID.randomUUID()
  def fromBinary(data: Array[Byte]): A = UUID.nameUUIDFromBytes(data)
}

opaque type LocationId = UUID
object LocationId extends Methods[LocationId]
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This is a cleaner solution, but there&#x27;s still room for improvement when it comes to imports.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Wrap External Dependencies</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">It&#x27;s seldom a good idea to liter the codebase with external dependencies, even if it is the JDK, such as <span class="p-2 bg-slate-100 rounded-sm font-medium">java.util.UUID</span>. I personally like to confine each external dependency to one file location in the code base, if possible. Wrapping an exernal dependency, whether it&#x27;s a library, resource, or a function, allows you to maximize your control, minimize change impact and encapsulate code.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">It does require more work to write the glue code, but I find it to be a worthwhile tradeoff in the long run. In this case, the wrapping code is just one line.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">opaque type Uuid = UUID
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This may appear like an unnecessary layer of abstraction, but it is a step towards reducing imports and simplifying domain id usage for other developers.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Uuid Companion Trait</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The final Uuid implementation encapsulates the Uuid methods and keeps the <span class="p-2 bg-slate-100 rounded-sm font-medium">java.util.UUID</span> type confined to one file.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">opaque type Uuid = UUID

object Uuid {
  def apply(value: UUID): Uuid = value

  trait Methods[A &gt;: Uuid] {
    def apply(value: Uuid): A = value
    def init: A = Uuid(UUID.randomUUID())
    def fromBinary(data: Array[Byte]): A = Uuid(UUID.nameUUIDFromBytes(data))
  }
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Defining each domain id type is a lot less work now; one import and two statements.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">import mypackage.util.Uuid

opaque type LocationId = Uuid
object LocationId extends Uuid.Methods[LocationId]
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Using the domain type is the same as before.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">val id1: LocationId = LocationId.init
val id2: LocationId = LocationId.fromBinary(bytes)
val id3: LocationId = LocationId(Uuid(value))
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Hopefully this helps make your code more readable, type safe, and reduces your maintainable code.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">TODO</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>I would love to reduce the type definition and the object extension method into one statement. I&#x27;m not sure how to do that yet. Maybe using Scala macros? Not sure.</li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attributions</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://leonardo.ai/"><span class="hover:underline text-blue-500 hover:text-blue-700">Leonardo.AI</span></a>, using the prompt &quot;Abstract painting representing category theory with bright colours and random numbers embedded in the background&quot;.</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Exponential Backoff with Input Reset]]></title>
        <id>https://danicabrown.com/posts/zio-schedule</id>
        <link href="https://danicabrown.com/posts/zio-schedule"/>
        <updated>2024-08-24T13:35:00.000Z</updated>
        <summary type="html"><![CDATA[Scala 3 extension method for resetting ZIO Schedule exponential backoff based on input data.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><em>Disclaimer: I&#x27;m new to ZIO Schedule, and I have a sneaking suspicion that I missed something in the documentation.</em></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Summary</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Using <a href="https://zio.dev/"><span class="hover:underline text-blue-500 hover:text-blue-700">ZIO</span></a> <a href="https://zio.dev/reference/schedule/"><span class="hover:underline text-blue-500 hover:text-blue-700">Schedule</span></a>, I wrote a Scala 3 extension method to reset exponential backoff when an input predicate is true: <span class="p-2 bg-slate-100 rounded-sm font-medium">resetWhenInput(f: A =&gt; Boolean)</span>. </p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Schedule</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I am working on a project that requires multiple processes to continually chew through relational data, forever. The processes need to fire fast when there is data, and use an exponential backoff when there is no data.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Given the codebase already uses the <a href="https://zio.dev/"><span class="hover:underline text-blue-500 hover:text-blue-700">ZIO</span></a> library, I started exploring the documentation looking for recommend ways of working with long running processes. I discovered that <a href="https://zio.dev/"><span class="hover:underline text-blue-500 hover:text-blue-700">ZIO</span></a> has the ability to elegantly <a href="https://zio.dev/reference/schedule/"><span class="hover:underline text-blue-500 hover:text-blue-700">Schedule</span></a> recurring effects, and this appeared to be the best path forward for trying to tackle this problem.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">My first attempt at creating a naive forever looping process, looked like this:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">val schedule = Schedule.forever

val loop = for {
  _ &lt;- printLine(&quot;Hello World&quot;)
  quantityProcessed &lt;- ZIO.succeed(10)
} yield quantityProcessed

loop.repeat(schedule)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The code when executed produced a never ending stream of <em>Hello World</em>. Certainly a step in the right direction, but it needed to be refined.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The schedule class contains many pre-defined schedules and combinators, such as <span class="p-2 bg-slate-100 rounded-sm font-medium">recurs</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">spaced</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">exponential</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">zip</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">whileInput</span>, <span class="p-2 bg-slate-100 rounded-sm font-medium">andThen</span>. Mixing these methods together produces a wide range of scheduling behaviours. My second attempt was almost exactly what I needed.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">// While the input (data size) is greater than zero, run forever.
val hasDataSchedule = Schedule.forever.whileInput[Int](_ &gt; 0)

// While the output duration of the exponential is less than 30s, continue to recur.
val exponentialBackoff = Schedule
  .exponential(50.milliseconds)
  .whileOutput(_ &lt; 30.seconds)

// Space recurrences at 30s.
val maxSpacing = Schedule.spaced(30.seconds)

// Once the exponential backoff stops then the max spacing schedule takes over.
val noDataSchedule = exponentialBackoff andThen maxSpacing

// Intersect the two schedules so the smallest duration will be used, recurs if
// either recurs.
val schedule = hasDataSchedule || noDataSchedule
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7"><em>Note: the above code excludes the for-comprehension and loop code.</em></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If you run the above code, at first glance it produces the desired scheduling behaviour, but there&#x27;s a subtle issue with the exponential backoff - it doesn&#x27;t reset when it encounters input data. The exponential backoff resumes at the last duration in state. This is the desired default behaviour for exponential backoff, but in the context of a loop, the state should reset when an input predicate is true.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The sample data and comments below illustrate where the exponential backoff should reset and increment.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">  val sampleData = Seq(
    Set(4, 5, 6),   // Data, fire immediately
    Set(7, 8, 9),   // Data, fire immediately
    Set(),          // No data, start exponential backoff
    Set(),          // No data, increment backoff
    Set(),          // No data, increment backoff
    Set(),          // No data, increment backoff
    Set(1, 2, 3),   // Data, reset backoff, fire immediately
    Set(2, 3, 7),   // Data, fire immediately
    Set(8, 2, 1),   // Data, fire immediately
    Set(),          // No data, start exponential backoff
    Set(),          // No data, increment backoff
  )
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The <a href="https://zio.dev/reference/schedule/"><span class="hover:underline text-blue-500 hover:text-blue-700">Schedule</span></a> methods that would reset the exponential backoff on input data eluded me. The methods that jumped out as potential candidates for solving this problem were <span class="p-2 bg-slate-100 rounded-sm font-medium">resetWhen(f: Out =&gt; Boolean)</span> and <span class="p-2 bg-slate-100 rounded-sm font-medium">resetAfter(duration: Duration)</span>. Using the output, or an arbitrary duration to determine when to reset the backoff wastes unnecessary time.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">There is probably a way to pass the input data through using built in methods, but it wasn&#x27;t clear to me how to do that. A reset that is based on the input data would be perfect — unfortunately I couldn&#x27;t find one. </p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Extension Methods</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I felt the need to write a function that would reset the exponential backoff when input data was present. Scala has an amazing feature called <strong class="font-semibold">extension methods</strong>, formerly known as implicits. With <a href="https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html"><span class="hover:underline text-blue-500 hover:text-blue-700">extension methods</span></a> any type already defined can have new methods added to it; this allows a method like <em>resetWhenInput</em> to be added to the Schedule type:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">extension (currentSchedule: Schedule):
  def resetWhenInput(predicate): Schedule = ???
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">When extension methods are imported into a file they can be called using the dot operator on the instance of the type extended, for example: <span class="p-2 bg-slate-100 rounded-sm font-medium">noDataSchedule.resetWhenInput()</span>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The input should be a predicate function that takes an argument and returns a boolean, <span class="p-2 bg-slate-100 rounded-sm font-medium">f(A) =&gt; Boolean</span>. The input should also accept anything that is a subtype, conforms-to, or extends that type, <span class="p-2 bg-slate-100 rounded-sm font-medium">[B &lt;: A]</span>. The new method signature looks like this <span class="p-2 bg-slate-100 rounded-sm font-medium">resetWhenInput[B &lt;: A](f: B =&gt; Boolean)</span>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="my-7 md:my-9 lg:-ml-8 lg:-mr-8 relative"><div class="rounded-lg bg-blue-100 overflow-hidden"><div class="border-l-4 border-blue-400 pt-5 md:pt-8 pb-2 px-7"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">A <strong class="font-semibold">Schedule[Env, In, Out]</strong> defines a recurring schedule, which consumes values of type <strong class="font-semibold">In</strong>, and which returns values of type <strong class="font-semibold">Out</strong>.</p></div></div><div class="bg-blue-400 absolute -top-4 -left-5 w-12 h-12 rounded-full border-4 border-white"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-white w-full h-full" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The extension method must also incorporate the generic Schedule types. Those generic Schedule types are <span class="p-2 bg-slate-100 rounded-sm font-medium">[Env, In, Out]</span>. Merging the predicate types with the Schedule generic types produces the following method signature:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">extension [Env, In, Out](self: Schedule[Env, In, Out]):
  def resetWhenInput[B &lt;: In](f: B =&gt; Boolean): 
    Schedule.WithState[self.State, Env, B, Out] = ???
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Reset when Input</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The final stretch requires digging into the inner workings of Schedule. There is a State type, initial state, and a step function. The step function is the most important part for determining whether to continue (step) or not. Essentially if the predicate is true, then use the initial state (&quot;reset&quot;), otherwise use the current state.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">extension [Env, In, Out](self: Schedule[Env, In, Out])

  def resetWhenInput[In1 &lt;: In](f: In1 =&gt; Boolean) =
    new Schedule[Env, In1, Out] {
      override type State = self.State
      override final val initial: State = self.initial

      override final def step(now: OffsetDateTime, in: In1, state: State)(
        implicit trace: Trace
      ): ZIO[Env, Nothing, (State, Out, Decision)] = {
        if (f(in)) then self.step(now, in, self.initial)
        else self.step(now, in, state)
      }
    }
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To verify this method works as expected:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">// TODO: TESTS
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Wrap Up</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Adding the new <span class="p-2 bg-slate-100 rounded-sm font-medium">resetWhenInput</span> method to the exponential backoff produces the desired scheduling outcome. For convenience, I wrapped the schedule in a function for quick usage and easy testing.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">def foreverWithBackoff(
  base: Duration = 50.milliseconds,
  max: Duration = 30.seconds,
) = {
  val hasDataSchedule = Schedule.forever.whileInput[Int](_ &gt; 0)
  val exponentialBackoff = Schedule.exponential(base).whileOutput(_ &lt; max)
  val noDataSchedule = (exponentialBackoff andThen Schedule.spaced(max))
    .resetWhenInput[Int](_ &gt; 0)

  hasDataSchedule || noDataSchedule
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Testing functions that deal with the passage of time can be a slow and painful process, but ZIO only describes the effects, so the passage of time can be adjusted. For example, to adjust the passage of time by 2 minutes in tests, use <span class="p-2 bg-slate-100 rounded-sm font-medium">TestClock.adjust(2.minutes)</span>. This will give instant results, but the code will behave as if 2 minutes has passed. The clock adjustment is really helpful when testing long running processes.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In the sample data below, the first repeat doesn&#x27;t take place until after the loop function has been executed once. Also important to know, the ZIO exponential function is defined as <span class="p-2 bg-slate-100 rounded-sm font-medium">base * factor ^ n</span>. With a base of 50ms, and a default factor of 2, the first no-data repeat will be 100ms (50 * 2 ^ 1 == 100) or 0.1s.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">object ScheduleSpec extends ZIOSpecDefault {
  def spec = suiteAll(&quot;Schedule&quot;) {

    val sampleData: Array[Set[Int]] = Array(
                    // Next Repeat    Elapsed Time
                    // ===========================
      Set(4, 5, 6), // 0.0s           0.0s
      Set(7, 8, 9), // 0.0s           0.0s &lt;- first repeat executed
      Set(),        // 0.1s           0.0s
      Set(),        // 0.2s           0.1s
      Set(),        // 0.4s           0.3s
      Set(),        // 0.8s           0.7s
      Set(),        // 1.6s           1.5s
      Set(),        // 3.2s           3.1s
      Set(),        // 6.4s           6.3s
      Set(),        // 12.8s          12.7s
      Set(),        // 25.6s          25.5s
      Set(),        // 30.0s          51.1s
      Set(),        // 30.0s          81.1s
      Set(1, 2, 3), // 0.0s           111.1s
      Set(),        // 0.1s           111.1s
      Set(),        // 0.2s           111.2s
      Set(),        // 0.4s           111.4s
      Set(),        // 0.8s           111.8s
      Set(),        // 1.6s           112.6s
      Set(),        // 3.2s           114.2s
      Set(),        // 6.4s           117.4s
      Set(),        // 12.8s          123.8s
    )

    // Increment the index but respect the length of sample data.
    val incrementIndex = (currentIndex: Int) =&gt; {
      val newIndex = currentIndex + 1
      if newIndex &gt; sampleData.length - 1 then 0
      else newIndex
    }

    /**
      * This is an impure function, it is mutating indexRef. This is only
      * to make the tests more readable, don&#x27;t do this.
      */
    val loop = (indexRef: Ref[Int]) =&gt; for {
      index &lt;- indexRef.get
      time  &lt;- currentTime(TimeUnit.MILLISECONDS)
      data   = sampleData(index)
      _     &lt;- printLine(s&quot;$index: $data, $time&quot;)
      _     &lt;- indexRef.update(incrementIndex)
    } yield data.size
    
    test(&quot;Exponential backoff&quot;) {
      for {
        indexRef  &lt;- Ref.make(0)
        fiber     &lt;- loop(indexRef).repeat(foreverWithBackoff()).fork
        _         &lt;- TestClock.adjust(13.seconds)
        index     &lt;- indexRef.get
      } yield assertTrue(index == 10)
    }

    test(&quot;Exponential backoff with input data reset&quot;) {
      for {
        indexRef &lt;- Ref.make(0)
        fiber    &lt;- loop(indexRef).repeat(foreverWithBackoff()).fork
        _        &lt;- TestClock.adjust(115.seconds)
        index    &lt;- indexRef.get
      } yield assertTrue(index == 20)
    }
  }
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">… and Voila! Green tests across the board. Writing this function didn&#x27;t take very long, thanks to the great ZIO documentation, both on the web and in the code. In fact this blog post took 1,000,000 times longer to write than the function.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I hope some part of this code journey was helpful for you.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>

<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">TODO</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>reach out to the ZIO library maintainers and ask if there is an alternate way of solving <span class="p-2 bg-slate-100 rounded-sm font-medium">resetWhenInput</span> with just the built-in Schedule methods.</li>
<li>If yes, update post</li>
<li>If not, ask if this would be a welcome contribution to the library</li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attribution</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://www.midjourney.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">MidJourney</span></a>, using the prompt &quot;abstract painting of charts depicting exponential growth with a plateau and drop&quot;.</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[CNC Journey]]></title>
        <id>https://danicabrown.com/posts/hobby-cnc-2024</id>
        <link href="https://danicabrown.com/posts/hobby-cnc-2024"/>
        <updated>2024-08-16T06:34:00.000Z</updated>
        <summary type="html"><![CDATA[My journey setting up, milling aluminum, and upgrading components on my hobby desktop CNC machine from 2021 to 2024. Learnings, pitfalls and ideas.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I&#x27;m in the process of upgrading different parts of my hobby CNC setup, and before I alter the system, I wanted to write about the various components and summarize what worked and what didn&#x27;t on my journey up until this point in time.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Machine inside enclosure with lights and water proof side panels" loading="lazy" width="3000" height="2039" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fenclosure-machine.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Machine</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In the Spring of 2021, I had an idea for a product that I wanted to make. After a couple shocking cost estimates and quotes from manufacturers to produce the idea, I decided to try and make it myself. One of the requirements to bring the idea to life was metal molds. I settled on aluminum because it met the heat specifications needed for baking the product and it seemed to be the easiest metal to work with on a budget. I decided that a small CNC (computer numerical control) milling machine would probably be the best option for making the aluminum molds.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Box of CNC machine" loading="lazy" width="3264" height="2448" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fnew-in-box.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The budget I had in mind for a CNC was roughly $1000 CAD (Canadian Dollars). I compared various CNC machines, and in the end settled on the <a href="https://www.sainsmart.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">SainSmart Genmitsu ProVerXL 4030</span></a>. It&#x27;s a desktop CNC that advertised it could mill/engrave aluminum. The final cost was a bit more than $1200 with shipping. The machine looked robust, had a decent work area, several upgrade options, there was a lovely community of people online, SainSmart had a good reputation, the company website was informative, and it could mill aluminum. It checked all of the boxes for me.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Newly Assembled Machine" loading="lazy" width="3264" height="2448" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fassembled.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">A lot to Learn</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I was incredibly naive of what I was getting myself into; the amount of time, effort and learning involved to operate a CNC machine was a lot more than I anticipated. I knew the learning curve would be steeper than 3D printing, but I vastly underestimated how much I would need to learn in order to mill parts.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="CAD, CAM and toolpaths" loading="lazy" width="2238" height="1282" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcad-cam.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Many factors are required to create a successful part, such as: the rigidity of the machine, the spindle speed, holding the material, fixtures, the size of the bit, the quality of the bits, the type of stock, accuracy, datums, what certain sounds mean, zeroing, work coordinates, operating temperatures, feeds, 3D CAD modeling, and a multitude of things related to the CAM software that create the actual program for the CNC to follow.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><div class="aspect-w-2 aspect-h-1"><img alt="First time making very rough mold halves" loading="lazy" decoding="async" data-nimg="fill" class="object-cover opacity-0 duration-[2000ms]" style="position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frough-mold.png&amp;w=3840&amp;q=75"/></div></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Hands-on</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I consumed as much information as I could — I read forums, I read books, I did tutorials, and I even watched videos of professionals and amateurs milling parts in various materials. The biggest leaps and bounds came from hands-on trial and error experience coupled with the knowledge I was soaking up from various sources. Those a-ha moments when I experienced the situation or scenario I read about, or a concept clicked in my head, those were pivotal learning moments.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I often found myself watching videos of how to do something on a machine that was 100x more expensive than mine, and it seemed easy with all the fancy features that machine had. When I tried to apply it to my own setup, things didn&#x27;t map over. Not all of the take-aways were applicable, but they all contributed to my understanding in small ways. Many topics depend on the machine and it&#x27;s capabilities, as well as the materials and skill you have with all of the above.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Experiments</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">While learning I lost track of my goal of milling aluminum, and for extended periods of time I went on unrelated tangents experimenting with various add-ons, such as a drag knife and laser cutter. It satisfied my curiosity at the time, but they were minor distractions.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Drag knife cutting leather on a vacuum box" loading="lazy" width="3000" height="2154" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdrag-knife-leather.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Learning was chaotic and messy. I found myself throwing various materials into the machine to see what I could make, and also experimenting with how the feeds and speeds needed to be changed for different materials. I played around with milling wood and engraved a cutting board for two friends getting married. I enjoyed milling wood, but hated the dust and mess, it was awful.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Cutting board underside engraving" loading="lazy" width="3000" height="1407" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-board.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Enclosure</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Milling wood produced a lot of dust, and milling aluminum left tiny little flakes around my office. I&#x27;d occasionally find a shiny metal chip in my living room that had hitched a ride on the bottom of my foot. I purchased a dust boot to suck up any debris. It worked well to collect lightweight cut materials.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Dust boot on small spindle" loading="lazy" width="3000" height="2129" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdust-boot.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The shop vac was actually louder than the CNC machine. Running the vacuum for extended periods of time wasn&#x27;t ideal and I soon learned that shop vacs are not designed for that use-case; I blew the motor on the shop vac and the smell of burnt materials filled my office.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I decided to ditch the dust boot and build an enclosure to keep the metal chips and any other materials safely contained. Nothing fancy, just a couple MDF panels glued and nailed together.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Wood enclosure for CNC machine" loading="lazy" width="3000" height="2582" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fwood-enclosure.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I added doors on the front and it worked really well for containing the mess inside the enclosure. After operations were finished, I would easily vacuum up the debris. I mounted a little web cam inside so I could see cutting operations while the doors were closed. Switching back and forth between the GCode being executed to the web cam footage was a blast. I got such a kick out of watching the machine making an absolute mess while bringing my ideas to life.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Web cam viewer of cnc in action" loading="lazy" width="1170" height="1224" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcam-view.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Spindle</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">As I refocused on my goal, I spent more time milling aluminum and exploring the feed rates and speeds that could work well for my machine. I struggled to produce parts at the rate and quality I wanted. The results were far below what I was hoping for. In fairness it wasn&#x27;t all on the machine, as a big factor was the operator (me) and my understanding/skill with the tools I was using — as you can see in the photo below, that bit could be shortened significantly to reduce chatter. At the time, I didn&#x27;t understand that, and the results I was getting were not what I had dreamed of. It was so slow and the goal of rapidly making parts felt so far away.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Poor quality with lots of bit chatter" loading="lazy" width="1169" height="1059" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchatter-poor-quality.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">It wasn&#x27;t uncommon for me to leave the machine running for 24 hours. Patiently waiting for tiny parts to be finished. Sometimes a mistake would happen half way through, and starting over was soul crushing. I wanted to push the feed rate faster and I needed to increase the cut quality, but I couldn&#x27;t figure out how to do it with the current setup.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Stop job in Candle software" loading="lazy" width="3000" height="1687" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fstop-job.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I did managed to successfully make the first version of two mold halves for the original idea I had, along with some other really basic parts, but it was taking a really long time to do anything. I was curious if a bigger spindle/router would help alleviate some of my pains.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Milling results with 300W spindle" loading="lazy" width="3000" height="1687" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fslow-milling.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The tiny 300W spindle that came with the machine was good to learn the basics and experiment with feeds and speeds, but my growing frustrations with the quality and durations led me to explore alternate options. One of the included options for the machine was a larger spindle holder, so I could add a more powerful spindle to the CNC.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Dewalt DWP611 Router" loading="lazy" width="1541" height="1009" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fdewalt-router.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I decided to upgrade the stock spindle to a 840W DeWalt DWP611 router with a 6.4mm (1/4&quot;) chuck. It was in my price point, and it was something I had seen others use in their setups online. At the time, had I known there was a difference between a router and a spindle, I would have gone a directly to a VFD spindle for rpm control, torque, reduced noise, and cooling.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">More power</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Very quickly it became clear that the extra cutting power was going to be a big win. The new router allowed me to vastly increase the feeds, speeds, and the quality of each part. Jobs that previously took 24 hours milling aluminum, now took 24 minutes. I was thrilled to see larger chips getting cut instead of little specs of aluminum.</p></div></div>
<div class=""><div class="mx-auto"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative"><img alt="New router quickly milling aluminum" loading="lazy" width="3000" height="1368" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frouter-chips.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">One major downside of the router, I soon encountered, was I had to manually turn it on before I started a job; I&#x27;d reach into the enclosure, flick the power switch on the router, the router would spin up to high speeds, I&#x27;d close the doors, and then press start on the computer to run the job. This also meant that if something went wrong during a job, the router would continue to spin even though the controller had stopped the machine. Not ideal.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Stock material comes loose during operation" loading="lazy" width="3000" height="1688" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Floose-stock-mishap.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Not even a day later after installing the new router, I setup the machine to mill a new job, and I wasn&#x27;t paying attention to my cut paths. My clamps were directly in the cutting path. The machine demolished one clamp and significantly chewed into the part before I realized something was wrong. Hitting the emergency stop button on the controller prevented the machine from advancing through the remaining clamp, but the router was still on and spinning, sitting flush against the clamp it had chewed half-way through. I shut off the router, and immediately started searching for solutions.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Stock material comes loose during operation" loading="lazy" width="3000" height="1798" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fclamp-cut-through.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Relay hack is essential for a router</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I saw that others had hooked up a relay switch from the CNC controller to toggle the router power and that was a reasonably quick fix (see blog post). That allowed the controller to start and stop the router just as it had done before with the stock spindle. Doing a similar modification to the speed control on the router wasn&#x27;t something I wanted to do, nor spend time looking into.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Relay switches for coolant pump and router" loading="lazy" width="2268" height="2238" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Frelay-box.png&amp;w=3840&amp;q=75"/></div></div></div></div>

<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Noise and Heat</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The router is tremendously loud compared to the stock spindle. I felt self conscious running the machine now and worried my neighbours might hear it. Keep in mind this machine is in my home office in a townhouse. I added 5cm (2&quot;) thick styrofoam to the inside of the enclosure to help reduce the noise. It muffled the sounds, but it also insulated the enclosure and prevented heat from radiating out. It wasn&#x27;t a major issue because I wasn&#x27;t running jobs for extended periods, thanks to the increased cutting power. It did get warm in the enclosure, but not something I felt I needed to address at that time.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Gummy aluminum on milling bit" loading="lazy" width="3000" height="1811" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fgummy-bit.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Another problem related to the extra cutting power surfaced. For the first time ever, I had aluminum melting to the milling bit. Possibly because my router was spinning too fast, not taking aggressive enough cuts, the cut aluminum was not getting cleared out of the cutting path properly, not being cooled correctly — maybe all of the above.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Coolant</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I used compressed air to clear the cutting path, and that helped, but it didn&#x27;t do much for cooling the bit. My router was spinning really fast (16,000RPM +) on it&#x27;s lowest setting. The other downside with the air was the compressor was louder than the CNC machine, and it often would blow the circuit breaker causing the CNC to shut off.</p></div></div>

<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Inspiration</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">What worked really well was occasionally spraying cutting fluid on the part being milled. It almost completely eliminated the aluminum build up on the milling bit. I&#x27;d crack open the enclosure doors while the CNC was running and spray cutting fluid on the part. It was terrifying to stand there with the doors open, putting my hands near this cutting contraption. Just imagine sharp aluminum chips flying towards you, fluid spattering all over, wincing and cowering behind your safety goggles for fear that a bit spinning at 20,000 RPMs might break off and find it&#x27;s way into your body. It was the last thing I wanted to be doing, but it was working.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Excessive cutting fluid on part" loading="lazy" width="2268" height="2268" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcutting-fluid.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The sporadic nature of when I would spray the cutting fluid on the part, wasn&#x27;t consistent enough to produce reliable results. It was too hands on, prone to error, and once again I had metal chips creating a mess on the floor. Every time I opened the enclosure doors I wondered if there was a better and safer way. I had seen videos of professional machines using large quantities of liquid coolant flooding the part, and that&#x27;s what I wanted to try. That looked like something that could run continuously with minimal input from me. I wanted to be hands off with the doors closed. I began brainstorming how I could seal the wooden enclosure for using liquids (link to blog post).</p></div></div>

<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Will it work?</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I wasn&#x27;t sure if this idea would work with my machine, so I tried to keep expenses to a minimum. I lined the interior of the enclosure with 3.2mm (1/8&quot;) plastic sheets - thinner would have also worked. Overlapping them where I could so liquids wouldn&#x27;t get out. I used a heat gun to shape the plastic underneath the cnc machine, forming a primitive a basin that would drain towards the center. I regretted using the plastic as soon as started trying to shape it — it bent, warped and expanded in ways I couldn&#x27;t anticipate.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Enclosure with plastic interior for liquid drainage" loading="lazy" width="3000" height="1827" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fplastic-enclosure-interior.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">If I hadn&#x27;t spent so much money on the sheets of plastic I would have tried using metal instead. I added a PVC drain to the center of the table, and from there the liquids would pass through a filter underneath, and into a reservoir. A fish pump in the reservoir then pushed coolant up through a series of clear flexible tubing to the CNC spray nozzle.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Coolant flowing over milling bit from nozzle" loading="lazy" width="3000" height="1814" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fcoolant-nozzle.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Filters</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The chip filter underneath the CNC was made of random kitchen supplies cobbled together; a strainer and a metal salad bowl with a hole drilled in the bottom. I added an inline water filter from the hardware store, to capture smaller partciulate before it got back to the reservoir. I connected it all together with PVC pipe, plumbers putty, thread tape, and PVC glue. No duct tape, but I think MacGyver would be proud. It&#x27;s the ugliest damn thing I&#x27;ve ever made, and I&#x27;m embarrased to share it, but it was perfect to experiment with. I tested all the connections with small buckets of water, fixed any leaks, and then quickly jumped into full operation with bio-lubricant.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Ugly filters and coolant resevoir" loading="lazy" width="2192" height="2192" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fugly-filters.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">It works, sorta</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">My budget coolant setup worked well enough that I could see and hear differences in the cutting performance. I no longer had aluminum getting gummy on the milling bits, I wasn&#x27;t opening the doors to fiddle with the machine, and the machine didn&#x27;t seem affected by all of the liquids spraying everywhere. I made little splash covers to protect the limit switches and provide a roof over the router and steppers motors. I added a second relay switch to toggle the coolant pump when the router was turned on and off. I added additional metal kitchen mesh to the top of the salad bowl to filter chips before they reached the inline filter.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Chips filtered from liquid coolant return" loading="lazy" width="3000" height="1687" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fchip-filter.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I kept using this setup for the better part of year, despite a number of mishaps. The biggest pain was after 30 or 40 minutes of continuous operation, the inline filter was prone to getting clogged and caused coolant to overflow the sides of the metal bowl. Turns out, I incorrectly placed the inline filter and the low pressure flow wasn&#x27;t ideal. Unfortunately once I realized my mistake I was already designing the new coolant system, so I left it and suffered for a period of time.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Incorrect usage of inline filter" loading="lazy" width="2994" height="2131" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Finline-filter.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I once made the mistake of closing the enclosure doors for a couple weeks and when I came back and opened the enclosure, it was incredibly humid inside, and a lot of the CNC parts looked corroded — screws, the shaft of the router, and other random parts. No airflow inside, coupled with the residual coolant on the walls wasn&#x27;t the ideal environment for certain metals.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="CNC machine inside plastic lined enclosure" loading="lazy" width="3000" height="1633" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Fmachine-plastic-interior.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The coolant setup has introduced a new anxiety to my CNC operations - a fear of overflow. I quickly added a secondary power switch for the coolant pump, so it could be manually shut off during jobs. The constant monitoring of coolant levels, and toggling the coolant pump off and on as needed to maintain levels, is annoying and stressful. I briefly enjoy hands off operations for the first thirty minutes of a job, but after that it is full time baby sitting. There is definitely room for improvement.</p></div></div>
<div class=""><div class="mx-auto"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative"><img alt="" loading="lazy" width="3000" height="1845" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fhobby-cnc-2024%2Ffixture-plate.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h2 class="text-3xl font-bold text-gray-800 leading-9 pt-4 mb-2">Next Steps</h2></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This hacked together hobby setup is less than perfect, but it works for now. I love that I can make metal parts on this inexpensive machine, but I also struggle with it&#x27;s limitations.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>update chip filters — this will allow for longer operating periods, less anxiety, more fun and joy [DONE]</li>
<li>circuit for detecting low and high coolant levels [IN PROGRESS]</li>
<li>add a separate pump and circuit for manually cycling the reservoir liquid [DONE]</li>
<li>move the hose line to the second pump, use a recoil hose and spray nozzle [DONE]</li>
<li>air blast hose for cleaning parts coming out of the machine [IN PROGRESS]</li>
<li>replace the T-tracks with a large fixture plate / add large fixture plate [DONE]</li>
<li>airflow in the enclosure</li>
<li>replace all of the chasis screws [IN PROGRESS]</li>
<li>replace the semi-open plastic reservoir bin with a sealed container [DONE]</li>
<li>update the controller board for coolant control</li>
<li>door alarms</li>
<li>4th axis</li>
<li>move all electronics into a new box</li>
<li>add more structural support to the machine for rigidity</li>
<li>consider using heavy duty linear rails with carriage blocks</li>
<li>add more height to the Z-axis</li>
<li>replace wood enclosure (water damage) with metal, open bottom with generous slopes for drainage and chip collection</li>
<li>slide doors open or up — swing doors drip coolant on the floor or chips fall out</li>
<li>larger workarea</li>
<li>removable side panels for larger pieces, or maintenance</li>
<li>have thick safety windows on the enclosure to see inside</li>
<li>replace router with water cooled VFD spindle — more power and bigger chuck - quick change would be ideal</li>
<li>tool height probe</li>
<li>offset library for tool changes</li>
<li>add an auger for chip evacuation</li>
</ul></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Scala 3 Implicit]]></title>
        <id>https://danicabrown.com/posts/scala3-implicits</id>
        <link href="https://danicabrown.com/posts/scala3-implicits"/>
        <updated>2024-07-04T10:13:00.000Z</updated>
        <summary type="html"><![CDATA[Scala 3 port of Rock the JVM Type Classes Scala 2 example]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This is a Scala 3 port of the <a href="https://www.youtube.com/watch?v=bupBZKJT0EA"><span class="hover:underline text-blue-500 hover:text-blue-700">Scala 2 Type Classes</span></a> example by <a href="https://rockthejvm.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">Rock the JVM</span></a>. For reference of the updated Implicits in Scala 3, read the reference on <a href="https://docs.scala-lang.org/scala3/reference/contextual/givens.html"><span class="hover:underline text-blue-500 hover:text-blue-700">Given Instances</span></a>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Scala 3</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">In Scala 3, <strong class="font-semibold">given</strong> and <strong class="font-semibold">using</strong> replace the original <strong class="font-semibold">implicit object</strong> and <strong class="font-semibold">implicit</strong>, respectively. Define the implicit object with <strong class="font-semibold">given</strong> and specify where it can be used with <strong class="font-semibold">using</strong>.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">trait Summable[T] {
  def sumElements(list: List[T]): T
}

given intSum: Summable[Int] with
  def sumElements(list: List[Int]) = list.sum

// With braces (aka curly brackets)
given stringSum: Summable[String] with {
  def sumElements(list: List[String]) = list.mkString(&quot;&quot;)
}

def processMyList[T](list: List[T])(using summable: Summable[T]): T =
  summable.sumElements(list)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Scala 2</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Define the <strong class="font-semibold">implicit object</strong> and override the trait methods.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">trait Summable[T] {
  def sumElements(list: List[T]): T
}

implicit object IntSummable extends Summable[Int] {
  override def sumElements(list: List[Int]) = list.sum
}

implicit object StringSummable extends Summable[String] {
  override def sumElements(list: List[String]) = list.mkString(&quot;&quot;)
}

def processMyList[T](list: List[T])(implicit summable: Summable[T]): T = {
  summable.sumElements(list)
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Full Scala 3 Example</h3></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">@main def hello(): Unit = {
  val intSum = processMyList(List(1, 2, 3))
  val stringSum = processMyList(List(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;))
  
  println(intSum)
  println(stringSum)
}

trait Summable[T] {
  def sumElements(list: List[T]): T
}

given intSum: Summable[Int] with
  def sumElements(list: List[Int]) = list.sum

given stringSum: Summable[String] with
  def sumElements(list: List[String]) = list.mkString(&quot;&quot;)

def processMyList[T](list: List[T])(using summable: Summable[T]): T =
  summable.sumElements(list)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Output</h3></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-scala" style="background:none">6
ABC
</code></div></div></div></pre>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 Growth]]></title>
        <id>https://danicabrown.com/posts/2024-growth</id>
        <link href="https://danicabrown.com/posts/2024-growth"/>
        <updated>2024-01-01T18:14:00.000Z</updated>
        <summary type="html"><![CDATA[I am trying something new this year and starting the year with a short list of daily and weekly micro commitments; little activities that are easy to do and will improve my life and health in small ways.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Morning Meditation</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Unplug and sit in silence for 1-15 minutes: intention, focus, connect, insight, inspired action. Do after showering.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Happy Hour</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">For 60 minutes, focus on advancing a personal project; develop, design, create. First thing in the morning.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Try-It Tuesday</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Dedicate 1-60 minutes to trying something new, or improving a skill; try a tutorial, reflect, write, repeat. Fusion 360, Photoshop, Scala, CNC, Sewing, Cooking, etc.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Writing Wednesday</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">For 1-60 minutes write. Topic ideas: learned about, proud of, worked on, a challenge, success, failure, book review, scared of, childhood, goals, ideas, etc.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Semana Social</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Connect in-person with loved ones. Go for a walk, enjoy a glass of wine, host games nights, experience an activity, try something new, cook dinner, etc. Get together, face to face.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Read Regularly</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Read at least 1 page or more before bed. Aim for 1 chapter. Balance the fiction and non-fiction. Stretch my body while reading.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Fresh Friday</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Cold plunge in the lake, balance in hot yoga, walk up a mountain, work from a coffee shop or a library.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Gratitude Glow</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Every Sunday at lunch, write down as many great or funny things that happened this week. Put it in a jar. Fill the jar.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Stair Squats</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Every trip up the stairs requires six deep squats, six calf lifts, and six step downs — as much weight as possible.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="2024 Micro Commitments" loading="lazy" width="1600" height="3000" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2F2024-growth%2Ftry.png&amp;w=3840&amp;q=75"/></div></div></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Bin Packing]]></title>
        <id>https://danicabrown.com/posts/bin-packer</id>
        <link href="https://danicabrown.com/posts/bin-packer"/>
        <updated>2023-04-21T10:43:00.000Z</updated>
        <summary type="html"><![CDATA[A simple bin packing strategy that minimizes the quantity of bins used, finds the smallest bin for packed item(s), and adds bins until all items are packed.]]></summary>
        <content type="html"><![CDATA[<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">TLDR;
A journey to create a simple bin packing strategy that minimizes the quantity of bins used, finds the smallest bin for packed item(s), and adds bins until all items are packed. You can find the code here: <a href="https://github.com/dbrown428/box-packer-ts"><span class="hover:underline text-blue-500 hover:text-blue-700">box-packer-ts</span></a>.</p></div></div>

<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Requirements</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">To accurately calculate shipping costs for <a href="https://www.brownhockey.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">Brown Hockey</span></a> customers, I needed a bin packing algorithm that could take a list of products, and determine an efficient, human executable, packing solution given a set of fixed box sizes. Preferrably this algorithm would be written in TypeScript or JavaScript, so I could easily drop it into the website code base. Many of the solutions I found on <a href="https://www.npmjs.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">NPM</span></a> were old, defunct, unmaintained, and/or did not have test coverage. The one package that stood out in the JavaScript pile was <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">BinPackingJS had good test coverage, it supported 2D and 3D bin packing, it wasn&#x27;t too old (last updated two years ago), it had a number of maintainers, decent weekly usage, and a few stale pull-requests. It was the best solution I could find in JavaScript and I hoped it would work for my needs.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Close, but not quite</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">At first glance, the package passed all of my simple bin packing tests, but more complex test cases highlighted its short-comings. The package appeared to lack the ability to minimize the quantity of bins used, it seldom found the smallest bin for an item, and if more bins were needed it didn&#x27;t add them.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">describe(&#x27;Packer&#x27;, () =&gt; {
  test(&#x27;No bins to pack&#x27;, () =&gt; { … })
  test(&#x27;No items to pack&#x27;, () =&gt; { … })
  test(&#x27;Item does not fit in bin&#x27;, () =&gt; { … })
  test(&#x27;Item fits in bin&#x27;, () =&gt; { … })
  test(&#x27;Rotated item fits in bin&#x27;, () =&gt; { … })
  test(&#x27;Multiple items fit in bin&#x27;, () =&gt; { … })
  test(&#x27;Too many items, no bins added&#x27;, () =&gt; { … })
  test(&#x27;Bin ids do NOT need to be unique&#x27;, () =&gt; { … })
  test(&#x27;Item exceeds max weight for bin&#x27;, () =&gt; { … })
  test(&#x27;Many items exceed max weight for box&#x27;, () =&gt; { … })

  test(&#x27;Does not minimize bins used&#x27;, () =&gt; {
    const item1 = { id: &#x27;abc&#x27;, width: 20, height: 30, depth: 10, weight: 1 }
    const item2 = { id: &#x27;def&#x27;, width: 20, height: 20, depth: 10, weight: 1 }

    const bin1 = { id: &#x27;123&#x27;, width: 20, height: 30, depth: 10, maxWeight: 10, volume: 6000 }
    const bin2 = { id: &#x27;456&#x27;, width: 20, height: 20, depth: 10, maxWeight: 10, volume: 4000 }
    const bin3 = { id: &#x27;789&#x27;, width: 20, height: 30, depth: 20, maxWeight: 10, volume: 12000 }
    
    const bins: Bin[] = [bin1, bin2, bin3]
    const items: Item[] = [item1, item2]
    const results = pack(bins, items)

    const expected = {
      manifest: [
        { id: bin2.id, items: [item2.id], remainingVolume: 0 },
        { id: bin1.id, items: [item1.id], remainingVolume: 0 },
        // Ideal solution: { id: bin3.id, items: [item1.id, item2.id] },
      ],
      unfitItemIds: []
    }
    expect(results).toEqual(expected)
  })
})
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">While testing the package, I started to realize <em>I was probably not using the package in the way the original authors had intended</em>. I needed a bin packing solution that minimized bins used, found the smallest bin for the packed item(s), and added bins until all items were packed. As it was, the package wouldn&#x27;t meet those needs.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Leveraging Strengths</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I have been spoiled with the quantity and quality of packages available these days, and instead of initially deciding to work with the package as-is, I opted to continue searching for alternative solutions. Once again, I came up empty handed. I could see that other people were running into similar issues as me, and alternative packages were popping up, but none solved any of my problems, or they were lacking essentials to be trustworthy enough to use in production. <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a> was still the best solution for the language I was working in.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I reluctantly decided to try and modify the behaviour of the package. I encapsulated the package to protect my project from any unexpected package changes. I kept the original unit tests I wrote, added simple value types, and charged forward with hope that I could improve the bin packing results. The first iteration simply added more bins if there wasn&#x27;t enough. It was very crude, but it solved a bin test I had.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">function pack(availableBins: Bin[], items: Item[], unlimitedBins: boolean): PackingResult {
  const itemCount = items.length
  const binCount = unlimitedBins ? (itemCount &gt; 1 ? itemCount : 1) : 1

  const makeBin = (bin: Bin) =&gt; new PackBin(bin.id, bin.width, bin.height, bin.depth, bin.maxWeight)
  const makeItem = (item: Bin) =&gt; new PackItem(item.id, item.width, item.height, item.depth, item.weight)

  const internalBins = availableBins.flatMap(bin =&gt; Array.from({ length: binCount }, () =&gt; makeBin(bin)))
  const internalItems = items.map(makeItem)

  const packer = new Packer()
  internalBins.forEach(bin =&gt; packer.addBin(bin))
  internalItems.forEach(item =&gt; packer.addItem(item))
  packer.pack()

  const unfitItemIds: string[] = packer.unfitItems.map((item: any) =&gt; item.name)
  const manifest: BinManifest[] = internalBins
    .filter(bin =&gt; bin.items.length &gt; 0)
    .map(bin =&gt; ({ id: bin.name, items: bin.items.map((i: any) =&gt; i.name) }))

  return { manifest, unfitItemIds }
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This ugly first attempt gave me the confidence to push forward. It did not thoroughly solve the &quot;add more bins&quot; problem, but it was a stepping stone to solving other problems. The code quickly exploded out of control, as I wrote a lot of unnecessary code, while attempting to wrap my head around this problem. I moved the packing strategies from the <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a> wrapper into their own file to keep things simple. As I wildly explored the solutions my brain was suggesting, I began to slowly understand the strengths and weaknesses of <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a>.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="my-7 md:my-9 lg:-ml-8 lg:-mr-8 relative"><div class="rounded-lg bg-blue-100 overflow-hidden"><div class="border-l-4 border-blue-400 pt-5 md:pt-8 pb-2 px-7"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">BinPackingJS is really good at finding a packing solution for one bin and many items.</p></div></div><div class="bg-blue-400 absolute -top-4 -left-5 w-12 h-12 rounded-full border-4 border-white"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-white w-full h-full" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The package handles rotating and limiting the weight of items for the bin really well. It was good at packing one bin and multiple items, or at least good enough for my needs. Building around the idea of &quot;one bin at a time&quot; felt like the next logical step.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Better fit</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Optimizating bin sizes was important to get the best shipping costs for <a href="https://www.brownhockey.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">Brown Hockey</span></a> customers. The <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a> package would often put items in bins that were excessively large. I began by brainstorming and visualizing the steps I might take to pack a bin (corrugated box) in front of me. Such as:</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ol class="list-outside ml-8 list-decimal text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>Grab a bin</li>
<li>Put item in bin: Is item too big? Is bin too big?</li>
<li>Try another bin size</li>
</ol></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">This exercise coupled with my basic understanding of <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a>&#x27; strengths helped me to focus on iteratively packing one bin. I cobbled together a recursive solution of finding smaller bins for the packed items. My conditional logic was flawed in the smaller bin comparison, but I didn&#x27;t notice it until much later.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">function tryPackingBox(binSizes: Bin[], items: Item[], previousResult?: PackingResult): PackingResult {
  if (items.length === 0) {
    return { manifest: [], unfitItemIds: [] }
  } else if (binSizes.length === 0) {
    return { manifest: [], unfitItemIds: items.map(item =&gt; item.id) }
  } else {
    return packBox(binSizes, items)
  }
}

function packBox(binSizes: Bin[], items: Item[]): PackingResult {
  const currentBin = binSizes[0]
  const smallerBins = binSizes.slice(1)
  const hasSmallerBoxes = smallerBins.length &gt; 0
  const result = pack([currentBin], items)
  return hasSmallerBoxes ? findSmallerBox(smallerBins, items, result) : result
}

function findSmallerBox(binSizes: Bin[], items: Item[], previousResult: PackingResult): PackingResult {
  const alternateResult = tryPackingBox(binSizes, items)
  const smallerBoxIsBetter = alternateResult.manifest.length &gt;= previousResult.manifest.length
    &amp;&amp; alternateResult.unfitItemIds.length &lt;= previousResult.unfitItemIds.length
  return smallerBoxIsBetter ? alternateResult : previousResult
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">Early versions of this code stopped at the first smaller bin found. I quickly realized that in some cases an irregular shaped bin could often fit fewer items than a smaller bin. For example, a long skinny rectangle could be the largest volume but it might only fit certain items. The code was changed to traverse down the entire list of bin sizes.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Irregular box shape and standard box shape" loading="lazy" width="1110" height="666" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-irregular.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Add more bins</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">At this point in the project I wanted to cleanup my original attempt at packing multiple boxes. It was ugly and unnecessary.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">/** The maximum boxes needed equals the quantity of items */
function tryPackingBoxes(binSizes: Bin[], items: Item[]): PackingResult {
  const unfitItemIds = items.map(item =&gt; item.id)
  const initialResult: PackingResult = { manifest: [], unfitItemIds }
  return items.reduce(result =&gt; packNextBox(binSizes, items, result), initialResult)
}

function packNextBox(binSizes: Bin[], items: Item[], previousResult: PackingResult) {
  const finishedPacking = previousResult.unfitItemIds.length === 0

  const packMore = () =&gt; {
    const unpackedItems = getItems(items, previousResult.unfitItemIds)
    const result = tryPackingBox(binSizes, unpackedItems)

    return {
      manifest: [...previousResult.manifest, ...result.manifest],
      unfitItemIds: result.unfitItemIds,
    }
  }

  if (finishedPacking) {
    return previousResult
  } else {
    return packMore()
  }
}

// Helper method… remove in the future
function getItems(items: Item[], itemIds: string[]): Item[] {
  return items.filter(item =&gt; itemIds.includes(item.id))
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I was feeling very positive about my progress, because most unit tests were passing, except for the last one. It was a contrived example, but it was a packing situation I knew would come up, and if I didn&#x27;t solve it now, it would plague me later on.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-text" style="background:none">Minimize Bins Used
  ✓ No items (4 ms)
  ✓ No bins (2 ms)
  ✓ No boxes fit (8 ms)
  ✓ Item fits (2 ms)
  ✓ Smallest box for item (47 ms)
  ✓ Item does not fit in first box (2 ms)
  ✓ Many items per box (3 ms)
  ✓ Largest bins are packed first (2 ms)
  ✓ Irregular large volume box holds fewer items than a smaller volume box (1 ms)
  ✓ Not all items fit (3 ms)
  ✓ Minimize bins used (12 ms)
  ✕ Pack multiple boxes (15 ms)
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">As I dug deeper into the failing test, I realized there was a simpler subset of items and bins in my contrived example that would cause a failing test. It was a subtle packing issue that gave terrible results.</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">const item1 = { id: &#x27;abc&#x27;, width: 20, height: 20, depth: 10, weight: 1 }
const item2 = { id: &#x27;def&#x27;, width: 20, height: 20, depth: 10, weight: 1 }
const item3 = { id: &#x27;hij&#x27;, width: 40, height: 20, depth: 10, weight: 1 }

const bin1 = { id: &#x27;123&#x27;, width: 20, height: 20, depth: 10, maxWeight: 10, volume: 4000 }
const bin2 = { id: &#x27;456&#x27;, width: 40, height: 20, depth: 10, maxWeight: 10, volume: 8000 }

const bins: Bin[] = [bin1, bin2]
const items: Item[] = [item1, item2, item3]
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">There was a lot of head scratching at this point, but I was determined to figure it out. I struggled to understand what was wrong with the code. I mentally stepped through line by line, and everything appeared to be doing what I expected. I went through all the code multiple times, and it wasn&#x27;t until I sat down with a piece of paper to track the output of each packing iteration when I finally saw the glaring issue.</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><div class="mb-4 md:mb-8"><div class="overflow-hidden relative rounded-lg"><img alt="Paper notes" loading="lazy" width="1280" height="720" decoding="async" data-nimg="1" class="object-cover opacity-0 duration-[2000ms]" style="color:transparent" sizes="(max-width: 1024px) 100vw, 100vw" srcSet="/_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fimg%2Fposts%2Fbin-packer%2Fbin-packer-tracking.png&amp;w=3840&amp;q=75"/></div></div></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">The smallest box comparison wasn&#x27;t taking into account the volume used, or more specifically how much volume was remaining in the current solution compared to the previous solution. It was only comparing the current and previous bin manifest and unfit item counts. I added a property for bin volume, and modified the comparison for smallest bin:</p></div></div>
<pre><div class="px-6"><div class="mx-auto md:max-w-screen-lg"><div class="mb-4 md:mb-8"><code class="language-typescript" style="background:none">function findSmallerBox(binSizes: Bin[], items: Item[], previousResult: PackingResult): PackingResult {
  const alternateResult = tryPackingBox(binSizes, items)
  const alternateFitCount = alternateResult.manifest.length
  const alternateUnfitCount = alternateResult.unfitItemIds.length
  const alternateVolume = remainingVolume(alternateResult)

  const previousFitCount = previousResult.manifest.length
  const previousUnfitCount = previousResult.unfitItemIds.length
  const previousVolume = remainingVolume(previousResult)

  /** The alternate box reduces volume, with the same or better manifest counts */
  const reducedVolume = alternateFitCount &gt;= previousFitCount &amp;&amp;
    alternateUnfitCount &lt;= previousUnfitCount &amp;&amp;
    alternateVolume &lt; previousVolume
  
  /** The alternate box has a better manifest count, with the same or reduced volume */
  const improvedManifest = alternateFitCount &gt; previousFitCount &amp;&amp;
    alternateUnfitCount &lt; previousUnfitCount &amp;&amp;
    alternateVolume &lt;= previousVolume

  const smallerBoxIsBetter = reducedVolume || improvedManifest
  
  if (smallerBoxIsBetter) {
    return alternateResult
  } else {
    return previousResult
  }
}

export function remainingVolume(result: PackingResult): number {
  return result.manifest.reduce((result, bin) =&gt; result + bin.remainingVolume, 0)
}
</code></div></div></div></pre>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">… and Voila! Green tests across the board. It was a moment of rejoice and delight for me. I had taken multiple stabs at the bin optimization problem sporadically over a few days and I was getting frustrated with the lack of progress in the last leg of the journey. It was nice to sink my teeth into this problem and see it through to completion. I&#x27;m very excited to integrate it into the shipping cost estimator for <a href="https://www.brownhockey.com/"><span class="hover:underline text-blue-500 hover:text-blue-700">Brown Hockey</span></a></p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><hr class="mb-8 md:mb-16 mt-8 md:mt-16"/></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">TODO</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><p class="text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">I have one more bin packing strategy I would like to explore, and then I&#x27;ll make this solution available to others:</p></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li><s>Publish a public repo of the code</s> [<a href="https://github.com/dbrown428/box-packer-ts"><span class="hover:underline text-blue-500 hover:text-blue-700">DONE</span></a>]</li>
<li>Publish my first public package on NPM</li>
<li>Connect with the <a href="https://www.npmjs.com/package/binpackingjs"><span class="hover:underline text-blue-500 hover:text-blue-700">BinPackingJS</span></a> crew and chat about contributing, eg. add type definitions, add packing strategies, update documentation</li>
</ul></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><h3 class="text-xl md:text-2xl font-bold text-gray-800 leading-9 mb-1 md:mb-2">Attributions</h3></div></div>
<div class="px-6"><div class="mx-auto md:max-w-screen-md"><ul class="list-outside ml-8 list-disc text-lg md:text-xl font-light text-primary leading-7 md:leading-9 mb-5 md:mb-7">
<li>The main post image was generated with <a href="https://leonardo.ai/"><span class="hover:underline text-blue-500 hover:text-blue-700">Leonardo.AI</span></a>, using the prompt &quot;1930s old fashioned film grain, stacks of corrugated boxes of various sizes with focus on single corrugated box sitting on the floor not in a stack, shipping container interior&quot;</li>
</ul></div></div>
]]></content>
    </entry>
</feed>