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 🙂
- 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
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.
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.
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.
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
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!
thanks for this series. However I found a little error:
verts = [(x, 0, 0) for x in range(columns)]
will create 10 vertices. Not 5 as stated in the text.
Nice catch, thanks!
Hate to say this,
but theres more…
For creating the first face you state we should use the verts in a counter clockwise fashion. So shows your diagram. But your order of (0, 1, 5, 6) is actually clockwise counting. CC would be (0, 6, 5, 1). The difference?
Actually its in the way the normal is facing. Yours is pointing away from us and an actual cc order would point towards the creator. Please don’t get me wrong here. I don’t want to offend you in any way. I had absolutely no idea of mesh construction with Python prior to reading this and I am here to learn about this stuff. But I think you can agree, that it’s important ( especially in my early stage ) that the gathered infos is correct. Anyway’s it’s kind of enhancing the learning experience when you actually 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 possible. So I hope it’s not to bothering here in public.
Hey, no problem. Everyone makes mistakes, and I make a _ton_ of them. I don’t know how I managed to get clockwise and CC mixed up, but I’ll get that fixed. And you’re right that the normals are pointing downwards. If you change the code to actually go counter clockwise, they point up as they should 🙂
That said I tried to avoid going into the normals thing for this series, unless they were giving trouble (like in the last part).
Disqus doesn’t have DMs AFAIK, but you can always send me an email at firstname.lastname@example.org. This was only the 2nd or 3rd tutorial I made, so I do appreciate some constructive feedback!
Your tutorials are very good. Thank you. I have very useful information for everyone who want to write addons for Blender 2.8
This is a great extension for VSCode that allow to have IntelliSense (autocomplete) and debugging in VSCode
Blender 2.8 Python Addons with VSCode
Thanks for the kind words Ivan! The VSCode extension is really awesome. You can also get some level of autocomplete in vim/pycharm/etc with the bpy modules used in that extension.
My simple instruction how to show indices in Blender 2.8
1) Edit > Preferences
2) Interface > Display > check “Developer Extras”
3) In right top corner of “3D Viewport” click on drowdown list “Viewport Overlays” > look for “Developer” section > check “Indices”
It work in “Edit Mode” only.
Yeah, we can enable them from the UI in 2.8. I’ll update these posts soon, thanks!
Hi, Just found this site, and trying the code. and getting errors right away. “module ‘bpy’ has no attribute ‘scene’. ..maybe this is because I’m using blender 2.8 which is more recent? ..I was so excited to find this tutorial..
use bpy.context.scene -> then it works
to view the vertex indices you need this: https://blender.stackexchange.com/questions/158493/displaying-vertex-indices-in-blender‑2–8‑using-debug-mode . I’m very sorry to say that I took ‑way- too long to find this.
Man, sorry I took so long to reply! The notification emails probably 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 mentioning it!
Your website is not available in Russia. I use VPN (Browsec) to open it.