# How can I align the 3D cursor to a face / polygon with python

I'm currently trying to align the 3D Cursor to a face / polygon with python on Blender 2.8, to snap one object on the surface, but I don't know how can I achieve that, currently I'm using this as a test with the default cube, but it doesn't work as I expected:

import bpy
from bpy import context

scene= context.scene

obj = bpy.context.active_object
face = obj.data.polygons[0]

scene.cursor.location = face.normal
scene.cursor.rotation_euler = face.normal

rot = context.scene.cursor.rotation_euler.copy()
loc = context.scene.cursor.location.copy()



Yes, I know, I can do this with the Blender's snapping functions, but I would like to know how can achieve this with Python.

Hopefully, could someone please bring me some help?

Thank you so much!

So, an important thing to know here is that a normal is only a vector, so it, in a way, represents a single axis of the rotation. The script below pretty much guesses an x and y heading and uses the z direction as the up axis for the cursor. Here, I'm finding the location and normal of the selected face and assigning it to the location and up axis of the cursor. I've simplified this script a lot. It doesn't use the bmesh data, which means you may need to make your face selection in edit mode, and switch back to object mode and then back into edit mode before you run the script.

import bpy
import mathutils

C = bpy.context
cursor = C.scene.cursor
obj = C.active_object
p = obj.data.polygons

#get the normal and center of the selected face
normal = obj.data.polygons[p.active].normal
center = obj.data.polygons[p.active].center

#generate a quaternion rotation from the normal vector
up = normal.to_track_quat('X', 'Y')

#set the cursor to the center location and rotate it to match the up vector
cursor.location = center
cursor.rotation_quaternion = up


And then back in object mode again carry on with getting the cursor location and rotation and creating the primative like in your example above:

#get the object world matrix
mat = obj.matrix_world

#generate the location and rotation
rot = cursor.rotation_euler.copy()
loc = mat @ cursor.location.copy()

bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.mesh.primitive_plane_add(location = loc, rotation = rot)


You have to get the object world matrix and use it to offset the center location because any vertex, edge, or face location will be based on a distance from the object center, not the world center. Of course, you don't have to use the cursor as a step in between and can just apply the rotation and location of the face directly using:

rot = up
loc = mat @ center

• I think this works, but you should make it clear that the Cursor's rotation has to be in Quaternion mode – yarun can Jul 23 '19 at 4:38

Similarly,

Differs in that it uses live edit bmesh for getting the active face, and also atm it appears that setting the cursor matrix has no effect.

import bpy
import bmesh
from mathutils import Vector, Matrix

context = bpy.context
scene = context.scene
cursor = scene.cursor
ob = context.edit_object
mw = ob.matrix_world

me = ob.data
bm = bmesh.from_edit_mesh(me)

f = bm.select_history.active
o = mw @ f.calc_center_median()
f.normal_update()
norm = (o + mw @ f.normal) - o

q = norm.to_track_quat('Z', 'Y')

M = q.to_matrix().to_4x4()
M.translation = o
cursor.matrix = M # does not work!! bummer

cursor.location = o
if cursor.rotation_mode == 'QUATERNION':
cursor.rotation_quaternion = q
elif cursor.rotation_mode == 'AXIS_ANGLE':
cursor.rotation_axis_angle = q.to_axis_angle()
else:
cursor.rotation_euler = q.to_euler(cursor.rotation_mode)


Related:

On aligning the to the tangent to emulate NORMAL transform axis display

Align to face normal vector

Python: vertex normal according to World

• I tried your version but it does not seem to aling to the normals properly, please see the image imgur.com/qBoWJ6A.png – yarun can Sep 11 '19 at 3:53