3d Game Programming with Java and libGDX -3d Model Generation with OpenSCAD (‘drilling’ holes)
Or… how to programmatically (as opposed to visually) generate a 3D Model using using OpenSCAD to export a usable model.
TL;DR¹
For those that want to accurately generate a model programmatically, OpenSCAD is an excellent choice. You will need to be able to do some simple programming, with possibly a bit of logic, but there is a fast feedback loop, and debugging mode to make sure that it all looks correct.
Note: this is part of a series of articles: See 3d Game Programming with Java and libGDX — Overview of Articles
What You Will Need
- Download OpenSCAD for your platform (it supports MacOS, Windows, and a variety of Linux distributions— at the time of writing it is version 2021.01.
- The OpenSCAD cheatsheet open in a browser — which has a quick overview of all the commands (although there are tooltops to help you along)
- Download blender for UV mapping exports and materials.
Why Use OpenSCAD
I, (and possibly you) may want to expand the range of tools that you use to generate a 3d model, choosing the right one to do the job.
From the OpenSCAD about page (emphasis mine):
OpenSCAD is software for creating solid 3D CAD models. It is free software and available for Linux/UNIX, Windows and Mac OS X. Unlike most free software for creating 3D models (such as Blender) it does not focus on the artistic aspects of 3D modelling but instead on the CAD aspects.
So, for some use cases, this is a perfect tool to generate 3d models.
Pros:
- It is free, simple, actively maintained and it does exactly ‘what it says on the tin’.
- The ability to do a ‘difference’ between 3d shapes is straight-forward in OpenSCAD (this use case is particularly nice when I was trying to ‘drill a hole’ through something in blender’).
- It has good documntation on the functions.
- It is text based, allowing me to source code control the code that I have written.
- It is programmatic — I get to control every dimension for every part.
- It has great debugging controls so I can see where I am going wrong (and right on the odd occasion).
- You can use modules and imports to split up your files so that they don’t get too confusing — which, paradoxically can get confusing if you break up files too much.
Cons:
- OpenSCAD does not support materials, meaning that another tool would need to be used to apply these (Blender is used in this example).
- The code part can get very complex very quickly, which can make it hard to determine at what point things are being done.
- The Integrated Development Environment(IDE) is a little clunky and lacks support for some nice features (e.g. Renaming variables, easily commenting out sections, tooltips can sometimes disappear at the most inopportune moment).
- Where you need to put semicolons ‘
;
’, or leave them out can be confusing until you figure out what the 3d rendering pipeline is attempting to do — and the commands that need to be done in order.
In This Guide
I will be showing you how to ‘drill’ holes through things easily which can then be used by LibGDX, after applying materials and UV mapping in blender.
What do I mean by drililng holes?
Let me show you an example: I wanted to generate this — and with Blender (despite my best efforts with tutorials) it was too difficult/time-consuming and just didn’t really work well. OpenSCAD makes this super-straightforward — in about 20 lines of code and about 5 minutes of effort.
For those that are interested in the above code — here it is:
$fn=50; // set the number of fragments
color("Gainsboro", 1.0) // metallic greydifference() {
// outer shell with beveled edges
intersection() {
cylinder(h=10, r=4, $fn=6);
cylinder(h=10, r=3.9);
} // inner cylinder to be removed
translate([0, 0, -0.5])
cylinder(h=11, r=3); // all of the holes drilled through the outer shell
for(i = [0:60:360]) {
rotate([0, 0, i])
translate([3.4, 0, -0.5])
cylinder(h=11, r=0.25);
}
}
First Steps
Understanding the IDE
After you have downloaded and installed OpenSCAD, when you open the application, you will be presented with this screen:
Click on new and the IDE is presented:
- File menu — saving, loading, and most importantly the rendering button (although every time that you save the code, it re-previews automatically).
- Code editing area (IDE) — where all of the code is typed.
- Rendering area — Where the 3d model is previewed/rendered.
- Rendering and view options —Preview/Render buttons, and ways to change the view on the rendering area.
- Console and error log — Useful when things go wrong to determine what has happened. Code parsing errors will also be highlighted in the ‘Code editing area (IDE)’
Set up your environment
Go to File
-> Preferences
Make sure that 3D View
is selected and that Nature
is selected as the Color scheme:
. This will match the examples in this article — as opposed to the default yellow background.
A quick example
To get started, we are just going to generate a simple cylinder. Start typing:
cylinder(
OpenSCAD provides tooltips — which is a nice touch, you should see this:
Where:
h
is the height of the cylinderr
,r1
,r2
are the radii — you only needr
if you are creating a cylinder,r1
andr2
for a coned
,d1
,d2
are the diameters — you only needd
if you are creating a cylinder,d1
andd2
for a conecenter
=true
/false
— whether you want to centre this about the origin (I personally recommend not doing this — i.e. leave the option out, or explicitly set it tocenter = false
)- See the OpenSCAD documentation for full options for a cylinder
Just filling in the details, I used:
cylinder(h=10, r=5, center=false);
click on the preview button (note that the render button to the right of it removes all colour options from the final rendered model):
So a few things:
- Use the mouse wheel to zoom in/out of a model
- Right mouse button to pan
- Left mouse button to rotate
- Don’t forget to save your work
The cylinder that is generated is a green colour, and the number of faces isn’t as smooth as it could be, so let’s quickly fix these two things.
- let’s change the colour, by putting in a simple line before the cylinder
color(“red”, 1.0)
Note: that there is no semicolon ‘;
’ after the command — this goes into the render pipeline and is reset after every render. - Let’s increase the number of faces for the cylinder as well, using a special variable
$fn
— which is the number of fragments to render with. (OpenSCAD documentation on special variables). I am using the value of50
which balances smoothness versus render time — remember that the more fragments that you have, the more UV unwrapping you will have to do, and the more processing that will need to be performed by the GPU to render it.
At the end of this, your code should look like this:
color("red", 1.0)
cylinder(h=10, r=5, $fn=50, center=false);
Let’s ‘drill’ a hole through the red cylinder
We want to create a cylinder the we can then ‘take away’ from the red cylinder, so we will create another cylinder.
color("red", 1.0)
cylinder(h=10, r=5, $fn=50, center=false);cylinder(h=10, r=4, center=false);
Two things to note here:
- The color has been reset to dark green — remember that I mentioned that the
color
command would reset after the execution of the pipeline. - The cylinder is only partially visible — this is because there are some slight floating point arithmetic conflicts between the two cylinders, by which I mean that when the GPU calculates which of the cylinders should be shown, sometimes it comes up red, and sometimes it comes up green.
‘Drilling’ the hole
We are going to use the difference function to subtract the green cylinder from the red:
difference() {
color("red", 1.0)
cylinder(h=10, r=5, $fn=50, center=false); cylinder(h=10, r=4, center=false);
}
As from the above — you can see that this is not looking correct.
- The two ends of the green and red cylinders are overlapping.
- The green cylinder does not have as many faces as the red cylinder (a little bit hard to see.
To fix this we are going to:
- Set a default number of fragments for rendering of every every object that goes through the pipeline.
- Offset the green cylinder a little bit so that the difference can be shown.
By setting the special variable $fn
at the top of the file, and removing it from the red cylinder function call, this will set the default for all object renders — however you may override this value on each call by including the $fn
special variable in the cylinder()
call — and yes you have to include the dollar sign ‘$
’ before the fn
.
The code now becomes:
$fn=50; // set default number of fragments for all objectsdifference() {
color("red", 1.0)
cylinder(h=10, r=5, center=false); // removed $fn here cylinder(h=11, r=4, center=false);
}
And the result:
So we have fixed the top rendering artefacts, but not the bottom. Thankfully OpenSCAD has a debugging feature to see what is going on. To see what part of the green cylinder is being taken away from the red cylinder, put a #
in front of the respective command — this indicates that the command will be in debug mode — thusly:
$fn=50;difference() {
color("red", 1.0)
cylinder(h=10, r=5, center=false); #cylinder(h=11, r=4, center=false); // debug command
}
You will notice the bottom still has the artefacts, so we shall fix that next, with the debugging left on so that we can see what is going on.
We shall fix this problem by shifting the green/debugcylinder down a little bit through the translate
function.
Translate moves the start point of the positioning of the object. You will note that I have set the height of the green/debug cylinder to 11
(h=11
) so there will be an overlap (the red cylinder is only 10
units).
Now I am going to move the green/debug cylinder down 0.5
units along the z-axis (half of the height so that there will be a definite ovelap). The z-axis by default runs from top to bottom of the screen.
Here you can see that there is overlap:
$fn=50;difference() {
color("red", 1.0)
cylinder(h=10, r=5, center=false); translate([0,0,-0.5])
#cylinder(h=11, r=4, center=false);
}
Note the translate([0,0,-0.5])
line above (the function call is translate([x, y, z])
) which moves the start of the cylinder down 0.5
units before rendering and does not have a semicolon after it — this designates it as a pipeline rendering command for the next function call.
Cleaning up the code and setting the colour to red gives us the final render:
The final code:
$fn=50;color("red", 1.0)difference() {
cylinder(h=10, r=5, center=false); translate([0,0,-0.5])
cylinder(h=11, r=4, center=false);
}
Now to Export…
Now to export the finished model so that it can be manipulated through blender. When you render the image all colour information is lost —which will need to be added back in through Blender (or some other tool).
- First
Render
the model (either pressing theF6
key, Using the menuDesign
->Render
, or clicking on theRender
Icon) - Export the Model as STL from the menu
File
->Export
->Export as STL…
(or just press theF7
key)
Import the Model into Blender
- Load up Blender
- Import the STL file
File
->Import
->Stl (.stl)
There is no step 3 — now you will need to do the UV mapping, adding a texture, and away you go.
See the 3d Game Programming With Java and libGDX — Setting Up a Model With Blender article for details.
Final Thoughts
If you are programmatically minded, OpenSCAD can be an easy way to generate models very quickly. There is a raft of functions available which means you can generate a huge amount of models with exact measurements without having to point and click around an interface like Blender. That being said — Blender is a powerful tool, with far more options than something as simple as OpenSCAD, and, should you have invested the time in learning Blender — it may be simpler for you to do it, and I would love to see an article on how to do it easily in Blender.
As you get more comfortable with OpenSCAD, and you start to refactor your code, it does become more complex, and harder to read, and the logic between union()
, difference()
, and intersection()
can make your head swim. For eaxmple, adding a slight bevel to the end of the mechanical pencil shaft ended up looking like this:
$fn=50; // set the number of fragmentsheight_of_shell = 10; // height of the outershell
offset = 1; // offset of the 'drill' cylinderscolor("Gainsboro", 1.0) // metallic greyintersection() { difference() {
// outer shell with beveled edges
intersection() {
cylinder(h=height_of_shell, r=4, $fn=6);
cylinder(h=height_of_shell, r=3.9);
} // inner cylinder to be removed
translate([0, 0, -0.5])
cylinder(h=height_of_shell + offset, r=3); // all of the holes drilled through the outer shell
for(i = [0:60:360]) {
rotate([0, 0, i])
translate([3.4, 0, -(offset/2)])
cylinder(h=height_of_shell + offset, r=0.25);
}
} union() {
translate([0,0, 0.5])
cylinder(h=height_of_shell, r=3.9);
translate([0,0, -4.5])
cylinder(h=5, r1=2, r2=4);
}
}
Which gives this result:
Whilst there where only an additional 10 lines of code, the mental gymnastics took a while to work through.
Colophon
This article came from my frustration in designing the ‘ultimate mechanical pencil’ with blender. Just trying to do simple things became frustrating, so I searched around for other tools.
Hello: If you enjoyed this article, the “clap” — 👏 — button below awaits you. Want to know a secret? You can press and hold it, so it goes up to 50 which is their version of a standing ovation.
Read more of what you love — Sign up to Medium — it is FREE, easy, and quick.
Footnotes:
¹ a TL;DR section is included in every article, so that the main points are highlighted, you get to decide whether this story is of interest, and quickly move on if there is nothing there for you [source: Too long, didn’t read — TL;DR]