Meshes with Python & Blender: The 2D Grid

Procedural gen­er­a­tion is awe­some! In this tuto­r­i­al series we’ll look at mak­ing mesh­es using Blender’s Python API.

Creating mesh­es pro­gram­mat­i­cal­ly opens many pos­si­bil­i­ties. You can cre­ate para­met­ric objects that respond to real world set­tings, gen­er­a­tive art, math based shapes or even pro­ce­dur­al con­tent for games. Blender is a great choice for this kind of work since it com­bines a full blown mod­el­ing and ani­ma­tion suite with a pow­er­ful (and fair­ly well doc­u­ment­ed) Python API.

In this series we will be look­ing at mak­ing sev­er­al prim­i­tives and some basic trans­for­ma­tions, I’ll also drop some tips along the way to make devel­op­ment eas­i­er. I’m assum­ing you already know some basic Python, and enough Blender to get around. For this intro­duc­tion we’ll skip the 3D and focus on mak­ing a sim­ple flat grid instead.

Pro Tip: Save often while work­ing on mesh­es! You will make Blender crash more than once 🙂

Tutorial Series

Setting it up

The Blender data sys­tem makes a dis­tinc­tion 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 import­ing bpy (sur­prise!) and set­ting 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 con­trol how many ver­tices the grid has. Next we set up the mesh and object adding part. We have to add a mesh dat­a­block first, then an object that uses that mesh and final­ly link them to the cur­rent scene. I’ve also added some emp­ty lists for verts and faces. We will be fill­ing 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 inter­est­ing part is from_pydata(). This func­tion cre­ates a mesh from three python lists: ver­tices, 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 run­ning the script now. You will see a new object has been added but there’s no geom­e­try to be seen (since we haven’t added it yet). With the basic scaf­fold­ing done, delete that object and read on to start build­ing the grid.

A grid of vertices

Let’s begin by adding a sin­gle vert. Vertices are rep­re­sent­ed by 3 coor­di­nates (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 cen­ter of the scene, which is also the ori­gin for glob­al coor­di­nates. In oth­er words, at coor­di­nates (0, 0, 0). Each ver­tex is a tuple of 3 floats, so mod­i­fy the list like this:

verts = [(0, 0, 0)]

Try run­ning the script again and you will see a lone­ly dot. You can actu­al­ly 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 ver­tices as the num­ber of columns we set up. This is real­ly easy to do with a list expression:

verts = [(x, 0, 0) for x in range(columns)]

range() returns inte­gers, so the ver­tex X coor­di­nate will be their col­umn num­ber. That means each col­umn will be 1 Blender Unit (or meter) wide. Run the script again you will see 10 ver­tices lined up on the X axis. To com­plete a grid all we have to do is more rows. We can eas­i­ly extend the expres­sion 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 ver­tex grid in all it’s glory.

A 2D grid in Blender

We can start mak­ing faces now, but first we need to under­stand how.

Understanding faces

Each vert we added has an index num­ber. Indices are set as we add each vert, so the first one is 0, the sec­ond 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 inte­gers by the way. Blender won’t fail or com­plain, but it will round them. Since we are mak­ing quads we will need to find four ver­tex indices for each face. But how? You could try guess­ing them, but there’s a bet­ter 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 any­thing from this tuto­r­i­al I hope it is debug mode. It’s the most use­ful set­ting you can have for mesh mak­ing and even gen­er­al addon devel­op­ment. To see the ver­tices indices, select the 2D ver­tex grid and go into edit mode. Open up the N‑panel and tog­gle “Indices” in the Mesh Display Panel. It’s under the “Edge Info” col­umn. If you can’t see it, you might not have enabled debug mode yet. Now any ver­tex you select will show it’s index, so select them all to see their indices.

Grid indices in Blender

Let’s focus on the first face. As you can see it’s made up of ver­tices 0, 1, 5 and 6. Let’s try mak­ing a sin­gle face with those indices:

faces = [(0, 1, 5, 6)]

Try run­ning the script again and… wait, some­thing looks wrong! It looks as if we con­nect­ed the wrong vertices.

Well, we did con­nect the right ver­tices but in the wrong order. Yes, there is an order to fol­low when set­ting up faces: counter-clock­wise start­ing from the lower-left.

Face vertex index order in Blender

Therefore the order for the face is 0, 5, 6, 1. Fix that line and run the script again.

Our first face generated in Python

Now we are in busi­ness. Anytime you see prob­lems like this, try swap­ping the first or last two sets of indices between them­selves. Alright, this is where things get fun­ny. We need to fig­ure out how to cal­cu­late all the indices to make a row of faces. If we look close­ly at the ver­tices indices we can see a pattern:

  • All index­es jump by 5 in the X axis. This is equal to the num­ber of rows.
  • The first index starts at 0, while the sec­ond starts at 1

We can fig­ure out the first index in a loop by mul­ti­ply­ing the cur­rent col­umn by rows. Since the sec­ond one is off­set by 1, we just have to add 1 to get it.

Try print­ing these val­ues to check them out.

for x in range(columns - 1):
     print(x * rows)
     print((x + 1) * rows)

You might be won­der­ing why we are loop­ing over columns - 1. We have 10 columns of ver­tices, but these only cre­ate 9 columns of faces. The last col­umn doesn’t con­nect to any­thing after it.

The third and fourth indices are (x + 1) * rows + 1 and x * rows + 1 respec­tive­ly. We add 1 to X before mul­ti­ply­ing 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 knowl­edge we can now build the first row of faces. But before we get to that, let’s sep­a­rate the face code into it’s own func­tion so we can keep the code nice and clean. I have also added sup­port for mak­ing 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 num­ber 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 expres­sion 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 rea­son we did columns. Run the script and behold.

And with that the grid is now com­plete. You have made a script that can cre­ate 2D grids! Pat your­self in the back and keep read­ing for some fin­ish­ing tweaks and challenges.

Scaling

We can con­trol how many ver­tices our grid has, but the squares are always exact­ly 1 BU in size. Let’s change that.

All we have to do is mul­ti­ply the X and Y coor­di­nates by a scale fac­tor. Start by adding a size vari­able. We can add this direct­ly to the verts expres­sion but again, it is clean­er 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 set­ting size to some­thing oth­er 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 intro­duc­tion to mak­ing mesh­es in Python. This is only the most basic exam­ple I could think of, and there’s plen­ty of oth­er inter­est­ing things to do. Here’s some easy things you can try to do for yourself:

  • Make it use 2 scal­ing fac­tors: X and Y
  • Add an off­set so the Grid doesn’t start at (0, 0).
  • Isolate all this nice­ly into it’s own func­tion (or class)

Stay tuned for the next tuto­r­i­al where we final­ly go 3D with cubes.

Creating terrains just got a lot easier.

Mirage lets you cre­ate ter­rains and pop­u­late them with a sim­ple UI and pow­er­ful fea­tures in realtime. 


Start cre­at­ing your world today!

All the posts you can read
TutorialsBlender, Meshes, Python18.08.2020