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

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

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

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

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 real­time.


Start cre­at­ing your world today!

Tutorials(Blender, Meshes, Python)Last updated 18.08.2020
All the posts you can read

12 Comments

  1. Markus Mayer(3 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(3 years ago)

      Nice catch, thanks!

      1. Markus Mayer(3 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(3 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(12 months 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(12 months 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.

  3. 8Observer8 (Ivan Enzhaev)(8 months ago)

    My sim­ple instruc­tion how to show indices in Blender 2.8

    1) Edit > Preferences
    2) Interface > Display > check “Developer Extras”
    3) In right top cor­ner of “3D Viewport” click on drow­down list “Viewport Overlays” > look for “Developer” sec­tion > check “Indices”

    It work in “Edit Mode” only.

    1. Diego Gangl(8 months ago)

      Hi Ivan,
      Yeah, we can enable them from the UI in 2.8. I’ll update these posts soon, thanks!

  4. Michael Jensen(3 months ago)

    Hi, Just found this site, and try­ing the code. and get­ting errors right away. “mod­ule ‘bpy’ has no attribute ‘scene’. ..maybe this is because I’m using blender 2.8 which is more recent? ..I was so excit­ed to find this tuto­r­i­al..

  5. Michael Jensen(3 months ago)

    ..? https://blender.stackexchange.com/questions/145658/link-new-object-to-scene-with-python-in‑2–8

  6. Michael Jensen(3 months ago)

    to view the ver­tex indices you need this: https://blender.stackexchange.com/questions/158493/displaying-vertex-indices-in-blender‑2–8‑using-debug-mode . I’m very sor­ry to say that I took ‑way- too long to find this.

    1. Diego Gangl(2 months ago)

      Man, sor­ry I took so long to reply! The noti­fi­ca­tion emails prob­a­bly got buried. I just noticed there’s a type in one of the code blocks that says “bpy.scene” instead of “bpy.context.scene”. Thanks for men­tion­ing it!

Leave a Reply

Your email address will not be published.