3
$\begingroup$

I'm trying to figure out how to create a pocket using bmesh (Python Console). The regular workflow to create the example below could be as follows —

enter image description here

Add a Cube in Object Mode, tab into Edit Mode and select an arbitrary Face. Then, use the following steps —

  1. Extrude the selected Face
  2. Scale the selected Face
  3. Extrude the selected Face again
  4. Translate the selected Face

The question is how to create the same result using bmesh? To get started in the Python Console,

import mathutils
import bmesh

me = bpy.context.edit_object.data
bm = bmesh.from_edit_mesh(me)

Let's just extrude an arbitrary Face and continue from there

f = bm.faces[2]  
d = bmesh.ops.extrude_discrete_faces(bm, faces=[f])
f = d['faces'][0]

Now f refers to the extruded Face. The next step is to scale the extruded Face. To keep things simple, let's assume the normal of the selected Face is parallel to the x-axis. The following code only works as intended if the cube is centered around the origin

bmesh.ops.scale(bm, vec=mathutils.Vector((1,.5,.5)), verts=f.verts)

because bmesh.ops.scale() seems to use the global (world) coordinates. Is it possibible to use local coordinates instead?

Another option might be to use bmesh.ops.inset_individual(), though unfortunately it does not create the 4 trapezoid Faces.

The last two steps are straightforward, just another bmesh.ops.extrude_discrete_faces() followed by bmesh.ops.translate().

$\endgroup$

2 Answers 2

5
$\begingroup$

This seems to work, in edit mode (for the simplicity of example)

import bpy
import bmesh

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

face = [bm.faces.active]

bmesh.ops.inset_region(bm, faces=face, thickness=0.4)
bmesh.ops.inset_region(bm, faces=face, thickness=0, depth=-0.5)

bmesh.update_edit_mesh(me, True)

enter image description here

Regarding bmesh.ops.scale(), it takes a space argument which is a matrix to base the operation on. If you are comfortable generating matrices then that's probably enough, but if not then see this snippet.

Template can be found at TextEditor->Templates->Python->Bmesh Simple

import bpy
import bmesh
from mathutils import Vector, Matrix

# Get the active mesh
me = bpy.context.object.data
bm = bmesh.new()   # create an empty BMesh
bm.from_mesh(me)   # fill it in from a Mesh

bm.edges.ensure_lookup_table()
bm.verts.ensure_lookup_table()

e = bm.edges[0]

mid_invert = (e.verts[0].co + e.verts[1].co) * -0.5
edge_matrix = Matrix()
edge_matrix[0][3] = mid_invert[0]
edge_matrix[1][3] = mid_invert[1]
edge_matrix[2][3] = mid_invert[2]

factor = (0.5, 0.5, 0.5)
bmesh.ops.scale(bm, vec=factor, space=edge_matrix, verts=e.verts)

# Finish up, write the bmesh back to the mesh
bm.to_mesh(me)
bm.free()  # free and prevent further access

I'm almost positive that there is an easier way to construct the space matrix, given a simple 3-vector, rather than my verbose approach.

$\endgroup$
5
  • $\begingroup$ Thanks! I completely overlooked bmesh.ops.inset_individual() and bmesh.ops.inset_region(). Still curious though, is it possible to use bmesh.ops.scale() using local coordinates? $\endgroup$
    – Ailurus
    May 18, 2015 at 15:49
  • 1
    $\begingroup$ I think so, you have to pass a matrix to the scale operator. blender.org/api/blender_python_api_2_74_5/…. Ask a fresh question, I might have time to answer! $\endgroup$
    – zeffii
    May 18, 2015 at 15:54
  • $\begingroup$ gist.github.com/zeffii/71862f5b1cad1bf1d2c1 here's an example that scales edges with reference to their own matrix.. $\endgroup$
    – zeffii
    May 18, 2015 at 16:04
  • 1
    $\begingroup$ @Ailurus I've extended the answer to include bpy.ops.scale() $\endgroup$
    – zeffii
    May 19, 2015 at 5:24
  • $\begingroup$ Cheers. I added some additional information on using bmesh.ops.scale() in a separate answer. $\endgroup$
    – Ailurus
    May 19, 2015 at 14:18
2
$\begingroup$

Figured out how to use bmesh.ops.scale() to scale a Face the same way as in Edit Mode. As it might be useful to others I thought I'd post it here.

First we have to create a translation matrix T that maps the centroid of our Face of interest to the origin. This part is straightforward — assuming the necessary modules are loaded and we have a reference to our Face of interest, e.g. like this,

import mathutils
import bmesh

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

k = 2
face = bm.faces[k]

the translation matrix T can be created as follows:

T = mathutils.Matrix.Translation(-face.calc_center_median())

Note the minus — we're mapping to the origin, not the other way around.

Next, we have to create a rotation matrix R that maps the normal n of our Face of interest to the z-axis. In order to create R, we require the axis of rotation. This is simply the cross-product between n and z (in this order). The angle between the two vectors can be determined in various ways, e.g. using the mathutils.Vector.angle() function or acos(n.dot(z)), after all both vectors are unit vectors. Note that a positive angle refers to counter-clockwise rotation in Blender.

n = face.normal
z = mathutils.Vector((0,0,1))
axis = n.cross(z)
angle = n.angle(z)

R = mathutils.Matrix.Rotation(angle, 4, axis)

Finally, we have to combine T and R. The order is important — first translation, then rotation. The example below scales the Face by 60% just like in Edit Mode.

bmesh.ops.scale(bm, vec=mathutils.Vector((.6,.6,1)), space=R*T, verts=face.verts)
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .