Suppose you have a mesh; you go into Edit mode, set Transform Orientation to Normal, show 3D manipulator widget, Pivot Point = Active Element, face select mode, and then you select any one face: immediately you will see a blue arrow representing the face normal, and also a red and a green arrow representing the face's tangent and bitangent.

What is the most straightforward way to refer to said tangent and bitangent vectors from a Python script? The API documentation confuses me with loops and uv's, yet, clearly, Blender assigns a tangent and bitangent to each face without hesitation!

• I believe what you are looking for is 'calc_tangents'. elysiun.com/forum/… Feb 22, 2015 at 7:54
• Thank you, gentlemen! To me, each face has a single loop that defines the perimeter, hence defines the face itself. But the API speaks of multiple loops, each with a single vertex?? Average, fine; thank you. But Blender surely has a tangent and bitangent already stored and associated with each face, as evidenced by the instantaneous appearance of those little XYZ arrows when Transform Orientation = Normal. Isn't that so? Feb 22, 2015 at 13:52

You access tangents and bitangents from MeshLoops:

import bpy
me = bpy.context.active_object.data

# tangents have to be pre-calculated
# this will also calculate loop normal
me.calc_tangents()

# loop faces
for face in me.polygons:
# loop over face loop
for vert in [me.loops[i] for i in face.loop_indices]:
tangent = vert.tangent
normal = vert.normal
bitangent = vert.bitangent_sign * normal.cross(tangent)


To get tangent and bitangent of some face you need to average tangents and bitangents of every vertex in that face loop.

• There is a convenience attribute on polygons you can use to loop over face loops: face.loop_indices. It's already a range object. for vert in [me.loops[i] for i in face.loop_indices]: should suffice. Feb 22, 2015 at 13:25
• Jerryno; CoDEmanX: face.loop_indices Duly noted: thank you! Feb 22, 2015 at 18:57
• I made this test comparing normals coming from the loops and the ones that are stored in me.vertices[x].normal, and they are different. Shouldn't those be exactly the same? I've made a separate question here blender.stackexchange.com/questions/71356/…
– Greg
Jan 21, 2017 at 15:02