Skip to main content

OpenSCAD Rendering Tricks, Part 1: Animated GIF

This is my fourth post in a series about the open source split-flap display I’ve been designing in my free time. Check out a video of the prototype.

Posts in the series:
Scripting KiCad Pcbnew exports
Automated KiCad, OpenSCAD rendering using Travis CI
Using UI automation to export KiCad schematics
OpenSCAD Rendering Tricks, Part 1: Animated GIF

Early when designing the split flap 3D model using OpenSCAD I wanted to include a visualization in the project’s README so others could see what it looked like. It’s possible to capture an image manually (File→Export→Export as Image), but that’s an extra thing to remember to do after every change and it’s also not very consistent. The image that’s exported is basically a snapshot of the current preview window, so the image dimensions and camera angle would be different each time. Plus, a single static image doesn’t fully convey the 3D model, so I wanted something more dynamic.

The final product: a 360° animation that cycles through three views of the model.

I was inspired by Bryan Duxbury’s blog post on creating an animated gif from an OpenSCAD model. He used OpenSCAD’s built-in animation feature, which lets you parameterize your model using a special animation time variable, $t. To make a spinning animation, you can just wrap your model in a rotate transformation proportional to $t. This works well, but still requires some manual export steps from the GUI.

To fully automate this, I used OpenSCAD’s command-line interface which lets you specify options like --imgsize=width,height and --camera=translatex,y,z,rotx,y,z,dist to control the exported image. This makes it easy to write a script that exports snapshots from 360 degrees:

num_frames = 50
start_angle = 135
for i in range(num_frames):
    angle = start_angle + (i * 360 / num_frames)
    openscad.run(
        'splitflap.scad',
        'frame_%05d.png' % i,
        output_size = [320, 240],
        camera_translation = [0, 0, 0],
        camera_rotation = [60, 0, angle],
        camera_distance = 600,
    )


(This uses a simple Python wrapper to invoke OpenSCAD’s command line interface)

 In addition to a simple rotation, I wanted to showcase different parts of the model in the animation. At the top of splitflap.scad , I defined a few variables that control the visibility/opacity of the enclosure and flaps (this was also useful while designing the model):

render_enclosure = 1; // 2=opaque color; 1=translucent; 0=invisible
render_flaps = true;


Then from a script, I can invoke OpenSCAD using arguments like -D render_enclosure=0 -D render_flaps=false which override the variable definitions in the file. I use this so that over the course of three animated revolutions you can see all the different parts of the design.

Three different views of the model by changing the render_enclosure and render_flaps variables.
Unfortunately, by invoking openscad once per frame, the 3D model’s geometry needs to be recompiled for every camera angle rendered, which takes a nontrivial amount of time. With a desired 50 frames per revolution * 3 rendering options, that’s 150 total invocations of OpenSCAD! As far as I can tell there’s no easy way around this, but we can still speed it up by using multiple cores.

Using a threadpool (multiprocessing.dummy.Pool in Python) we can enqueue each of the OpenSCAD frame-rendering tasks to be run in parallel across a specified number of workers. Since each OpenSCAD process uses up to a single core, we can choose a pool size to match the number of cores available.

from multiprocessing.dummy import Pool
num_frames = 50
start_angle = 135
def render_frame(i):
    angle = start_angle + (i * 360 / num_frames)
    openscad.run(
        'splitflap.scad',
        'frame_%05d.png' % i,
        output_size = [320, 240],
        camera_translation = [0, 0, 0],
        camera_rotation = [60, 0, angle],
        camera_distance = 600,
    )
pool = Pool() # By default, Pool uses one thread per available CPU
for _ in pool.imap_unordered(render_frame, range(num_frames)):
    # Consume results as they occur so any task exceptions are rethrown asap
    pass
pool.close()
pool.join()


As a minor aside, it’s not really necessary to use separate threads, since each task is already launching a separate subprocess, but a threadpool provides a convenient abstraction for bounded parallel execution.

On my machine, rendering with a 4-thread Pool reduced the rendering time from 6 minutes 41 seconds down to just under 3 minutes!

The last step is to put all those frames together as an animated gif, which is fairly straightforward using ImageMagick:
convert 'frame_*.png' -set delay 1x15 animation.gif

The full script implementation can be found in the following files:
/3d/generate_gif.py
/3d/openscad.py

In a past blog post, I discussed how I run this script using Travis CI to automatically re-render the 3d animation every time I make a change to the source code. You should check it out if you haven’t already: Automated KiCad, OpenSCAD rendering using Travis CI.

Thanks for reading! In part 2 I’ll cover some more OpenSCAD tricks with similar command line scripting techniques to easily export a design for laser cutting.

Comments

Post a Comment

Popular posts from this blog

OpenSCAD Rendering Tricks, Part 3: Web viewer

This is my sixth post in a series about the  open source split-flap display  I’ve been designing in my free time. Check out a  video of the prototype . Posts in the series: Scripting KiCad Pcbnew exports Automated KiCad, OpenSCAD rendering using Travis CI Using UI automation to export KiCad schematics OpenSCAD Rendering Tricks, Part 1: Animated GIF OpenSCAD Rendering Tricks, Part 2: Laser Cutting OpenSCAD Rendering Tricks, Part 3: Web viewer One of my goals when building the split-flap display was to make sure it was easy to visualize the end product and look at the design in detail without having to download the full source or install any programs. It’s hard to get excited about a project you find online if you need to invest time and effort before you even know how it works or what it looks like. I’ve previously blogged about automatically exporting the schematics, PCB layout , and even an animated gif of the 3D model to make it easier to understand the project at a glanc

Scripting KiCad Pcbnew exports

This is my first post in a series about the  open source split-flap display  I’ve been designing in my free time. Check out a  video of the prototype . Posts in the series: Scripting KiCad Pcbnew exports Automated KiCad, OpenSCAD rendering using Travis CI Using UI automation to export KiCad schematics OpenSCAD Rendering Tricks, Part 1: Animated GIF OpenSCAD Rendering Tricks, Part 2: Laser Cutting OpenSCAD Rendering Tricks, Part 3: Web viewer For the past few months I’ve been designing an open source split-flap display in my free time — the kind of retro electromechanical display that used to be in airports and train stations before LEDs and LCDs took over and makes that distinctive “tick tick tick tick” sound as the letters and numbers flip into place. I designed the electronics in KiCad, and one of the things I wanted to do was include a nice picture of the current state of the custom PCB design in the project’s README file. Of course, I could generate a snapshot of the

Using UI automation to export KiCad schematics

This is my third post in a series about the open source split-flap display I’ve been designing in my free time. I’ll hopefully write a bit more about the overall design process in the future, but for now wanted to start with some fairly technical posts about build automation on that project. Posts in the series: Scripting KiCad Pcbnew exports Automated KiCad, OpenSCAD rendering using Travis CI Using UI automation to export KiCad schematics OpenSCAD Rendering Tricks, Part 1: Animated GIF OpenSCAD Rendering Tricks, Part 2: Laser Cutting OpenSCAD Rendering Tricks, Part 3: Web viewer Since I’ve been designing the split-flap display as an open source project, I wanted to make sure that all of the different components were easily accessible and visible for someone new or just browsing the project. Today’s post continues the series on automatically rendering images to include in the project’s README, but this time we go beyond simple programmatic bindings to get what we want: the