2
$\begingroup$

I want to use bpy module to create a polygon in blender.

However, I have some problem with the normal of faces.

See the following example (adopt from this post):

Works:

import bpy

verts = [(1.0, 1.0, -1.0),
         (1.0, -1.0, -1.0),
         (-1.0, -1.0, -1.0),
         (-1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0),
         (1.0, -1.0, 1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
         (4, 7, 6, 5),
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

mesh = bpy.data.meshes.new('CubeMesh')
mesh.from_pydata(verts, [], faces)
mesh.update()
cube = bpy.data.objects.new('Cube', mesh)
scn = bpy.context.scene
scn.objects.link(cube)

The normals in the edit mode are correctly outward.

Then, I did some experiment on the same example code like this:

Don't Work:

import bpy

verts = [(1.0, 1.0, -1.0),
         (1.0, -1.0, -1.0),
         (-1.0, -1.0, -1.0),
         (-1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0),
         (1.0, -1.0, 1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
         (4, 5, 6, 7),         # (4, 7, 6, 5) --> (4, 5, 6, 7)
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

mesh = bpy.data.meshes.new('CubeMesh')
mesh.from_pydata(verts, [], faces)
mesh.update()
cube = bpy.data.objects.new('Cube', mesh)
scn = bpy.context.scene
scn.objects.link(cube)

If you copy and paste above code, you will see the face defined by (4, 5, 6, 7) flipped down.

It seems like the order of the vertices which define a face are critical on determining the direction of the normal.

My question is then how Blender internally determines the direction of normal of a face given the vertices? Is it the right-hand rule or something else? If it is so, is there any helper function or module that can help me with ensuring an outward normal of a face with respect to the world origin (0, 0, 0)?

Thanks.

$\endgroup$
  • $\begingroup$ Yes, you are right. I have fixed it, thanks. $\endgroup$ – Dboy Liao Nov 16 '15 at 7:51
2
$\begingroup$

Yes, the order in which we define the vertex indices of the Face directly influence the Face's normal.

See this thread on StackOverflow for some insight

Usually when constructing geometry you can keep track of the order and never encounter this problem, just be consistent. If you suddenly change the order in which you specify the vertices, you will get an inverted normal on that face (which is what happens to you). There's no way for Blender to know if that's intentional or not.

enter image description here

Sometimes it can be more convenient to let an algorithm recalculate normals at the end, as an extra processing step.

bmesh.ops

There's a bmesh.ops to recalculate normals .

bmesh.ops.recalc_face_normals(bm, faces=bm.faces)

See TextEditor > Templates > Python > Bmesh Simple for the template to get a bmesh representation from an object.

import bpy
import bmesh

verts = [(1.0, 1.0, -1.0),
         (1.0, -1.0, -1.0),
         (-1.0, -1.0, -1.0),
         (-1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0),
         (1.0, -1.0, 1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
         (4, 5, 6, 7),         # (4, 7, 6, 5) --> (4, 5, 6, 7)
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

mesh = bpy.data.meshes.new('CubeMesh')
scn = bpy.context.scene
mesh.from_pydata(verts, [], faces) 
mesh.update()

bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()

cube = bpy.data.objects.new('Cube', mesh)
scn.objects.link(cube)

You may want to work this into a function for clarity.

bpy.ops.mesh

You can use normals_make_consistent() if it's more convenient, there are however general reasons to avoid using bpy.ops within a scripting context. See the post on why to avoid bpy.ops

def make_object(name, verts, faces, normal_recalc=True):

    scn = bpy.context.scene

    mesh = bpy.data.meshes.new(name + "_Mesh")
    mesh.from_pydata(verts, [], faces) 
    mesh.update()

    ob = bpy.data.objects.new(name, mesh)
    scn.objects.link(ob)
    if normal_recalc:
        scn.objects.active = ob
        bpy.ops.object.mode_set(mode = 'EDIT')
        bpy.ops.mesh.normals_make_consistent()
        bpy.ops.object.mode_set(mode = 'OBJECT')

    return ob

make_object('my_cube', verts, faces)
# make_object(name, verts, faces, normal_recalc=False)
$\endgroup$
  • $\begingroup$ I find a interesting post and find out that there is a magic function in the bpy module which will recalculate the normal for you. Just make the object you want to recalculate normal for be selected and run: bpy.ops.mesh.normals_make_consistent() Boom! all the normals are outward now. $\endgroup$ – Dboy Liao Nov 16 '15 at 8:05
  • $\begingroup$ yes, bpy.ops are a useful alternative too. $\endgroup$ – zeffii Nov 16 '15 at 8:15
0
$\begingroup$

I think it is better to have solutions as many as possible.

Here is my solution with only bpy

import bpy

verts = [(1.0, 1.0, -1.0),
         (1.0, -1.0, -1.0),
         (-1.0, -1.0, -1.0),
         (-1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0),
         (1.0, -1.0, 1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
         (4, 5, 6, 7),
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

mesh = bpy.data.meshes.new('CubeMesh')
mesh.from_pydata(verts, [], faces)
mesh.update()

cube = bpy.data.objects.new('Cube', mesh)
scn = bpy.context.scene
scn.objects.link(cube)
scn.objects.active = cube

cube.select = True
bpy.ops.object.mode_set(mode = 'EDIT')     # Enter edit mode.
bpy.ops.mesh.normals_make_consistent()     # Recalculate the normals.
bpy.ops.object.mode_set(mode = 'OBJECT')   # Turn back to object mode.
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.