Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Wednesday, October 2, 2019

Generate an STL 3D model with a surface described by an equation

I recently needed to generate a 3D model for printing based on an equation and couldn't find software to do the job.  In the end the easiest thing to do was to write a quick Python script called "stl-surface.py" to do the job.

At first the task seemed daunting, but generating the STL file is rather easy with the use of the numpy-stl library. The equation for the surface is calculated over a structured square/rectangular grid and each little cell of the grid is split in two to form triangles. You use many many of these small triangular faces to construct the model. All you have to do is to generate the coordinates for the 3 vertices that make up each triangle and numpy-stl does the rest. The bottom and sides are flat which makes things rather easy, and the top is defined by the equation.

When constructing the model it's important to list the coordinates of each face in a counter clockwise direction when looking at the model from the outside. This allows numpy-stl to later calculate things like volume of the model and centre of gravity and also test if the surface is closed (although they use a slightly buggy way to test this).

I've shown a few examples of the generated models below.

10 - 2*(1 - math.cos(2 * x * math.pi / 20)) * (1 - math.cos(2 * y * math.pi / 20))
Dimpled 3D model
10 + 2 * math.cos(math.sqrt((x - 50)**2 + (y - 50)**2)/2)
Rippled 3D model

Here you can see how the triangles are assembled to construct the model.
The triangular faces that make up the model

The triangles that form the edges of the model
Edge of the model

From the top you can see the grid points where the surface function is evaluated.
Top of the model

I hope this code can help someone else. I may not be the easiest thing to use and if you need help getting started get in touch.
Get the code!


Saturday, September 14, 2019

Reducing Evaporation Rates With Rippled Surfaces

A theory that I want to test out is that adding a ripple to a surface makes it easier to clean in certain situations.  I work in retail, and sometimes the cleaning of non food safety related issues are delayed due to more important tasks.  In particular I'm talking about drips.  You might see these on metal trays under bottles of milk in the fridge, or on plastic sheets under the shelves in the meat department.  In both cases drips are meant to be caught on a surface that is easy to remove and clean.  The problem is that if you leave a spill too long it dries and become hard to remove and requires vigorous cleaning which may damage the surface and waste time.

To avoid this, you want to slow the rate of evaporation.  This can be done by decreasing the surface area by increasing the depth.  A rippled surface is perfect for this.  You essentially make little cups to hold the spill.  Another requirement that would be nice to include is no tight internal corners.  Anything that is designed to be cleaned shouldn't contain a concave surface that you can't get a finger into.  Anyone that knows me well understands that I think in equations, and the one below matches our criteria perfectly.

$$$z=10 - 8 \left(\dfrac{1-\cos(2\pi x / 20)}{2}\right)\left(\dfrac{1-\cos(2\pi y / 20)}{2}\right)$$$

Let's talk about what this equation means.  The two large sections in the brackets are periodic terms that create ripples in the x and y directions.  The 20 means that this ripple will repeat every 20mm.  By subtracting the cos term from one and then dividing that result by 2, the term in the bracket will range from 0 to 1.  Multiplying the two brackets together will also give a result between 0 and 1.  By multiplying this by 8 we now have a function that ranges from 0 to 8.  The 10 describes the maximum of the equation.  By subtracting the rest of the equation from 10 we now have a surface that ranges from 2 to 10 above zero.  The important things to remember are, 20 specifies how wide the ripples are, and 8 describes how deep they are.

Sometimes though it helps to have the real thing in your hand to test so I created a 3d model to send away for 3d printing.  There doesn't seem to be anything out there to create a 3d model from an equation so I had to write some software to do that.  I'll post that when I tidy it up and comment it properly.

I'm not made of money so the model is only 100mm x 100mm x 10mm (a volume of 100mL) with 20mm wide ripples that are 8mm deep.  By a stroke of luck, I happened to write the software is a way that easily calculates the volume of the model.  In this case the model is 80 mL.  As the bounding box of the model is 100 mL this means the volume of the 25 little cups is 20mL.  Each one holding 0.8 mL.

3D model of a rippled surface
Rippled Drip Tray

I don't have the print yet, but it has been done and photos sent to me.  In theory this surface should hold 20 mL of liquid and covers an area of 100 square centimeters, so as a test I poured 20 mL of water on a flat surface and it spread to cover 180 square centimeters.  So already the ripple pattern has reduced the surface area by 45%

3D printed model of a rippled surface
3D Printed Drip Tray Top

That may not sound like much, but the ripples can be made deeper.  If they were 3x times deeper (24mm) the surface would hold 60mL.  Once they get too deep though, you would need to make the ripples wider to make cleaning easier.  Changing the width of the ripples doesn't effect the volume that the surface would hold though.

3D printed model of a rippled surface
3D Printed Drip Tray Top

In this demonstration I've shown the surface as a solid block with depressions.  In reality you'd use something like a polypropylene sheet moulded to this shape.  It would give an object shaped similar to an egg carton.

3D printed model of a flat surface
3D Printed Drip Tray Base

Anyway, this is just a thought that I wanted to explore.  Maybe it'll work out, maybe it won't.  Either way the process was enjoyable.

Friday, June 16, 2017

Merge A Data Set With A Template File To Generate Output Files

For something I'm working on I need to be able to create a large number of files by filling in fields in a template file with entries from a data set. You'd think that would be easy with Linux but I couldn't find a way to do it. (This will be where people tell me a thousand different ways to do it) I didn't think what I wanted was complicated so I wrote SimpleMerge to take care of it. It is a basic Python script that takes data from a tab delimited data file and fills in data fields in a template file.

The first row of the data file are the field identifiers to find and replace and the other rows are just data. This file can be easily generated from a spreadsheet program. The template file contains the structure of the file you intend to create, just with field identifiers in the place of real data.

I haven't done extensive testing on the program but it seems to work fine.  It handles UTF-8 file encoding and maintains the line endings of the template file for both UNIX and Windows systems. The following command generates the two files File1.txt and File2.txt as seen in the block diagram below.

SimpleMerge.py template.txt Data.txt


Block Diagram
Simple Merge Block Diagram
You can use this method on any file really, even SVG files.  Hint hint wink wink.  You can go from this template file.....

Periodic Table Symbols
SVG Template

to this in a matter of minutes. Just by replacing colour and three text fields.

Periodic Table Symbols
Generated Images

I make no guarantee as to how well this works. So my advice is to back things up before using it. Have fun.
Get The Code!
.

Friday, May 19, 2017

Testing All The PINs On A Lock Box With A Forgotten Code

A long time ago I did a blog post on PIN coded lock boxes that you put on your house to hold a spare set of keys. Their main function is to give easy access for emergency services in case they need keys to get in if an elderly relative has a fall and can't get up. The point of my post was to demonstrate that although they have 10 buttons, look secure, and you can choose how many digits are in the PIN, they only really have 1024 PIN combinations. This is because the numbers can only be used once in each PIN.

Lock
PIN coded key lock box
The math is explained in my initial blog post but the results are below. I opined it would take about 4 hours to try all the combinations, and in this case it would require pressing the open button 1024 times, the clear button 1024 times, and pressing digits (0*1 + 1*10 + 2*45 + 3*120 + 4*210 + 5*252 + 6*210 + 7*120 + 8*45 + 9*10 + 10*1) = 5120 times.  In total there are 7168 button presses. That works out at about one button press every 2 seconds.
Table
Number of PINs vs PIN length
At some point in the 5 years since I first wrote about these locks (the time flies doesn't it) it occurred to me that I was being inefficient. Testing if the box opens doesn't clear the current code. So I  can test multiple PINs at once by just chaining them together. It's similar to the De Bruijn sequence, a mathematical tool that can be used to brute force another type of PIN. In this case however you have to reset the lock after a few buttons are pressed. To illustrate the process I'm trying to explain, imagine that the clear button has been pressed and I then enter the numbers 0 through 9, testing if the lock will open in between numbers. I've actually just tested the codes (Null) (0) (01) (012) (0123) (01234) (012345) (0123456) (01234567) (012345678) (0123456789). I've tested 11 codes with one press of the clear button, 11 presses of the open button, and just 10 digit presses. The whole process won't be this efficient but it'll be better than nothing.

But where do we start? In a perfect world, you may see by looking at the chart below and table above, that the best we can do is 1 sequence testing PINs of length 0-10,  followed by 9 sequences testing PINs of length 1-9, 35 sequences testing PINs of length 2-8, 75 sequences testing PINs of length 3-7, 90 sequences testing PINs of length 4-6, and 42 sequences testing PINs of length 5.

This will still result in 1024 presses of the test button, only 252 presses of the clear button, and (1*10)+(9*9)+(35*8)+(75*7)+(90*6)+(42*5) = 1646 presses of the number buttons. For a new total of 2922 button presses, or about 41% of the original estimate.
Graph
Distribution of PIN lengths
There's no guarantee that these sequences exist though and I had no deep understanding of how to create them, so I tried the first thing that came to mind and got lucky.

First, generate all possible combinations for each PIN length and then sort the numbers in each PIN. Then sort the PINs for each length comparing them element by element.  Start in the middle with the PINs of length 5 as there are more of these than the others. Then take the PINs of length 4 and go through them one by one from the start and place them to the left of first 5 digit PIN that could follow it. For instance (1, 5, 6, 7) might go to the left of (1, 2, 5, 6, 7) as only a 2 would have to be pressed to get to the 5 digit PIN. Do the same for the 6 digit PINs and place them on the right of the 5 digits. In this case (1, 2, 3, 5, 6, 7) might go to the right of (1, 2, 5, 6, 7) as only a 3 needs to be pressed. Repeat this left right procedure until all PINs are processed. It will look like a mess, but if you sort the sequences by length you will get a spreadsheet that looks like the one below (zoomed and rotated to fit). Look familiar? It's reflects the distribution graph above, and it shows that the sequences can be generated.

Spreadsheet
Rotated spreadsheet of the sequences
I've placed my code in a Github Gist for you to generate your own sequences in case there are a different number of buttons on your lock. If you have a lock with 10 buttons I've already generated the file for you. It looks a little different from the output of the Python file because I've done some some find and replace operations in Notepad++. I've used the word test instead of unlock as well.

Update1 There is a MP3 file of the instructions in this blog post, Linux Text To Speech With Saved Audio.

Update2 I've sorted the sequences by how often the 4 digit PINs appear in the 2009 Rock You data breach. There are two new files that cover the all the PINs. One of them has the sequences sorted by the 4 digit PIN popularity but still groups the sequences lengths, while the other sorts by PIN popularity only. An Excel file with all this data is also included so you can sort the data however you want. The associated files are in this Google drive folder. It could be argued that Rock You users and the users of lock boxes are a completely different demographic, and it's true. However, their users will exhibit similar behaviours like using birthdays and years that make the effort worthwhile. Although the instructions for the lock suggest selecting a PIN between 4 and 7 digits long, 4 digit PINs have been targeted as they are what people tend to think of when you mention PINs, even though 5 digit PINS are more secure.

So what about those locks you see in banks and other offices that have 14 buttons.  I've done the maths so you don't have to, but they can be broken with 50316 button presses. So 4 extra buttons buys you an increase in security by a factor of about 17. Not that it matters, whenever I've seen these used, people aren't too discreet about entering the PIN.

Door Lock
PIN Door Lock

Friday, May 5, 2017

Generating Seed Points For Voronoi Stippling

I've been working on a way to generate an initial distribution of seed points for a Voronoi stippling program that I played with a while ago.  The problem is that for the final image to look good you really need a lot of points, and when you have a lot of points they are very slow to converge to a solution.  The process I used in the past is to generate a few points and allow them to be processed, split them in two, process them and so on and so on.  Each time the number of points is doubled and the solution is eventually found but it's still not great.  An improvement I thought I could try is to linearize the image via a Pseudo-Hilbert (PH) curve and apply a rolling threshold to the data and place points at locations where the counter rolls over.  You may see a major optimisation that I've know all along but haven't implemented.  I'll give you a clue, it a PH curve really necessary?  Find out at the end of the post.😉

The implementation of the threshold is a little abstract so I'll just explain the general concept. First of all we always work on an inverse grey scale version of the image as we are distributing black dots and the background is white. Imagine that the sum of all the pixels in the image is equal to 1,000,000 and you want 2000 points.  This means that each point represents 500.  So as you travel along the PH curve you add the pixel values and every time you accumulate another 500 place a point.

The points generated from this process are then iterated on via the Voronoi stippling process to produce a result. Another small feature I've added is a dithering step. For a certain number of iterations a random displacement is added to each point.  This can be seen in the GIF below as a jiggling of the points. What is the purpose of this? Imagine a jar of jelly beans that won't quite close. What do you do?  You pick the jar up and shake it to settle the contents.  That's what we're doing here.  What you end up with is a number of points that when randomly perturbed, return to their original position.  i.e. a stable arrangement.

The results in the GIF aren't that great.  The main reason for this is that I've kept the features large so you can see what's happening.  If you really wanted to process this image the best way to do it is to increase the size of the image and then process it.  When the calculated Voronoi regions are small and they only contain a few pixels, due to quantization issues the points won't move as the centroid calculations will always yield the same result.  So if you notice herding of points in the final image try enlarging the image and reprocessing it.
Distribution of Seed Stippling Points on a Pseudo-Hilbert Curve
To test the algorithm I've used an image of a plant from the paper I'm working from.  Weighted Voronoi Stippling, by Adrian Secord.  If this link becomes dead, contact me, I have a copy of the paper.
Test Image Used in Adrian Secord's Paper
In the image below I've started modestly with 4000 points to test that everything is working properly, and as you can see, everything is working.  On closer inspection though, you can see that the dynamic range, for want of a better term, of the image has been reduced.  In other words, the areas that are meant to be dark aren't as dark as they're meant to be, and in comparison, the areas that are meant to be light aren't as light.
4000 points - power 1
The dynamic range problem can be solved by pre-compensating the image.  Specifically raising each pixel to a power or exponent.  In the image below the value of each pixel has been squared before processing.  I've referred to this as a power parameter.  I've re-scaled the resulting data back to a range of 0-255 just for consistency but it's really not required.
4000 points - power 2
In the next image the number of points has been doubled and the power parameter has been pulled back slightly to 1.9 and things are starting to look even better.
8000 points - power 1.9
Let's step things up once again and go for 20000 thousand points to match the example in the paper. I've also pulled back the power parameter to 1.6. Why? It's subjective. I think it looks better.
20000 points - power 1.6

I think that looks pretty spectacular.  Of course I've had to resize the image and scale it down a bit so downloading this page isn't a 100 MB exercise, so there's a little bit of quality degradation, but the original vector files are even better.

The point of this post however is to discuss the PH curve approximation for the seed points and how close it gets us to the final image.  What does the initial distribution of seed points look like before any processing?
20000 points - power 1.6 - no processing
The structure is immediately visible, but there is a fuzziness to the image. This is more apparent when looking at an enlarged section. The points are distributed on a grid so you will see more lines and square corners in the arrangement.

Close Up Section of Seed Point Distribution
So what happens after processing?  The points are allowed to find their own space and curved features start to appear.  I can't explain the exact reasoning but in sections the points form curved lines running in multiple directions.  If you can't see what I'm talking about, go and have a look at something called a Fermat Spiral (also a way to distribute points to evenly cover an area) and you'll see what I mean.  In all things just look more pleasing to the eye.
Close Up Section of Points After Processing
I'm sorry but I'm not releasing the code on this one just yet. Why? Because it's terrible. There are very few comments and a lot of sections where code has been commented out. I know the theory is to develop in public and let the code be scrutinised, but I try to make a program so that it's not only functional but it also explains the concept I'm trying to describe to the user.  At this point I think it would just confuse people.

It's also hard to develop things cleanly when you don't exactly know where they are going to go. Unlike a lot of programming where you have a clearly defined goal and a general way to get there. I start not usually knowing either.  Think of this type of coding as a science experiment.  I'll try different things and see what the results are, change things until I get what I want and then go back and clean up a section and lock it down.

I will release this soon though.  There are couple things to fix but it's mostly there.  If for some reason I don't post the code, let me know and I'll help you out.

Here is the optimization I didn't implement.  You don't need a PH curve.  Just add a white border to the source image to make it a square so that the length of a side is a power of 2 and then use an actual Hilbert curve and crop the result.  They are way more efficient to generate than a PH curve.  I did it this way because I was interested in the problem of covering arbitrary rectangular regions with a curve.  As this blog is a labour of love I reserve the right to go off on the occasional tangent to satisfy my curiosity.

Monday, April 10, 2017

Pseudo Hilbert Curve for Arbitrary Rectangular Regions - Part 2

I fixed the problems mentioned in my last blog post about Pseudo Hilbert curves.  When both side of a block are of length divisible by 4 the block is divided into 2x2 sub-blocks.  Each of these blocks are scanned in an appropriate manner to maintain the flow of the parent block.  I won't show all the examples from the paper I'm working from, but the python module I wrote does generate all the example curves.  I can see room for improvement in places but I'm happy with its operation and how fast it works.  After all, perfect is the enemy of good.


The module allows you to specify an rectangular region.  It will then create an in order list of the (x,y) coordinates of the curve and it will create a "2D" list of the length along the curve.  I have some plans for this that I hope workout.

Get the Code!

Thursday, March 30, 2017

Pseudo Hilbert Curve for Arbitrary Rectangular Regions - Part 1

It's been a bit longer than usual so I thought that an update was in order.  I've been working on implementing an algorithm that generates Hilbert curves for arbitrary rectangular regions and I've been having issues.  The paper that describes the algorithm is 

Zhang, Kamata, Ueshige "A Pseudo-Hilbert Scan for Arbitrarily-Sized Arrays"

The problems I've been having aren't so much related to my understanding of the problem, it's coming up with a understandable, fast piece of code to generate the curve (while finding time for sleep and work). I'm using python so I could increase the speed by going to C for some parts, but I've worked on my implementation until it generates a curve for a 3000 by 2000 area in about 20 seconds. I think that's a reasonable speed. The plan was to hold off on this post until I'd solved it.  It forces me to work and sets a deadline to meet.  The code reached a point last night that I was happy with and I was ready to post and then I compared my results with the curves in the paper.  Guess what, they don't match.  I know why and I'll get to that later but first an explanation of the algorithm.

The first step is to recursively divide the region vertically and horizontally.  In the small example below the region is divided into 4 blocks vertically and 4 blocks horizontally.  There will always be an equal number of blocks vertically and horizontally and it will be a  power of two.  These blocks determine the general direction of the curve as they can be easily traversed by a Hilbert curve.   The division algorithm also ensures that the dimensions of each block in all but the bottom and left rows will be a power of two.

Pseudo Hilbert Curve
Partition a 17 x 15 Region into Blocks
The next step is to determine a scanning method for each block.  The way the sides are divided makes this step easier.  Depending on the oddness or evenness of the each of the dimensions some block scanning methods are determined by a simple lookup process.  Others are a bit more complicated.

Hilbert scanning methods
Scanning methods

Once you know the size of the blocks, their location, and their scanning method, the curve can be easily generated.

Here's where things went wrong.  My curves match the curves in the paper for some sections but not others.  Bellow are a couple example of my curve vs the one from the paper and an overlay to show where they match and where they differ.

Pseudo Hilbert Curve
My version of a 123 x 97 curve
.
Pseudo Hilbert Curve
The 123 x 97 curve in the paper
.
Pseudo Hilbert Curve
Overlay of the two curves to see where they differ
.
Pseudo Hilbert Curve
My version of the 125 x 88 curve
.
Pseudo Hilbert Curve
The version of the 125 x 88 curve from the paper
.
Pseudo Hilbert Curve
Overlay of the two curves to see where they differ

I was scratching my head trying to figure out why, and after a bit of research I discovered that the author later made some optimisations to the scan methods for blocks larger than or equal to 4x4. The images in the paper were generated with that method.  Basically larger blocks are scanned with a Hilbert like method and not the bidirectional raster scans shown above.  So I'm not losing my mind. Well, not for that reason anyway.  I need to think about how to alter my code to make this work. Hmmm.

My other blog posts on the hilbert curve can be found here.
Calculating Hilbert Curve Coordinates

Wednesday, February 1, 2017

Voronoi Stippling

I'm trying to write my own software to convert normal raster images to a corresponding stippled version.  If you're unfamiliar with the term, stippling is a style of artwork that that uses are large number of small dots to represent a scene.  Areas with a high density of dots appear darker, while areas without dots are white.  Normally I like to have reasonably good understanding of a subject before I do a blog post about it, but in this case I'll give a quick outline of what I'm trying to achieve and then fill in the details in a later post.

So, how do you get an image like this,
Plant
Stippled Image

From something like this.
Plant
Original Image

To start with I'm trying to replicate the process in a paper by Adrian Secord.

Weighted Voronoi Stippling
Adrian Secord
2nd International Symposium on Non-Photorealistic Animation and Rendering (NPAR 2002)

The first step is to generate a number of points equal to the desired number of stippling points in the final image.  They can be randomly distributed over the image, or ideally via a rough approximation, close to the locations of the stippling dots in the output image.  As the process I'm about to describe is iterative, the closer the initial approximation is the final output, the faster the algorithm will converge.

After the points have been created, their Voronoi diagram is generated.  A Voronoi diagram produces a region for each input point that identifies all the locations that are closest to that dot than any other.  There are some tricks to making sure that the regions around the boundary are properly created but in general it's a simple call to scipy.spatial.Voronoi

That's great, but the process so far hasn't taken the input image into account(unless you use it to generate the initial point locations).  Now that a number of points and their corresponding regions are created, the weighted centroid of the area is calculated.  The centroid then becomes the location of the new point.  This process of creating Voronoi diagrams and calculating centroids is repeated until some convergence criteria is met and is better known as Lloyd's Algorithm.

In the image below, the black dots are the initial points and the white dots are the centroids of the Voronoi regions after an iteration.
Voronoi Diagram
Voronoi Diagram with centroids
Overlaying this diagram on the original image helps to illustrate the process a little better.  Obviously the image below is sparsely stippled and the object isn't recognisable with only 64 points.

Voronoi Diagram
Voronoi Iteration

I've generated the next diagram with I think 256 points to explain a subtle point.  The Voronoi region for each point is a convex polygon.  Therefore its centroid will always be located within itself.  As the number of points increases the size of the regions decreases.  This means that the process will take a long time to converge as the points can only move a small amount during each iteration.   That's why you need to place the initial points as close to their final location as possible.  Or do you?

I only discovered this problem after programming my solution and needed a quick way to overcome it.  If a large number of points take a long time to converge then a small number should converge quickly.  So I started with only two points.  After a few iterations, each point splits, like a cell undergoing Mitosis.  Each point splits into two new points diametrically opposed on a unit circle centred on the old point.  The direction of the split is randomly chosen.  (I have a hunch that continually splitting in the same direction will cause artifacts that will delay convergence)  These new points are then iterated on for a while and then split again.  The key is to get the points close to where they need to be early and then converge and add the detail later.
Voronoi Diagram
Dense Voronoi diagram
I have some ideas I want to try to make the image better.  There are obviously some linearity problems with the output.  White parts aren't as light as they are should be.  My code is a mess too.  It needs a clean up.  I'm having fun though.

Tuesday, January 17, 2017

Arrange Rectangles with Python to Design a Carton

Recently I've been interested in cardboard boxes and how to design and model them.  In my last blog post I described how to create a box in Fusion 360.  It works but it's a cumbersome, frustrating, and time consuming process.  What was needed was a way to go from concept to template quickly.  After looking around and not finding a tool for the job, I designed one.  It's called rectangleBuilder.

Laying out a template isn't too hard once you get the hang of it.  Draw all the rectangles that make up the box you intend to build and then dimension them.  Then define relationships between all the rectangles.  An example of a relationship can be seen in the plan below.  The bottom middle of rectangle B is in the same location as the top middle of rectangle A.

Plan
Box Template Plan

This can be done by defining a Rectangle class in python,  It is initialized with its width and height and by default is placed with its bottom left corner at (0,0).  The Rectangle object is also aware of the position of all its corners and edge midpoints.  When called with a code for one of these points it returns the xy coordinates in list form.  For example, calling an object with the argument 'TM' will return the coordinates of the top middle.  All of this shorthand is explained in the code.

Python Code
Define rectangle sizes

This is the part that I think is really cool.  Positioning the rectangles by defining geometric relationships.  In the code below the bottom middle of rectangle A is positioned at the origin.  Rectangle B is then positioned so that it's bottom middle is in the same position as the top middle of rectangle A.  The last line demonstrates the offset feature.  It essentially says, place rectangle M so that its bottom left corner is in the same position as the bottom right of rectangle B then shift it in the y direction by an amount (t+b).  The few lines of code above and below completely describe the template for the box.

Python Code
Position rectangles

This data can then be used to generate an SVG template in python to see if everything is as expected.  The outline to cut is in black and the bend lines are marked in red.

Box Template
Box template

That's all well and good but how do we transfer that to cardboard?  I don't have a plotter or laser cutter so it has to be done by hand.  After some experimenting, the below instructions are the easiest way to describe the rectangle edges so that they can be easily drawn.  The y coordinate and the start and end x coordinates of the horizontal lines are listed in increasing y order.

Coordinates
Stats and Horizontal lines

The same process is repeated for the vertical lines as well.

Coordinates
Vertical lines

Take the piece of cardboard that you plan to use and draw a base line in the direction of the x axis.  At each end of this line draw perpendicular guides in the direction of the y axis.  Along each of these lines mark all the y coordinates from the horizontal line list using the base line as the zero point.  You can then draw the line segments in the following manner.  77.0 , (84.0 - 124.5) means place a ruler on the 77.0 marks you would have drawn in the last step with the 0 mark on the left guide.  Then draw a line from 84.0 to 124.5 on the ruler.  By working through the list the design will gradually appear.  The vertical list is supplied just to be complete and isn't really needed as the ends of the horizontal lines can be joined to form the rectangles.

guide
Edge guides

The layout looks like the SVG file so everything worked out OK.

Box Plan
Box to cut out

The bend lines can be perforated with a tool like the one below that's used for transferring dressmaking patterns.

Perforated Cardboard
Perforated Bend Lines

Cut out the outlines with a scalpel.

Flattened box

When folded together everything works quite well.  You can see in the image below what my intention was.  The top extends over to cover the entire top and although not necessary is a fun little design challenge.

Cardboard Box
Top overlap

As it's a tight fit, the box needs to be taped together to stop it springing open.

Cardboard Box
Assembled Box

Everything seems to fit nicely.  A little tweaking could make things better, but I'm happy with it.  In the end the fit all comes down to how accurately the template is drawn and bent.  Folding across the corrugations is easy to get right, but folding in the same direction as the corrugations is hard and I need to come up with a better way to do it.  When folding with the corrugation it's not uncommon for the fold to follow the middle of the corrugation.  This could be a couple mm away from from the line.
Get The Code!

.