OpenSCAD Tips

These OpenSCAD tips have been written for experienced programmers
who want to design complex 3D-objects with OpenSCAD.

I wrote about fifteen thousand lines of code in OpenSCAD since spring 2013.
The panoramic head Panohero was designed entirely with OpenSCAD.
I also designed some jewelry but it is not yet shown on my website.
For the benefit of other designers I want to share the insights which I have gained so far.
Have fun using OpenSCAD for your own designs and contribute to make it even better!

OpenSCAD tips – performance

F5 versus F6

Under the tab „Design“, the GUI of OpenSCAD offers „Compile“ with shortcut F5
and „Compile and Render (CGAL)“ with shortcut F6 amongst other commands.

Both of these commands can show you the result of your code as a 3D object in the related frame of the GUI.
It is good to know a little bit about the differences between F5 and F6 from a users view.

Using F5

With F5 you can have the colors of your choice.
Colors are very helpful for the design process, for debugging,
and for producing images when you want to show your design to others.
Moreover, with F5 you get a visible result much faster than with F6.
Due to these properties I use F5 for 95% of the time during the design process.
On the other hand, with F5 not all results may be shown automatically.

Depending on the circumstances, OpenSCAD may decide not to render the result
of an operation when you compile with F5.
This may happen e.g. for operations like intersection, difference, hull or minkowski.
This behaviour may easily make you wonder what is wrong with your code –
you coded something but you do not see the expected result.
Sometimes the result is initially displayed but as your design evolves,
OpenSCAD decides at some point to not display the result anymore.

The cure to such behaviour are carefully placed render-statements.
These tell OpenSCAD that you definitely want to see what you designed –
even if you compile only with F5.

Further, the compilation with F5 is not sufficient for generation of an STL-file.
You need to use F6 before exporting your design to an STL-file for production.

You will find more about this issue in other sections further below.

Using F6

With F6 no part of your design will ever become invisible due to policy decisions of OpenSCAD.
So F6 is a safe way for checking what you have designed. But you loose all color information.
And the time spent for F6 may be some magnitudes above the time spent for F5.

In any case, at some point you will need F6 –
and that is e.g. prior exporting the STL-data that a 3D-printer needs.

Intersect and subtract objects early and unite them late

I follow this rule for better speed in the rendering of my designs (with F5):
That is, I rather subtract the object D from the objects A and B individually before uniting them
than to subtract the object D from the union of A and B.

E.g. use rather

  // (A-D) +(B-D) +C
  union() {
      difference() {B(); A();}
      difference() {C(); A();}
      D();
  }

than

  // (A+B+C) -D.
  difference() {
      union() {A(); B(); C();}
      D();
  }

Exploit symmetry for speed

For efficiency, exploit symmetry by rotation and by mirroring.
This is a huge and simple source of performance improvement
as long as you work with F5.

Minkowski operator

Apply the minkowski operator with care – prefer (2D, 2D) over (3D, 3D).
Restrict the operator to those edges and corners which definitely need it.
E.g. use a pyramid instead of an octahedron as second argument
when one side does not need bevelling.

Nested Minkowski operators are almost unusable – too slow – might never return.

Bounding Boxes

You can use bounding boxes when trying to gain efficiency.
The bounding box should consist of a few faces only (like e.g. a cube),
it should cover the region which is affected by the operation
and it should exclude regions which are not affected.
Now you can restrict slow operators like e.g. difference or intersection
to a smaller region which may result in faster computation (even after
joining the missing parts).
However, do not expect too much performance gain from this.
It gave me usually only a few percent.

Fix extremely slow reactions of the GUI

Sometimes it is unfeasible to move an object with the mouse
because the respone time is too long (e.g. several minutes).
The same can even happen when you try to scroll the text output of the console window.
Such performance problems can usually be cured by prefixing the proper
difference or intersection operator with a render-statement.

Find the right place for the render-statement using divide and conquer:
Successively split off parts of your model and check the speed.
That way, narrow your model down to the lines which cause the slowness.

Performance of nested render-statements

Nested render-statements are not detrimental to performance; that is,
outer render-statements benefit from the work done in inner render statements.

However, render-statements take time – and the time might be proportional
to the square of the number of involved triangles. Therefore, use render only when needed.

Avoid render-statements for large unions

Avoid the use of render-statements for large unions – they are slow and you don’t need them.

Iit may take a long time to compute a render-statement for a large union with F5.

While a render-statement may help to make „hidden“ results visible –
e.g. results of expensive operators like intersection and difference -,
the result of a union is always displayed and it it always displayed quickly.

Typically, a well-placed render-statement makes sense in cases like this one:

  // render()                // Bad: Less performance for F5
  for(i=[0:n-1])
  rotate(i*360/n, vector)
  render()                   // Good: Make the result visible for F5
  intersection() {
      object1();
      object2();
  }

Prefixing a union with a render-statement has no visible effect besides making the processing slower.
With F5 and in the absence of a render-statement, the union will be compiled quickly,
it will be visible, and it will be displayed quickly.

Moreover, for a union, interactive reaction of the GUI to changes of the view
as e.g. controlled by the mouse is always fast –
no matter whether or not you prefixed the union with a render-statement.

OpenSCAD tips – avoid and find errors

Syntax Errors

With OpenSCAD, syntax errors can be hard to find.

  • In the presence of include-statements,
    line numbers in error messages of OpenSCAD are misleading.
    The reason is, that OpenSCAD counts also included lines
    while your editor does not because it is not aware of includes.
    To search for a syntax error in this situation, first comment out all includes.
    This will cause new semantic errors but it will not mask the wanted syntax error –
    unless the included files contain additional syntax errors.
    The line number given in the error message will be more meaningful,
    when the includes have been commented out.
    Reactivate the includes once the bug is found and fixed.
  • Use block comments to comment out suspicious parts of code.
    When this makes the error disappear, than you know where to have a closer look.
  • Watch out for missing or misplaced commata, semicolons, and braces.
  • Avoid braces around code with an assignment.
    Doing so will surprise you with a syntax error.
    Ordinary assignments work within the braces of a module
    but not within any other pair of braces.
    Prefix other braces with an assignment in parentheses instead.Example: Assume you want to color these objects:

      x=...; obj(x); ... y=...; obj(y);
    

    and to do so you do this (which causes a syntax error):

      color("green") {x=...; obj(x); ... y=...; obj(y);}
    

    Fix 1, destroying locality:

      x=...; ... y=...; color("green") {obj(x); ... obj(y);}
    

    Fix 2, preserving locality:

      color("green") {assign(x=...) obj(x); ... assign(y=...) obj(y);}
  • Consider to move some of your core functionality into a few basic modules.
    Equip these important modules with routines for argument checking and for error output.
    For error output, make use of the fantastic feature of dumping a stacktrace.
    This tells you rather precisely where the problem occured in your dynamically nested modules.
    This may help to find errors more quickly because additional meaningful output is available
    and because less time is lost for insertion and removal of echo statements for debugging.
  • Consider to use a separate editor with syntax highlighting
    (I use vim with a syntax file for „.scad“ files).
    Set the option „view | Hide editor“ of OpenSCAD
    and use the key <Ctrl>-4 for recompiling.
    This works great for me.

Fix an „invalid 2-manifold“

The error „invalid 2-manifold“ is usually caused by a pair of touching objects.
Objects should either be separate or they should intersect. These are the possible cases:

  • The objects are separate, that is they do not have any point in common – this  is okay.
  • The objects touch in a point – this will cause the above error.
  • The objects touch in a line – this will also cause the above error.
  • The objects touch in a face – this  is okay in most cases but in certain cases it may cause the above error.
  • The objects intersect in a volume – this  is okay.

To find the source of the above error,
remove objects from the model and narrow down the design to the troublesome couple.
Use comments and the asterisk modifier (*) for this.
Consider to move an involved object by some fraction of a millimeter to fix the problem.

Recompile often

Fresh errors due to limited changes of code are much easier to find.

Calls with named parameters

Named parameters can cause strange effects which are hard to debug.
In a call, never use positional parameters after named parameters.

OpenSCAD tips – maintainability

Organize your code in lots of modules

Modules are easy to test individually while sections of long code are harder to test.
Moreover, modules are easier to reuse and to improve than multiple copies of the same code.

Make use of nested modules

Inner modules can read variables of outer modules.
Nested modules can help to keep variables and code closer together.

OpenSCAD tips – get it done

Functions: Use parameters instead of variables

Within a functions it is often desirable to assign a value to a variable
to be able to reuse that value later in multiple places.

Since assignment of variables is not supported in functions,
you have to use nested function calls with separate arguments instead.

This is best demonstrated with an example.
To reuse the discriminant of a quadratic equation in two places,
we pass it on to a nested function.
To avoid duplication of the final formula for the result,
we introduce another nested function:

  // Solve the equation a*x*x +b*x +c = 0 for x.
  function roots(a, b, c) = 
    _roots1(a, b, c, b*b -4*a*c);

  function _roots1(a, b, c, d) = 
    [_roots2(a, b, c, +sqrt(d)), _roots2(a, b, c, -sqrt(d))];

  function _roots2(a, b, c, sqrtd) =
    (-b +sqrtd) / (2 *a);

  echo(roots(1, -3, 2));	// => [2, 1]

This example is meant to illustrate the concept of replacing variables by parameters –
it is not meant to give the best possible solution for solving quadratic equations.

Functions: Use recursion instead of assignment loops

Do you need a function which modifies a variable in a loop?
Do you think that this is impossible with OpenSCAD?

Indeed there is a way to achieve the desired behaviour in OpenSCAD:
You have to replace the variable with a parameter
which is passed to a recursive function call.

This technique is a rather straightforward enhancement of the preceding tip.

„Return“ results from a module

Assignments, loops and conditions are handy to compute results
which depend in a complex way on the arguments.

But alas, in OpenSCAD such results can not be returned from a module,
because a module returns exclusively a list of objects as its implicit result.

On the other hand, while a function allows you to return results to the caller,
you can not really use assignments (except with the recursive approach)
and loops in a function.

One solution to this dilemma is to make all required calculations global.
Then the needed results are globally available too.
However, this spoils your code in several ways:

  1. You are forced to make all intermediate results global.
    This requires that you find globally unique names for all your intermediate results.
    That way you need longer and longer global names for lots of rather irrelevant variables,
    you run more easily into semantic name clash errors which OpenSCAD does not report
    and your lines of code get longer and less readable.
  2. You loose structure. You can not syntactically tie intermediate variables to a local scope.
    This makes your code less readable and less maintainable.

However, there is a way which allows you to combine syntactical locality of intermediate results
with global availability of end results, and that is the use of selector arguments
like e.g. in

  mod(pos=[1,2,3], part="top_face") {
      h1=...; h2=...; h3=...;
      if(part=="top_face") ... else ...
   }

Exploit the power of matrices

In which point does a line intersect a plane?
In which points does a line intersect a sphere?
In which point do three planes intersect?

In parameterized models it is often desirable to answer such questions by calculations in OpenSCAD.
It is not obvious that such calculations are possible at all in OpenSCAD because there is no direct support for them.

Luckily, you have matrices and vectors available and you can add and multiply those in every usual way –
e.g. scalar product, cross product, matrix product etc.
Moreover, OpenSCAD gives you the great operator multmatrix to transform an object with a 4×4 matrix.

Transformations with multmatrix cover translation, rotation, scaling, and shearing.
However, they do not cover perspective transformations.
Perspective transformationsare are possible with 4×4 matrices in general
but multmatrix does not cover the full generality of 4×4 matrices.

When you are familiar with the calculus of matrices,
then the presented features give you everything you need to support the above calculations.
It definitely requires some work to provide a related set of functions
but doing so may greatly enhance your possibilities.

Missing Features

The minkowski operator is by far not as general as it could be. It does not support these cases:

  • generate a 2D face from two non-collinear vectors
  • generate a 3D volume from two non-planar 2D-objects
    (this would require the 3D-rotation of 2D-objects amongs other things)
  • It can not be applied to a 2D-object with holes
    (but amazingly it works for a 3D-object with holes)

WARNING: minkowski() and hull() is not implemented for 2d objects with holes!

  • Was andere über Herdima sagen

    „The Panohero is a great tool, it works wonders. Thank you and félicitation the inventor of panohero!“

    Antoine Perez (living in France)