Extruding meshes with Bmesh

22.06.2018 @ Tutorials(Blender, Bmesh, Python)

In pre­vi­ous tuto­ri­als we looked into the basics of how to cre­ate mesh data to make new objects. Let’s shift gears and start abus­ing pre-exist­ing mesh­es instead, today we will look at extru­sions. I hope you haven’t for­got­ten how to use Bmesh!

Let’s start with the usu­al: imports and some basic set­up.

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 Vec­tor to cre­ate the direc­tion vec­tor for the extru­sion and BMVert to make the code nicer look­ing fur­ther down.

We first need to deter­mine the faces to extrude and pass them on to one of BMesh’s extrude oper­a­tors. There are 4 of them.

extrude_face_region()Extrude faces (the E key in edit mode)
extrude_vert_indiv()Extrude ver­tices indi­vid­u­al­ly
extrude_discrete_faces()Extrude indi­vid­ual faces sep­a­rate­ly
extrude_edge_only()
Extrude only edges

Let’s start with a sim­ple plane. Make sure you have added a plane to the scene and have select­ed it before run­ning the script. We will grab the first (and only!) face. Don’t for­get to put it in brack­ets to wrap it in a list, as the extrude oper­a­tors want iter­ables. We also need to call ensure_lookup_table() first since we are access­ing a face direct­ly 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 cre­ates new geom­e­try for us but doesn’t move it or trans­form it in any­way.  We will use the trans­late() oper­a­tor to take care of that. Trans­late takes a list of ver­tices though, so we will need to fil­ter the geom­e­try we received from the extru­sion 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)
The result of extrud­ing a plane 1 BU up

Getting rid of internal faces

Mov­ing up in com­plex­i­ty, let’s try extrud­ing 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 orig­i­nal face we picked to extrude is still there. That doesn’t hap­pen in edit mode extru­sions! Actu­al­ly the oper­a­tor we use in edit mode auto­mat­i­cal­ly deletes the orig­i­nal geom­e­try to pre­vent inter­nal faces.

Notice the inter­nal face in the mid­dle? Nasty!

But since we are work­ing at a pret­ty low lev­el we have to take care of clean­ing up after our­selves. We can  delete the orig­i­nal faces with (you guessed it) the delete() oper­a­tor. This func­tion takes two key­word para­me­ters: the geom­e­try we want gone and the dele­tion mode. The modes cor­re­spond to the options we get when press­ing X in edit mode. These val­ues come from an enum but have to be passed as an int. I find it’s more read­able to make con­stants for them though. This is in the list of things to fix for 2.8 though, so it will prob­a­bly 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 work­ing with “prob­lem­at­ic” geom­e­try you might also want to remove dou­bles to be safe.

# Remove doubles
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
Geom­e­try we can be proud of

Final­ly, let’s update the nor­mals for good mea­sure, 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 help­ful. Did you spot any mis­takes or have any ideas for more tuto­ri­als? Let me know in the com­ments!

All the posts you can read

No comments yet

Leave a Reply

Your email address will not be published.