Meshes with Python & Blender: The 2D Grid

09.08.2017 @ Tutorials(Blender, Meshes, Python)

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 vari­ables.

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 lat­er.

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.context.scene.objects.link(obj)

# Select the object
bpy.context.scene.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 func­tion

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 expres­sion:

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 glo­ry.

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 fol­low­ing:

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.

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 ver­tices.

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 low­er-left.

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

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 pat­tern:

  • 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 ver­tices:

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 chal­lenges.

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 func­tion.

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.objects.link(obj) # Select the object bpy.context.scene.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 your­self:

  • 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.

All the posts you can read

6 Comments

  1. Markus Mayer(2 years ago)

    Hi there,
    thanks for this series. However I found a lit­tle error:
    verts = [(x, 0, 0) for x in range(columns)]
    will cre­ate 10 ver­tices. Not 5 as stat­ed in the text.

    1. Diego Gangl(2 years ago)

      Nice catch, thanks!

      1. Markus Mayer(2 years ago)

        Hate to say this,
        but theres more…
        For cre­at­ing the first face you state we should use the verts in a counter clock­wise fash­ion. So shows your dia­gram. But your order of (0, 1, 5, 6) is actu­al­ly clock­wise count­ing. CC would be (0, 6, 5, 1). The dif­fer­ence?
        Actually its in the way the nor­mal is fac­ing. Yours is point­ing away from us and an actu­al cc order would point towards the cre­ator. Please don’t get me wrong here. I don’t want to offend you in any way. I had absolute­ly no idea of mesh con­struc­tion with Python pri­or to read­ing this and I am here to learn about this stuff. But I think you can agree, that it’s impor­tant ( espe­cial­ly in my ear­ly stage ) that the gath­ered infos is cor­rect. Anyway’s it’s kind of enhanc­ing the learn­ing expe­ri­ence when you actu­al­ly spot such things ;P
        I’d rather told you this in a PM but I’m new to this whole Diqus thingy and don’t even know if thats pos­si­ble. So I hope it’s not to both­er­ing here in pub­lic.

      2. Diego Gangl(2 years ago)

        Hey, no prob­lem. Everyone makes mis­takes, and I make a _ton_ of them. I don’t know how I man­aged to get clock­wise and CC mixed up, but I’ll get that fixed. And you’re right that the nor­mals are point­ing down­wards. If you change the code to actu­al­ly go counter clock­wise, they point up as they should 🙂
        That said I tried to avoid going into the nor­mals thing for this series, unless they were giv­ing trou­ble (like in the last part).
        Disqus does­n’t have DMs AFAIK, but you can always send me an email at diego@sienstesia.co. This was only the 2nd or 3rd tuto­r­i­al I made, so I do appre­ci­ate some con­struc­tive feed­back!

  2. Ivan Enzhaev(1 week ago)

    Your tuto­ri­als are very good. Thank you. I have very use­ful infor­ma­tion for every­one who want to write addons for Blender 2.8

    This is a great exten­sion for VSCode that allow to have IntelliSense (auto­com­plete) and debug­ging in VSCode

    Blender 2.8 Python Addons with VSCode
    https://www.youtube.com/watch?v=q06-hER7Y1Q

    1. Diego Gangl(1 week ago)

      Thanks for the kind words Ivan! The VSCode exten­sion is real­ly awe­some. You can also get some lev­el of auto­com­plete in vim/pycharm/etc with the bpy mod­ules used in that exten­sion.

Leave a Reply

Your email address will not be published.