Generating a procedural solar system with Blender’s Python API

Did you know that you could program in Blender to automatically create 3D objects and animations?

This article was originally posted on Demando’s blog; it is also available on Medium!

Blender is a well-known piece of software for 3D modelling, sculpting, texturing, animation and more! As the versions kept on coming out, this tool has slowly earned its place in the CGI industry, so much so that there are now a few long-feature films that are made entirely using Blender and that there are Youtube channels like Blender Guru entirely dedicated to learning the ins and outs of this soft…

… while it is completely free and open-source!

This has introduced a profound change of mindframe in the world of 3D because it showed people that anyone could try and have a go at this form of art and that it could achieve pretty incredible results.

So, yes – at first, CGI software is primarily aimed at artists. But Blender, just like its competitors, also has another side for developers: a way to program your 3D scenes.

Today, I want to show how this programmatic approach to Blender allows you to instantly create a basic solar system like this one:

If you want to get the code directly, it’s available as a Github gist 🚀

Are you ready? Then let’s dive in! 🙂

The Blender Python API

Ok, now: how does Blender allow us to “program a scene”? Via its Python API.

You might be wondering why it’s interesting to have a Python API for a 3D soft. I mean: why embed a creative tool with stuff for coders?

There are, in truth, lots of use cases where it can be useful to automate a task: whether you want to quickly randomise your scene population algorithm, count objects and get custom stats on your scene or even create an entire world from scratch that can be reproduced accurately with seeds… having a way to integrate procedural generation or tailor-made tools directly into a CG context is just an amazing opportunity!

Moreover, the fact that it’s in Python, a language famous for being easy-to-learn and for which there is just an endless number of tutorials on the net, makes it neat for beginners to dive in (without the fear of old C/C++-based APIs that required, in my opinion, higher coding skills…).

By the way: this Python API is not just a shiny toy for wannabe-devs: it’s actually part of the Blender soft itself and it’s used internally by the program as a core tool, although the user inputs and the results are wrapped in a user-friendly UI 😉

The API in itself, bpy (for “Blender Python”), can be browsed on Blender’s specific docs; it is subdivided in several submodules, the 3 most important / commonly used being:

  • bpy.context: it contains getters and readers on read-only values that describe your current working context or even the area (i.e. the panel in your window) that is currently being accessed
  • bpy.data: it gives you access to the resources in your scene (the objects, the materials, the meshes…) so you can load, add or delete them
  • bpy.ops: that’s the real meat of the API – it’s what allows you to perform actions and call operators on your objects or your views; it’s basically how you can simulate user actions via scripting (like selecting an object, entering edit mode, applying subdivisions, changing to “flat” shading, maximising a window…)

Today, we’ll focus on generating objects thanks to this API, so we’ll be doing some procedural generation. I’ve already discussed the benefits of this approach in other articles; roughly put, procedural generation is about defining a set of rules and a machine that uses those rules to automatically create valid instances. The big advantage is that, once you’re done making the engine, you can create as many instances as you want! Moreover, this generation can be pretty quick, dynamic depending on given conditions and infinite (for example for never-ending games like runners). But, of course, it’s usually a bit harder than hand-design because you need to teach your software the right and wrong patterns (i.e., the “rules”).

Procedural generation is a very vast and complex topic. There are plenty of tools for generation engines – and to be honest, a large amount relies on randomness. The idea is to start off with random values and then somehow “control the craziness” so it is valid.

Spawning and animating a random solar system!

To see how powerful Blender’s Python API can be and how to use it, let’s work on a basic example: instantiating a bunch of planets around a sun (all being simple spheres) with random size, speed and colour, and having the planets rotate around the sun along circular orbits.

Now, let’s be clear: this will not be a physical simulation. We will pick all of our values at random and there will be no logical relationship whatsoever between the distance to the sun, the radius and the surface colour of the planets. The goal here is just to play around with Blender’s Python API, to see how to do some procedural generation and to have fun with glowy spheres 😉

Step 1: Preparing our Blender

First things first: let’s see how to setup our Blender for Python and learn the different tools we have at our disposal.

Note: this tutorial uses screenshots from Blender 2.93. Also, I will be using the Python API from Blender 2.8+, so make sure your version matches 🙂

When you want to work with Python in Blender, something that is quite useful is to start your Blender with the console attached. This will allow you to see some logs if your Python script errors, which is pretty essential for proper debugging!

Basically, the idea is to launch Blender from your terminal rather than from your Application shortcut. The exact path to your Blender executable depends on your OS; I’m using Mac, so all I have to do is open a terminal and then run something like this:

/Applications/Blender.app/Contents/MacOS/Blender &

From that point on, whatever outputs my Python scripts give me will show up in this console 🙂

Now, to get ready for coding, simply go to the “Scripting” tab at the top of your Blender’s window:

This will give a set of new panels in your workspace, namely:

  1. the 3D view: it’s just like you’re usually getting started screen in Blender – you’re seeing your current scene in 3D, in “solid” shade mode, and you can select your various objects as usual
  2. the Python interactive console: that tailor-made Python console is incredibly useful to browse the Blender API and explore all the fields and methods the Python Blender objects provide us with; in addition to the basic Python built-ins, this console also comes loaded with Blender’s built-ins and a few convenience variables to easily access the Blender API common submodules
  3. the Info panel: it might seem strange at first, but this panel just debugs everything you do – which is yet again great for discovering the possibilities of the API! If you try and select an object, or move your light, or any other action that you’re used to doing in Blender, you’ll see it is logged in this panel and so you can understand which part of the API you should access to reproduce this action via scripting
  4. the Text Editor: you might have guessed – it’s the main course for us today; this is where we’ll create, edit, save and run our Python script. It is a simple Python editor with syntax highlighting, line numbers, and even a few utilities like “Find & Replace” if you open the sidebar (with <Ctrl + T>, or <Cmd + T> on Mac). Note however that this editor is limited in terms of features so if you plan on making a big thing, it’s probably better to use a nice external IDE and then load your script in Blender when it’s ready
  5. the Outliner: that’s another common view you likely already know that just shows the hierarchy of objects in your current scene and allows for quick access
  6. the Properties panel: similarly, this panel is the same as in the common layouts – it’s where you can view and edit the properties of your scene, your render parameters, your world settings and the specific data for your currently selected object

To check that everything is working, simply click on the “+ New” button of the Text Editor, write a basic print('hello world') line in the Python script and click the “run” icon on the right (or use the shortcut <Alt + P>; make sure your mouse is hovering the Text Editor panel for this to work).

If everything is setup properly, you should see “hello world” appear in your attached terminal!

Step 2: Creating a basic mesh via the API

Alright! Time to get real and instantiate some objects in our scene using Python 🙂

To begin with, delete all the initial objects: the cube, the camera and the light. We won’t be using those.

Now, let’s edit our Python script to create a new sphere object. This is done using the bpy.ops subpackage and more precisely the mesh.primitive_uv_sphere_add() method:

The parameters are pretty self-explanatory: we are creating a sphere with a radius of 3 at the origin point, with a normalised scale. (Feel free to browse the docs for more details on the available options)

Once again, run your code by clicking the “run” icon or with the <Alt + P> shortcode. And tadaa! We’ve just created a simple UV sphere in our 3D scene purely via scripting! 😉

Step 3: Instantiating our planets

We’re now able to spawn one sphere – so let’s see how easy it is to spawn several!

What we want to do is:

  • to create N_PLANETS objects, each being a simple UV sphere
  • have every object use a random radius within a given range
  • and a random distance from the origin (where we’ll eventually place our sun) that depends on the index of the planet (so that their orbits are nicely spread)

Making N_PLANETS instances is straight-forward: we’ll simply wrap our primitive_uv_sphere_add() call in a for-loop and run it multiple times. To keep the script readable, we’ll actually extract this instantiation process into a util function, create_sphere(), and we’ll pass it in the random radius and distance.

To get a random value for our radius and our distance, we can rely on the Python built-in random module.

Also, we should name our objects properly: this is interesting to keep our hierarchy clear and it will be essential when, later on, we automatically clean up the scene upon script initialisation. I’ll simply call each object “Planet-00”, “Planet-01” and so on, for the N_PLANETS spheres.

Here is the updated script:

If you run this again, you should get a little set of well-aligned planets with various sizes that are all named in the right format:

Step 4: Adding the sun and the radius rings

In a similar way, we can re-use our create_sphere() method and make another one, called create_torus(), to add a sphere for the sun and some torus objects to show the planet orbits:

The sun is of course bigger than the planets, and the rings are simply placed at the same time as the planet spheres, using the distance to the origin for the torus radius parameter:

Step 5: Setting the materials and the shading for our objects

This is great, but those objects are a bit morose, all gray and blocky. It’s time to work on two important 3D concepts: the shading and the materials of our objects!

Generally speaking, the shading refers to how an object reacts to light and is drawn in the 3D view or in a render. Here however, I’m focusing on the “smooth” versus “flat” shading, which determines how faceted an object appears. It’s basically a second layer that further impacts how the object is rendered, but doesn’t change its actual geometry: it just looks smoother.

Setting the objects to use the “smooth” shading is really quick to do: we have a dedicated shade_smooth() method in the bpy.ops submodule:

The real question here is: how can we give some colours to our planets, and how could we make our sun glow?

The answer is: thanks to materials and shaders! 🙂

What are materials and shaders?

Both those terms can be used to talk about “the palette of properties that define how an object gets rendered”.

Roughly put, shaders are what give 3D objects their colour, their shininess, their glossiness, their roughness… Think about it: in the real world, what makes you say that “hum, this is wood” is just a whole set of clues: the object is some tint of brown, it reflects light a bit but not with sharp light spots like metal, it doesn’t refract rays like glass and it doesn’t reflect your image like a mirror… Well – with the ever-improving CGI technology, all of these properties can now be modelled and reproduced in our 3D scenes!

Note: when you’re really aiming for realistic renders, you’ll probably have to dive into physical-based rendering, or PBR. And that’s what Blender’s Cycles engine is for: creating amazingly lifelike pictures that just “feel” real, mostly thanks to a bunch of complex and well-tweaked shaders.

In Blender, shaders are usually edited via the node-based graph editor (available in the “Shading” tab) that lets you chain and combine as many built-in nodes as you want to build more or less complex shading flows. In this tutorial however, we’ll make a super simple shader that only has one node, so we’ll do everything in our Python script 🙂

Materials then use those shaders and apply them to your 3D geometry. An object may have several material slots, i.e. it can use different shaders for different parts of the geometry, but we won’t get into that today, and we’ll stick with one material slot per object.

Picking our shader type

Ok, so – what shader should we create?

For this project, I’m using the EEVEE engine that can also work with shader nodes, even though it doesn’t have all the same node types as the Cycles engine.

But it’s fine because, here, what I want is one that exists in both: the Emission shader. You can picture as a big lightbulb that has an intensity (the “strength” parameter) and a colour. It will make your 3D object emit light (so your object will become a light source in your scene that we’ll interact with the rest of the meshes!) and essentially make it “glow” 🙂

Creating a shader in our Python script

To create and assign a shader fully via scripting, we have to:

  • create a new shader resource using the bpy.data submodule
  • “edit” this shader as we would with the shader node editor: we’ll remove some nodes, add others, set their properties and link them together
  • retrieve the reference to our newly created material
  • and finally add it to the materials data (i.e. the slots) of our object

Let’s work on this step by step. We’ll start by making a new function called create_emission_shader() that will receive some strength and color parameters, and that will use those to setup a basic 2-nodes graph with an Emission node and an output node.

The idea is to start from the basic node template and clear all the starter nodes; then, we can add in our emission and output nodes, configure the emission node by updating the values of its input fields and create a link between the two nodes:

It’s now pretty easy to use this method to create our material resources and apply them on our objects. We’ll need one white emissive material for the rings, one yellow emissive material for the sun and one emissive material per planet with a random colour (though I’ll add in more blue for a better overall colour balance 😉 ):

If you change the shading mode in your 3D view to “Rendered”, you delete all the objects currently in the scene and you run the script again, you’ll see that they now have nice glowy materials on them!

Note that after you’ve run the script, you can even go to the “Shading” tab, select an object with a shader and see the shader graph:

It looks as expected: two nodes, one Emission and one Output, a link between the two, and some custom values for the “Strength” and “Color” properties.

Note: when you initially open the panel, nodes will be all packed together in the middle on top of each other. Here, I moved the nodes manually for the demo but you can actually do it in your code, too, with the .location property.

Step 6: Animating the planets

We’re getting close to having our final “solar system generator”! The last thing we need to take care of is animating the planets so that they rotate around the sun as time passes by.

To do this, we’ll use Blender’s animation curve system (the F-curves). It’s a more advanced version of keyframed animation where you specify the key values for one or more properties of your object over time (e.g. you force its position, rotation, scale… at a given frame) but also the interpolation between them.

Here is an example of the same set of key points with the three possible interpolations (from left to right: constant, linear and bézier):

You see how the interpolation affects the intermediary auto-computed values (all the segments in-between the keyframes that we defined manually) and how that impacts the overall evolution of this property for the object. Suppose this line represents the altitude of a little helicopter:

  • with constant interpolation, the helicopter just keeps on diving up and down, then going straight ahead with little plateaux
  • with linear interpolation, the helicopter slides from one point to another with sweet diagonals, but it still changes direction quite suddenly when it reaches extrema
  • with bézier interpolation, the helicopter slows down before reaching those valleys and ridges and smoothly updates its course

The “best interpolation” depends on the type of animation you want. In our case, we should pick the linear interpolation so that planets just move at a regular pace along their orbits.

To create some animation for our planets, we just need to use the animation_data_create() method and the animation_data field of our objects to create and then edit the F-curve for the Z-rotation property. This property is actually part of a 3D-vector property of the object called the “Euler rotation” (more on Blender rotation modes here) where the Z-axis is the third component, i.e. the one at index 2 (because the components are 0-indexed).

Once we’ve grabbed this property, we’ll simply add two keyframes: one for the start frame (with our current rotation of 0) and another for the end frame (with a random rotation of one or more semi-circles around the sun, so that the planets have different speeds). And we’ll make sure that those keyframes use a linear interpolation mode 😉

Note: all rotations must be written using radians.

If you clean up your scene and re-run the script, you’ll see that… nothing happens! The planets aren’t moving, even when you play the animations!

And that’s because, in truth, they are rotating… but not around the right pivot! For now, the planet are simply revolving, they are rotating around their local Z axis.

To fix this, we just need to change the pivot point of our objects and snap it back to the cursor that is at the world origin point (the same location as our sun):

Now, if you hover the 3D view and hit the spacebar, time will start flowing and the animations will play, making our planets revolve around the sun! 🙂

Step 7 (Bonus): Auto-cleaning the scene on script init

After we’ve run our script, we have created two types of resources in our scene: the 3D objects for the planets, the sun and the orbit rings; and the dynamic materials for the planets.

Getting rid of this is pretty simple: we just need to use the bpy.data submodule to go through our objects and our materials, check for the names and remove the ones that we created while running the script:

Now, you can run the script as many times as you want: each time the scene will first be cleaned up so that you get only one instance of our “solar system” 🙂

Step 8 (bonus): Auto-setting the scene properties, the render engine and the 3D view settings

For nicer visuals, with a little bloom effect, a dark background and no grid or X/Y-axis, you can even add the following snippet at the beginning of your script to setup the scene with nice settings:

Conclusion

Blender is not the only 3D soft that allows you to program your scenes and automate tasks; but it is living up to the expectations and, with each new version, Blender is gradually becoming a credible all-in-one solution for CG production, from storyboarding with Grease Pencil to node-based compositing.

The fact that you can use Python scripts with just a few additional packages to batch your object instantiation, generate stuff procedurally, setup your render settings or even get custom stats on your current project is great! To me, it’s a way to lighten the load on tedious tasks but also to bring developers to the party to extend this creative tool community beyond artists.

In this post, we’ve seen that with less than a hundred lines of Python, we can create a basic procedural solar system with dynamic and randomised meshes, materials and animation!

I hope you’ve enjoyed this quick peek at Blender’s Python API – and of course, feel free to comment and tell me if you have ideas of other nice visualisations we could create thanks to this tool! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *