Sunday 22 July 2018

Creating 3D Printable Art with OpenJSCAD

This month we were lucky to have the multi-talented James Porter help us explore creating algorithmically designed for 3D printing.


The slides for James' tutorial are here: [pdf]. His event page is here: [link].

A video recording is here: [skillsmatter].


Why 3D Printing

The ever onward march of technology brings with it not just new innovations, but also makes them ever more affordable and practical.

Printing 3-dimensional physical objects has seen a growth in popularity and accessibility in recent years. Practical applications are plenty, but so are creative ones too.

Suddenly, imagining 3-dimensional objects and making them reality is no longer the realm of magical fantasy or science fiction.

Here is an example of James' experiments - an interesting landscape made of hexagonal rock forms.


In algorithmic art, we can use computers to create art forms that no human hand could ever create. Extending this to 3-dimensional forms is exciting!


OpenJSCAD

We're very fond of tools that work entirely in a browser, with no additional software needing to be installed. Not only does it make the tools independent of your computer brand, it also reduces technical complexity making them ideal for beginners. We love openprocessing, which is a p5js environment entirely on the web.

James introduced OpenJSCAD, which allows us to use code to create 3-dimensional forms, which can then be exported in a form suitable for 3D printers.


James then led us through the basic concepts for coding in OpenJSCAD, together with some interesting challenges for us to tackle. You can see them being discussed on the video.

Here I'll try my own introductory tutorial to create simple 3D forms.


Basic OpenJSCAD Ideas

OpenJSCAD will create 3-dimensional forms defined by computer program we write. That program is written in code which looks very much like javascript (tutorial).

OpenJSCAD expects to run a main() function, and expects it to return a single object. That single object can be a single sphere or cube, or a collection of shapes which together form an interesting form.


Let's try creating the simplest of objects. Try replacing the main() function with just the following simple code:

function main () {
  return cube({size: 10, center: true});
}

You can see the thing being returned by main() is a cube. The data inside the cube() function sets the size to 10, and locates the centre of the object at the centre of the virtual world. Let's have a look at the result:


You can use your pointer to drag the view around and confirm it is indeed a 3-dimensional object:


If we want to compose a more interesting form, we need to use several basic shapes. Remember that the main() function can only return one single thing, so that thing needs to be a group of other objects. That's called a union here, which reflects the mathematical term, a union of sets.

Here's how we use union:


Let's try joining a cube and a sphere together:

function main () {
  return union(

      cube({size: 20, center: true}),
      sphere({r: 12, center: true})

      );
}

The sphere() function needs its radius set, which we can see is set to 12 here. Here's the result:


If we kept adding more shapes, they'd all be overcrowding the same origin of this 3-d world. We need a way to move them around. We can do this by using a translate() function:


The translate() function takes an object and translates it by [x, y, z] along these axes. A translation of [1, 0, 0] would translate an object only along the x-axis by 1.

Let's try it:

function main () {
  return union(
      
      cube({size: 10, center: true}),
      translate([10, 0, 0], sphere({r: 6, center: true}))
      
      );
}

The result is a cube at the centre of the world, and a sphere which has been shifted along the x-axis by 10 units.


Let's chain a few of these shapes together. Here's some code which has a series of spheres and cubes all shifted along so that they form a nice line:

function main () {
  return union(
      
      translate([-30, 0, 0], sphere({r: 6, center: true})),
      translate([-20, 0, 0], cube({size: 10, center: true})),
      translate([-10, 0, 0], sphere({r: 6, center: true})),
      translate([0, 0, 0], cube({size: 10, center: true})),
      translate([10, 0, 0], sphere({r: 6, center: true})),
      translate([20, 0, 0], cube({size: 10, center: true})),
      translate([30, 0, 0], sphere({r: 6, center: true}))
      
      );
}

Here's the result:


Cool!

We can see how we could create quite sophisticated forms just by using a union of simple objects. I you look at the OpenJSCAD reference, you'll see that other simple shapes like a cylinder, torus and a polyhedron, as well as flat shapes like a circle and a rectangle.You can use any of these in a union, and you can translate any of them too.

Let's look at one more shape modifier - scale - which you can guess scales a shape. We use it in a similar way to translate where we pass it the object we want to scale.

The following code is the same as that above, but has the spheres scaled by 1.7.

function main () {
  return union(
      
      translate([-30, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([-20, 0, 0], cube({size: 10, center: true})),
      translate([-10, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([0, 0, 0], cube({size: 10, center: true})),
      translate([10, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([20, 0, 0], cube({size: 10, center: true})),
      translate([30, 0, 0], scale(1.7, sphere({r: 6, center: true})))
      
      );
}

Here's the result.


We can see that only the spheres have been scaled, and that's because we only applied scale() to the spheres.

We have an interesting shape that we could now bring to life with a 3D printer. We can use the export options to save a file accepted by many printers. The STL format is more or less the standard for 3D printing.



More Interesting Ideas 

We can do more than just join objects together. One really interesting idea is to subtract one object from another, a bit like carving out one shape from another.

Let's go back to our example which had a simple union of a cube and a sphere. Let's change the union to a difference:

function main () {
    
  return difference(
      
      cube({size: 10, center: true}),
      translate([10, 0, 0], sphere({r: 6, center: true}))
      
      );
}

Comparing the results, we can see that the difference takes the first object and craves out the second object.


We can combine several objects into a union, and use that union to crave out a shape from another object. Have a look at this code:

function main () {
    
  return difference (
      
      cube({size: 10, center: true}),
      union (
          translate([10, 0, 0], sphere({r: 6, center: true})),
          translate([-10, 0, 0], sphere({r: 6, center: true})),
          translate([0, 10, 0], sphere({r: 6, center: true})),
          translate([0, -10, 0], sphere({r: 6, center: true})),
          translate([0, 0, 10], sphere({r: 6, center: true})),
          translate([0, 0, -10], sphere({r: 6, center: true}))
      )
      
      );
}

We can see that we've constructed a union of six spheres, and that union is taken from a cube.


You can explore more ways of combining and transforming objects on the OpenSCAD reference pages.


JavaScript Power

We also have the full power of javascript at our disposal. That means we can use loops, functions, and lasts to help construct sophisticated shapes more efficiently.

Let's use a loop to construct a circle of spheres.

function main () {
    
    // list of sheres
    var spheres = [];
    
    // loop
    for (let angle = 0; angle < 360; angle += 40) {
        let x = 10 * cos(angle);
        let y = 10 * sin(angle);
        
        spheres.push( translate([x, y, 0], sphere({r: 3, center: true})) );
    }
    
  return union(spheres);
}

You can see we create an empty list called spheres, and then using a loop, we add lots of spheres to it, each translated according to a bit of trigonometry to follow the edge of a larger circle. Finally the main() function returns the union of the list of spheres.


We can extend this idea into the z-axis, and vary the size of the spheres too.

function main () {
    
    // list of sheres
    var spheres = [];
    var z = 0;
    var distance = 10;
    var size = 1;
    
    // loop
    for (let angle = 0; angle < 720; angle += 10) {
        let x = (0.1 + distance) * cos(angle);
        let y = (0.1 + distance) * sin(angle);
        
        spheres.push( translate([x, y, z], sphere({r: 0.1+size, center: true})) );
        
        z += 0.2;
        distance *= 0.99;
        size *= 0.95;
    }
    
  return union (spheres);
}

The result is interesting, although not printable as it isn't a fully connected object.


A more printable composition can be made by carving out spheres from a block:

function main () {
    
    // list of sheres
    var spheres = [];
    
    // loop
    for (let angle = 0; angle < 360; angle += 40) {
        let x = 10 * cos(angle);
        let y = 10 * sin(angle);
        
        spheres.push( translate([x, y, 0], sphere({r: 3, center: true})) );
    }
    
    var block = translate([0, 0, -3], scale( [3, 3, 0.3], cube({size: 10, center: true}) ) );
    
  return difference(block, union(spheres));
}

The results are rather nice. In fact the union would make a nice physical object itself.


In the meetup itself, we were encouraged to explore using simple maths to calculate vertical blocks which form a landscape when places regularly on a grid.


James provides many more examples to explore on his GitHub repository.


Physical Objects

James also brought along examples of 3D printed forms for us to experience for real.


He did discuss some of the limitations of 3D printers that are priced to appeal to home enthusiasts.

  • there is a limit to the fine detail and intricacy that cheaper printers can reproduce
  • some objects bend and distort during the printing process
  • some shapes are either impossible, or barely constructible
  • the time taken to print objects is long, many minutes or hours even at medium quality settings




Conclusion

James succeeded in showing how easily and accessibly we can create objects ready for 3D printing, and not just any object but objects created algorithmically through code.

I was really pleased that the class was inspired by James, with some wanting to go on to try 3D printing themselves.


References