Thomas shares makes

2020-12-13

Openscad: Interlocking Hash shaped platform

concept

While designing a screw-drive based RC tank (perhaps more on that later), I wanted to create a large easily customisable support structure out of a minimal amount of plastic. I started prototyping an interlocking structure in the shape of a octothorpe (#). This turned out to be a lot of ugly code, and so I got sucked into rabit hole of optimising my openscad model and found a cleaner solution.

I found this nice article on advanced use of OpenSCAD which explains how to make OpenSCAD modules behave like an operator, an essential trick to factor out repeated translation operations.

The essence of it is that you can use the children() call to access the objects from the scope that the module is applied to. To iterate over the objects, you can use a for loop for 0..$children.

I created a module that removes chunks out of overlapping objects like the beams like shown next, so they slide/hook into each other:

desk

A simple operator module

As a simple example of creating module operators, lets look at this helper module. It will remove the top or bottom side (on an axis) of an object.

module side_of(pn, sidelength, v)
{
    // mask-select positive or negative side of child object
    // sidelength: side of large volume used
    // pn: positive or negative side of axis
    // v: xyz vector used to define the axis
    intersection()
    {
        children(0);
        // move centered cube to positive or negative part
        translate([ pn*v[0]*sidelength/2,
                    pn*v[1]*sidelength/2,
                    pn*v[2]*sidelength/2]
                    )
            // masking object
            cube([sidelength,sidelength,sidelength],center=true);
    }
}

As you can see, it performs an intersection between the chilren() object and a big cube on the wanted side.

My interlocking operator-module

I called the module 'jigsaw'. I tried to document the code with comments, but will try to explain in a concise way.

Given a child number, the module will bite out of the object the upper part of the overlapping volume with objects defined earlier, and the bottom part of the overlapping volume for objects defined later.

If you define horizontal beams first, and then the vertical beams, you will end up with consistent pattern of horizontal beams carrying the vertical ones.

module jigsaw(l, v, i)
{
    /* returns a selected child object from intersecting child objects,
    and removes half of the intersecting area
    arguments:
     l: side length of a large helper volume used to mask half of the working volume of openscad
     v: xyz vector, axis used to mask set to 1, others must be 0
     i: modified child object to return
    limitations: the objects must be centered along axis specified in v
    */

    difference()
    {
        /* substract from selected object */
        children(i);


        if (i > 0) // not applicable to first object
        {
            for(j = [0: i-1])
            {
                /* remove pieces of objects earlier in the list */
                side_of(-1, l, v) // select bottom side
                {
                    // get overlapping area
                    intersection()
                    {
                        children(i); // selected object
                        children(j); // intersecting object
                    }
                }
            }
        }
        if (i < $children-1) // not applicable to last object
        {
            for(j = [i+1: $children-1])
            {
                /* remove pieces of objects later in the list */
                side_of(1, l, v) // select upper side
                {
                    // get overlapping area
                    intersection()
                    {
                        children(i); // selected object
                        children(j); // intersecting object
                    }
                }
            }
        }
    }
}

Application

We can call the jigsaw code like this, on a bunch of randomly placed objects:

module piece(i)
{
    jigsaw(1000,[0,0,1],i)
    {
        mirror([0,0,0])
            translate([0,10,0])
                cube([40, 5, 10],center=true);
        mirror([0,1,0])
            translate([0,10,0])
                cube([40, 5, 10],center=true);

        mirror([0,0,0])
            translate([10,0,0])
                cube([5, 40, 10],center=true);
        mirror([1,0,0])
            translate([10,0,0])
                cube([5, 40, 10],center=true);

    }
}

Notice that I unrolled the loop on purpose. That is needed because a for loop would be treated as a single child of the scope to which the operator module is applied to. Unfortunately this is necessary until this enhancement ticket is resolved.

The code can be optimised a bit further by creating a module that returns one of the beams bases on its number. But that would take us too far now.

I generated the exploded view of the assembly:

piece(0);
piece(1);
translate([0,0,10])
{
    piece(2);
    piece(3);
}

Liked something? Worked on something similar? Let me know what you think on Mastodon!
You can use your Mastodon account to reply to this post.

Reply to post

You can respond to this post with an account on the Fediverse or Mastodon. Since Mastodon is decentralized, you can use your existing account or create your account on a server of your choice.

Copy and paste this URL into the search field of your favourite Fediverse app or the web interface of your Mastodon server.

Learn how @carlschwan wrote the code that loads Mastodon posts into this webpage here.

Follow me on Mastodon!