clojure
Oct 25, 2023

Designing 3D Printable Objects with Clojure and OpenSCAD

Combine creativity with algorithmic generation to create beautiful and precise physical objects

author picture
Dajana Herichova
Software Engineer
image

In this blog post, we dive into the fusion of art, technology, and code, exploring the endless possibilities and efficiency of using Clojure for 3D design. Through 3D printing with code, we can bring complex geometries into the real world with intricate precision.

So let’s dive in to the world of 3D modeling together, and create our first 3D object with Clojure.

Setting Up Your Environment, with Clojure and OpenSCAD

We’ll start by installing the software we need:

  • Install OpenSCAD (to visualize and render models in real-time). Visit the official OpenSCAD website and download the appropriate version for your operating system. Follow the installation instructions provided by the software.
  • Set up a Clojure environment
  • Clone the GitHub project squiggle. When the project is cloned, execute the command clj -X:new to download dependencies and generate the squiggle.scad file.
  • Open the squiggle.scad file in OpenSCAD by running openscad CAD/squiggle.scad from the command line in the project directory.
  • At this point you should see an OpenSCAD screen with coordinates.
image
  • To run the project in Emacs, you can run it by invoking M-x and cider-jack-in. If you’re using a different IDE, I recommend looking at the official Clojure getting started guide for detailed instructions tailored to your specific environment.
  • Open the playground.clj namespace. This namespace already contains everything we need to start with our design.

From Idea to Reality: Designing 3D Printable Objects

We’ll start by designing an object using basic shapes. Don’t worry, this approach can lead to creations that are both funny and interesting. We can combine spheres, cuboids, cylinders, and other elementary shapes, to craft imaginative characters, playful sculptures, or quirky inventions. The simplicity of the design elements adds it’s own charm.

And when you’re a programmer, the ideal first design choice is undoubtedly the debugging duck – an invaluable companion for troubleshooting :)

Within the playground.clj namespace, you can find the my-design function, which takes a radius as an argument. It generates a sphere with the provided radius. Once the namespace is compiled, the rendered sphere will be visible in OpenSCAD.


(def resolution 300)

(defn my-design [radius]
  (os/sphere {:radius radius
              :resolution resolution}))

(core/write (my-design 1))

The open-scad.clj namespace contains all the basic open-scad shapes and functions.

image

To create the body of the duck, we will use this sphere. As an initial step, let’s rename the function my-design to body so that we can design each part of the duck independently.


(def resolution 300)

(defn body [radius]
  (os/sphere {:radius radius
              :resolution resolution}))

Let’s now experiment with this shape to create a more duck-like form. We can achieve this by scaling it along the x-axis with a factor of 1.5 and setting the radius to 10. Additionally, we need to render the body definition instead of my-design function call result.

ℹ️ os/scale is used to resize or scale a shape in three-dimensional space


(def body
  (os/scale {:x 1.5}
            (os/sphere {:radius 10
                        :resolution resolution})))

(core/write body)
image

To ensure that our duck can sit on a flat surface, we need to remove the bottom part of the spheroid. For this purpose, we’ll use the os/difference function. However, before performing the subtraction, let’s create the shape that will be “cut” from the duck body. It will be a cuboid with dimensions 100, 100, and 10. When creating a cuboid, we provide a vector of dimensions “a”, “b”, and “c” as an argument.

ℹ️ os/difference is used to subtract one or more shapes from another shape

(core/write (os/cube [100 100 10]))
image

Although the shape itself is precisely what we need, it requires correct positioning to achieve the desired result. Without proper placement, the shape will not align correctly.


(def body
  (os/difference (os/scale {:x 1.5}
                           (os/sphere {:radius 10
                                       :resolution resolution}))
                 (os/cube [100 100 10])))

(core/write body)
image

To position it correctly, we’ll use the os/translate function. It accepts a map of positioning coordinates as the first argument and the object we wish to move as the second argument. This way, we can precisely move the object to the desired location.

ℹ️ os/translate is used to move or translate a shape in three-dimensional space.

(def body
  (os/difference (os/scale {:x 1.5}
                           (os/sphere {:radius 10
                                       :resolution resolution}))
                 (os/translate {:z -17 :x -50 :y -50}
                               (os/cube [100 100 10]))))
image

Now it’s time to incorporate the tail. We’ll introduce two new functions for this purpose. First, the os/rotate function, which accepts a map of axes and angles as arguments, allowing us to rotate an object around a specific axis. Second, the os/hull function will be used.

ℹ️ os/rotate is used to rotate a shape in three-dimensional space

ℹ️ os/hull is used to create a new shape that encompasses multiple input shapes. It takes multiple shapes as arguments and generates a new shape that covers the outer boundary of all the input shapes. The resulting shape forms a convex hull that encloses the combined geometry of the input shapes.

To design the tail more effectively and have a better overview of our progress, we will create a separate definition for it.

As a foundation for the tail, we will generate a scaled sphere with a radius of 2. However, this time we will scale it along both the :x and :y axes.

(def tail
  (os/scale {:x 2 :y -0.1} (os/sphere {:radius 2})))


(core/write tail)
image

If we apply os/hull to the body shape and tail, we won’t observe any changes because the tail piece will be contained within the body.

(def body
  (os/hull (os/difference (os/scale {:x 1.5}
                                    (os/sphere {:radius 10
                                                :resolution resolution}))
                          (os/translate {:z -17 :x -50 :y -50}
                                        (os/cube [100 100 10])))
           tail))

(core/write body)
image

By shifting the tail towards the right (along the x-axis) and upward (along the z-axis), the tail is an integrated part of the body and is clearly visible.

(def tail
  (os/translate {:x 14 :z 2}
                (os/scale {:x 2 :y -0.1} (os/sphere {:radius 2}))))
image

However, at the moment, it doesn’t resemble the body of a duck at all. Instead, I would say it appears more like a peculiar lemon shape. To change this, we can achieve a more accurate representation by rotating the tail along the y-axis.

Let’s use os/rotate again, the function used to rotate a shape in three-dimensional space

(def tail
  (os/rotate {:y -25}
             (os/translate {:x 14 :z 2}
                           (os/scale {:x 2 :y -0.1} (os/sphere {:radius 2})))))
image

Although it has improved, it’s still not perfect. It’s time to make some adjustments by cutting a specific shape from the duck body. This will help us create a duck body that looks, at the very least, appealing.

(def tail-cut
  (->> (os/sphere {:radius 5})
       (os/scale {:x 3 :y 2})
       (os/translate {:x 8 :z 7})
       (os/rotate {:y -22})))

(core/write tail-cut)
image

Now let’s proceed to create a duck definition where we will start assembling the various pieces. The initial step involves taking the difference between the body and the tail-cut. However, before doing so, we will extract the tail from the body function, leaving the function to exclusively handle the body. Inside the duck definition, we will introduce a hull operation.

(def body
  (os/difference (os/scale {:x 1.5}
                           (os/sphere {:radius 10
                                       :resolution resolution}))
                 (os/translate {:z -17 :x -50 :y -50}
                               (os/cube [100 100 10]))))

(def duck
  (os/hull body tail))

(core/write duck)
image

We will now continue by cutting the tail section.

(def duck
 (os/difference (os/hull body tail) tail-cut))
image

We will include the body component once again. After removing the tail from the body definition, the shape now primarily consists of an egg shape with a flat bottom. To connect multiple pieces together, we will utilize the os/union function.

ℹ️_os/union is used to combine multiple shapes or geometries into a single object._

(def duck
  (os/union body (os/difference (os/hull body tail) tail-cut)))
image

Now we reach an interesting part where we will use the power of Clojure functions. We’re going to create the wings for our design. Let’s start with the feather component, which involves scaling, translating, and rotating a sphere shape.

(defn feather [translate-x translate-y feather-size rotate-y]
  (->> (os/sphere {:radius feather-size
                   :resolution resolution})
       (os/scale {:x 2.9 :z 1.5})
       (os/translate {:x translate-x
                      :y translate-y})
       (os/rotate {:y rotate-y})))

We can visualize the feather by rendering it. And by experimenting with different feather sizes, we can determine the ideal size for our duck.

(core/write (feather 0 0 2 0))
image

After rendering various feather sizes, I concluded that the optimal size for the outer feathers is 2, while for the inner feathers it is 1.5.

With this information in mind, we can proceed to create a row of feathers. This function will accept translation coordinates “x” and “y” as arguments to determine the position of the feather row. Additionally, it will take the feather size and rotate-y coordinate to appropriately rotate the feathers, ensuring they point in the desired direction.

If you’re new to Clojure and wondering what is the purpose of the map function, you can learn more about it by reading this resource.

(defn row-of-feathers [translate-x translate-y feather-size feathers-count]
  (let [feather-angles (range 30 -11 (- (/ 40 (dec feathers-count))))]
    (map (partial feather translate-x translate-y feather-size)
         feather-angles)))

To ensure that the feather row spans a total of 40 degrees (because it covers exactly the area we need), we need to assign each feather a specific angle. We can calculate the angle for each feather using the expression (range 30 -11 (- (/ 40 (dec feathers-count)))).

To see whether it matches our expectations, we can render the feather row.

(core/write (apply os/union (row-of-feathers 5 0 1 4)))

To combine all the individual feathers into a single wing, we use the (apply (os/union ...) function. In this particular case, we apply it only for rendering purposes to see if the row of wings aligns with our intended design. Additionally, we have provided a translation of translate-x 5 to the row of feathers. This is necessary because we are rotating the feathers and need to position them further away from the center point.

When we rotate the shape around its center point:

(core/write (apply os/union (row-of-feathers 0 0 1 4)))
image

And when we translate it along the x-axis:

(core/write (apply os/union (row-of-feathers 5 0 1 4)))
image

Now let’s combine the large and small feathers.

(defn create-wing [{:keys [outer-feathers-count outer-feather-size
                           inner-feathers-count inner-feather-size]}]
  (apply os/union
         (apply merge
                (row-of-feathers 5 0 outer-feather-size outer-feathers-count)
                (row-of-feathers 3.5 -1.1 inner-feather-size inner-feathers-count))))

(core/write (create-wing {:outer-feathers-count 3
                          :outer-feather-size 2
                          :inner-feathers-count 4
                          :inner-feather-size 1.5}))
image

We can then place it on the duck’s body, making use of rotation and translation to position it correctly.

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))]
    (os/union body left-wing (os/difference (os/hull body tail) tail-cut))))

(core/write duck)
image

To incorporate a wing on the opposite side, we can use the (os/mirror) function to mirror it around the :y axis.

ℹ️_os/mirror function is used to create a mirrored or reflected copy of a shape._

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))
        right-wing (os/mirror {:y 1} left-wing)]
    (os/union body left-wing right-wing (os/difference (os/hull body tail) tail-cut))))
image

Incorporating the head is a straightforward process. We simply position a large sphere on top of the body.

(def head
  (os/translate {:x -5 :z 16}
                (os/scale {:x 1.1 :y 1.1}
                          (os/sphere {:radius 10
                                      :resolution resolution}))))

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))
        right-wing (os/mirror {:y 1} left-wing)]
    (os/union body
              left-wing
              right-wing
              head
              (os/difference (os/hull body tail) tail-cut))))
image

Additionally, we will include an eye on each side of the head.

(def eye
  (os/translate {:z 17
                 :x -13.5
                 :y -5}
                (os/union
                 (os/translate {:x -0.7
                                :z 0.3
                                :y -0.5}
                               (os/sphere {:radius 0.6}))
                 (os/scale {:z 1.2}
                           (os/sphere {:radius 1.4})))))

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))
        right-wing (os/mirror {:y 1} left-wing)
        right-eye eye
        left-eye (os/mirror {:y 1} right-eye)]
    (os/union body
              left-wing
              right-wing
              left-eye
              right-eye
              head
              (os/difference (os/hull body tail) tail-cut))))
image

To construct the duck’s bill, let’s use the os/rotate-extrude function. This function generates a 3D object by rotating a 2D shape around the z-axis. We will begin with the following 2D shape.

(def bill
  (os/hull
   (os/translate {:x 15} (os/circle {:radius 1
                                     :resolution resolution}))
   (os/translate {:x 10} (os/circle {:radius 4
                                     :resolution resolution}))))
(core/write bill)
image
(def bill
  (os/scale {:y 1.3}
            (os/rotate-extrude
             {:angle 180
              :resolution resolution}
             (os/hull
              (os/translate {:x 15} (os/circle {:radius 1
                                                :resolution resolution}))
              (os/translate {:x 10} (os/circle {:radius 4
                                                :resolution resolution}))))))
image

After applying a small scaling adjustment, we will incorporate the os/hull function to finalize the bill design.

(def bill
  (os/hull (os/scale {:y 1.3}
                     (os/rotate-extrude
                      {:angle 180
                       :resolution resolution}
                      (os/hull
                       (os/translate {:x 15} (os/circle {:radius 1
                                                         :resolution resolution}))
                       (os/translate {:x 10} (os/circle {:radius 4
                                                         :resolution resolution})))))))
image

Position it onto the duck’s body.

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))
        right-wing (os/mirror {:y 1} left-wing)
        right-eye eye
        left-eye (os/mirror {:y 1} right-eye)
        bill (os/rotate {:z 90}
                        (os/translate {:z 13
                                       :y 8}
                                      (os/scale {:x 0.5
                                                 :y 0.5
                                                 :z 0.5}
                                                bill)))]
    (os/union body
              bill
              left-wing
              right-wing
              left-eye
              right-eye
              head
              (os/difference (os/hull body tail) tail-cut))))

(core/write duck)
image

To add a touch of elegance to our duck, let’s give it a hat using shapes like os/rounded-cylinder and os/cylinder.

(def hat
  (os/union
   (os/cylinder {:radius 8
                 :height 1
                 :resolution resolution})
   (os/rounded-cylinder {:radius 5
                         :height 7
                         :resolution resolution})
   (os/rotate-extrude {:resolution resolution}
                      (os/translate {:x 8} (os/circle {:radius 1})))))

(core/write hat)
image

If you want to personalize your duck further, you can also add text using os/text. We will position it on top of the hat. Since the text is a 2D object and we are working with 3D shapes, we will use the os/linear-extrude function. This function transforms the 2D shape into a 3D object by extruding it in the z-axis. The height parameter determines the extent of the extrusion along the z-axis.

ℹ️_os/linear-extrude is used to create a 3D object by extruding a 2D shape in a specified direction. It takes a 2D shape as input and extends it along the specified axis to generate a solid 3D object._

(def text
  (os/linear-extrude {:height 1}
                     (os/text {:text "JUXT"
                               :size 2})))

(core/write text)
image

We will incorporate the text into the hat by utilizing the os/difference function, creating an embossed effect on the top surface of the hat.

(def hat
  (os/difference (os/union
                  (os/cylinder {:radius 8
                                :height 1
                                :resolution resolution})
                  (os/rounded-cylinder {:radius 5
                                        :height 7
                                        :resolution resolution})
                  (os/rotate-extrude {:resolution resolution}
                                     (os/translate {:x 8} (os/circle {:radius 1}))))
                 (os/translate {:z 6
                                :x -3.5
                                :y -1}
                               text)))

(core/write hat)
image

Let’s position the hat on top of the duck’s head.

(def duck
  (let [left-wing (os/rotate {:y -25}
                             (os/translate {:x -3.5
                                            :y -8.8
                                            :z 0.5}
                                           (create-wing {:outer-feathers-count 3
                                                         :outer-feather-size 2
                                                         :inner-feathers-count 4
                                                         :inner-feather-size 1.5})))
        right-wing (os/mirror {:y 1} left-wing)
        right-eye eye
        left-eye (os/mirror {:y 1} right-eye)
        bill (os/rotate {:z 90}
                        (os/translate {:z 13
                                       :y 8}
                                      (os/scale {:x 0.5
                                                 :y 0.5
                                                 :z 0.5}
                                                bill)))
        hat (os/rotate {:x 25}
                       (os/translate {:z 23.6
                                      :x -5
                                      :y 6}
                                     (os/scale {:x 0.7
                                                :y 0.7
                                                :z 0.7}
                                               hat)))]
    (os/union body
              bill
              left-wing
              right-wing
              left-eye
              right-eye
              head
              hat
              (os/difference (os/hull body tail) tail-cut))))



(core/write duck)
image

The duck is now complete, and doesn’t he look dapper with his charming hat? :)

You can access the complete code and additional examples here

Bringing Your Designs to Life:

To render your duck or any other object you’ve designed, navigate to edit -> render in OpenSCAD. From there, you can prepare your design for printing on your 3D printer or submit it to a local or online 3D printing service.

Now you can see that by combining the elegance and expressiveness of Clojure with the innovative capabilities of 3D printing, you have the power to turn your ideas into reality.

With Mr Duck, we wish you luck! :)

image
Recommended Resources
Head Office
Norfolk House, Silbury Blvd.
Milton Keynes, MK9 2AH
United Kingdom
Company registration: 08457399
Copyright © JUXT LTD. 2012-2024
Privacy Policy Terms of Use Contact Us
Get industry news, insights, research, updates and events directly to your inbox

Sign up for our newsletter