Extruding meshes with Bmesh step-by-step
In previous tutorials we looked into the basics of how to create mesh data to make new objects. Let’s shift gears and start abusing pre-existing meshes instead, today we will look at extrusions. I hope you haven’t forgotten how to use Bmesh!
Let’s start with the usual: imports and some basic setup.
import bpy
import bmesh
from mathutils import Vector
from bmesh.types import BMVert
# Create BMesh object
scene = bpy.context.scene
obj = bpy.context.object
bm = bmesh.new()
bm.from_object(obj, scene)
We import Vector to create the direction vector for the extrusion and BMVert to make the code nicer looking further down.
We first need to determine the faces to extrude and pass them on to one of BMesh’s extrude operators. There are 4 of them.
extrude_face_region() | Extrude faces (the E key in edit mode) |
extrude_vert_indiv() | Extrude vertices individually |
extrude_discrete_faces() | Extrude individual faces separately |
extrude_edge_only() | Extrude only edges |
Let’s start with a simple plane. Make sure you have added a plane to the scene and have selected it before running the script. We will grab the first (and only!) face. Don’t forget to put it in brackets to wrap it in a list, as the extrude operators want iterables. We also need to call ensure_lookup_table() first since we are accessing a face directly by its index.
# Get geometry to extrude
bm.faces.ensure_lookup_table()
faces = [bm.faces[0]]
# Extrude
extruded = bmesh.ops.extrude_face_region(bm, geom=faces)
Now, the extrude op creates new geometry for us but doesn’t move it or transform it in anyway. We will use the translate() operator to take care of that. Translate takes a list of vertices though, so we will need to filter the geometry we received from the extrusion first to get a list of verts.
# Move extruded geometry
translate_verts = [v for v in extruded['geom'] if isinstance(v, BMVert)]
up = Vector((0, 0, 1))
bmesh.ops.translate(bm, vec=up, verts=translate_verts)
Getting rid of internal faces
Moving up in complexity, let’s try extruding up a cube. This time we’ll grab the cube’s top face.
faces = [bm.faces[5]]
If you run the script you will notice that the original face we picked to extrude is still there. That doesn’t happen in edit mode extrusions! Actually the operator we use in edit mode automatically deletes the original geometry to prevent internal faces.
But since we are working at a pretty low level we have to take care of cleaning up after ourselves. We can delete the original faces with (you guessed it) the delete() operator. This function takes two keyword parameters: the geometry we want gone and the deletion mode. The modes correspond to the options we get when pressing X in edit mode. These values come from an enum but have to be passed as an int. I find it’s more readable to make constants for them though. This is in the list of things to fix for 2.8 though, so it will probably change soon.
# We have to pass these as ints, so why not make constants for them?
# This will be fixed in 2.8 (AFAIK)
DEL_VERTS = 1
DEL_EDGES = 2
DEL_ONLYFACES = 3
DEL_EDGESFACES = 4
DEL_FACES = 5
DEL_ALL = 6
DEL_ONLYTAGGED = 7
bmesh.ops.delete(bm, geom=faces, context=DEL_FACES)
If you are working with “problematic” geometry you might also want to remove doubles to be safe.
# Remove doubles
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
Finally, let’s update the normals for good measure, update the mesh data and call it a day.
# Update mesh and free Bmesh
bm.normal_update()
bm.to_mesh(obj.data)
bm.free()
Final code
import bpy
import bmesh
from mathutils import Vector
from bmesh.types import BMVert
# Create BMesh object
scene = bpy.context.scene
obj = bpy.context.object
bm = bmesh.new()
bm.from_object(obj, scene)
# Get geometry to extrude
bm.faces.ensure_lookup_table()
faces = [bm.faces[0]] # For a plane
faces = [bm.faces[5]] # For the top face of the cube
# Extrude
extruded = bmesh.ops.extrude_face_region(bm, geom=faces)
# Move extruded geometry
translate_verts = [v for v in extruded['geom'] if isinstance(v, BMVert)]
up = Vector((0, 0, 1))
bmesh.ops.translate(bm, vec=up, verts=translate_verts)
# Delete original faces
# We have to pass these as ints, so why not make constants for them?
# This will be fixed in 2.8 (AFAIK)
DEL_VERTS = 1
DEL_EDGES = 2
DEL_ONLYFACES = 3
DEL_EDGESFACES = 4
DEL_FACES = 5
DEL_ALL = 6
DEL_ONLYTAGGED = 7
bmesh.ops.delete(bm, geom=faces, context=DEL_FACES)
# Remove doubles
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
# Update mesh and free Bmesh
bm.normal_update()
bm.to_mesh(obj.data)
bm.free()
Hope you found this helpful. Did you spot any mistakes or have any ideas for more tutorials? Let me know in the comments!