Meshes with Python & Blender: The 2D Grid
Procedural generation is awesome! In this tutorial series we’ll look at making meshes using Blender’s Python API.
Creating meshes programmatically opens many possibilities. You can create parametric objects that respond to real world settings, generative art, math based shapes or even procedural content for games. Blender is a great choice for this kind of work since it combines a full blown modeling and animation suite with a powerful (and fairly well documented) Python API.
In this series we will be looking at making several primitives and some basic transformations, I’ll also drop some tips along the way to make development easier. I’m assuming you already know some basic Python, and enough Blender to get around. For this introduction we’ll skip the 3D and focus on making a simple flat grid instead.
Pro Tip: Save often while working on meshes! You will make Blender crash more than once 🙂
Tutorial Series
- Part 1: The 2D Grid
- Part 2: Cubes and Matrices
- Part 3: Icospheres
- Part 4: A Rounded Cube
- Part 5: Circles and Cylinders
Setting it up
The Blender data system makes a distinction between mesh data and objects in the scene. We have to add a mesh and link it to an object, and then link that object to a scene before we can see any results.
Let’s start by importing bpy (surprise!) and setting up some variables.
import bpy
# Settings
name = 'Gridtastic'
rows = 5
columns = 10
Name
will be both the mesh and object’s name, while rows
and columns
will control how many vertices the grid has. Next we set up the mesh and object adding part. We have to add a mesh datablock first, then an object that uses that mesh and finally link them to the current scene. I’ve also added some empty lists for verts and faces. We will be filling those later.
verts = []
faces = []
# Create Mesh Datablock
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)
# Create Object and link to scene
obj = bpy.data.objects.new(name, mesh)
bpy.scene.collection.objects.link(obj)
# Select the object
bpy.context.view_layer.objects.active = obj
obj.select = True
The most interesting part is from_pydata()
. This function creates a mesh from three python lists: vertices, edges and faces. Note that If you pass a faces list you can skip edges. Check the API Docs for more info on this function
Try running the script now. You will see a new object has been added but there’s no geometry to be seen (since we haven’t added it yet). With the basic scaffolding done, delete that object and read on to start building the grid.
A grid of vertices
Let’s begin by adding a single vert. Vertices are represented by 3 coordinates (X, Y and Z). Our grid will be 2D so we only care about X and Y, Z will always be zero. We will place the vert in the center of the scene, which is also the origin for global coordinates. In other words, at coordinates (0, 0, 0). Each vertex is a tuple of 3 floats, so modify the list like this:
verts = [(0, 0, 0)]
Try running the script again and you will see a lonely dot. You can actually go into edit mode now and play with it. Lets ramp up and make a row of verts. To do this we need to loop and add as many vertices as the number of columns we set up. This is really easy to do with a list expression:
verts = [(x, 0, 0) for x in range(columns)]
range()
returns integers, so the vertex X coordinate will be their column number. That means each column will be 1 Blender Unit (or meter) wide. Run the script again you will see 10 vertices lined up on the X axis. To complete a grid all we have to do is more rows. We can easily extend the expression to loop through rows as well:
verts = [(x, y, 0) for x in range(columns) for y in range(rows)]
Now we can see the vertex grid in all it’s glory.
We can start making faces now, but first we need to understand how.
Understanding faces
Each vert we added has an index number. Indices are set as we add each vert, so the first one is 0, the second is 1 and so on.
To make a face you need to add a tuple of indices to the faces list. This tuple can be 3 (tri), 4 (quad) or more (ngon) indices. These are all integers by the way. Blender won’t fail or complain, but it will round them. Since we are making quads we will need to find four vertex indices for each face. But how? You could try guessing them, but there’s a better way. We can enable debug mode in Blender. Open a Python Console in Blender and type the following:
bpy.app.debug = True
If you get anything from this tutorial I hope it is debug mode. It’s the most useful setting you can have for mesh making and even general addon development. To see the vertices indices, select the 2D vertex grid and go into edit mode. Open up the N‑panel and toggle “Indices” in the Mesh Display Panel. It’s under the “Edge Info” column. If you can’t see it, you might not have enabled debug mode yet. Now any vertex you select will show it’s index, so select them all to see their indices.
Let’s focus on the first face. As you can see it’s made up of vertices 0, 1, 5 and 6. Let’s try making a single face with those indices:
faces = [(0, 1, 5, 6)]
Try running the script again and… wait, something looks wrong! It looks as if we connected the wrong vertices.
Well, we did connect the right vertices but in the wrong order. Yes, there is an order to follow when setting up faces: counter-clockwise starting from the lower-left.
Therefore the order for the face is 0, 5, 6, 1. Fix that line and run the script again.
Now we are in business. Anytime you see problems like this, try swapping the first or last two sets of indices between themselves. Alright, this is where things get funny. We need to figure out how to calculate all the indices to make a row of faces. If we look closely at the vertices indices we can see a pattern:
- All indexes jump by 5 in the X axis. This is equal to the number of rows.
- The first index starts at 0, while the second starts at 1
We can figure out the first index in a loop by multiplying the current column by rows
. Since the second one is offset by 1, we just have to add 1 to get it.
Try printing these values to check them out.
for x in range(columns - 1):
print(x * rows)
print((x + 1) * rows)
You might be wondering why we are looping over columns - 1
. We have 10 columns of vertices, but these only create 9 columns of faces. The last column doesn’t connect to anything after it.
The third and fourth indices are (x + 1) * rows + 1
and x * rows + 1
respectively. We add 1 to X before multiplying to set the index on the next row.
Here’s a loop to print all the indices:
for x in range(columns - 1):
print('first:', x * rows)
print('second:', (x + 1) * rows)
print('third:', (x + 1) * rows + 1)
print('fourth:', x * rows + 1)
print('---')
Fleshing out the mesh
Armed with all this knowledge we can now build the first row of faces. But before we get to that, let’s separate the face code into it’s own function so we can keep the code nice and clean. I have also added support for making a face on any row. Rows make indices increase by 1 as you they up the Y axis, so we can just add the row number at the end.
def face(column, row):
""" Create a single face """
return (column* rows + row,
(column + 1) * rows + row,
(column + 1) * rows + 1 + row,
column * rows + 1 + row)
Let’s put this into an expression like we did with vertices:
faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)]
We use rows — 1 for the same reason we did columns. Run the script and behold.
And with that the grid is now complete. You have made a script that can create 2D grids! Pat yourself in the back and keep reading for some finishing tweaks and challenges.
Scaling
We can control how many vertices our grid has, but the squares are always exactly 1 BU in size. Let’s change that.
All we have to do is multiply the X and Y coordinates by a scale factor. Start by adding a size variable. We can add this directly to the verts expression but again, it is cleaner if we do it in it’s own function.
size = 1
def vert(column, row):
""" Create a single vert """
return (column * size, row * size, 0)
verts = [vert(x, y) for x in range(columns) for y in range(rows)]
Try setting size to something other than 1 and check the grid.
Final code
import bpy
# Settings
name = 'Gridtastic'
rows = 5
columns = 10
size = 1
# Utility functions
def vert(column, row):
""" Create a single vert """
return (column * size, row * size, 0)
def face(column, row):
""" Create a single face """
return (column* rows + row,
(column + 1) * rows + row,
(column + 1) * rows + 1 + row,
column * rows + 1 + row)
# Looping to create the grid
verts = [vert(x, y) for x in range(columns) for y in range(rows)]
faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)]
# Create Mesh Datablock
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)
# Create Object and link to scene
obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.collection.objects.link(obj)
# Select the object
bpy.context.view_layer.objects.active = obj
obj.select = True
Wrap up
Hope you’ve enjoyed this introduction to making meshes in Python. This is only the most basic example I could think of, and there’s plenty of other interesting things to do. Here’s some easy things you can try to do for yourself:
- Make it use 2 scaling factors: X and Y
- Add an offset so the Grid doesn’t start at (0, 0).
- Isolate all this nicely into it’s own function (or class)
Stay tuned for the next tutorial where we finally go 3D with cubes.
Creating terrains just got a lot easier.
Mirage lets you create terrains and populate them with a simple UI and powerful features in realtime.
Start creating your world today!