bl_info = {
"name": "Snap Cursor to Normals",
"blender": (3, 5, 1),
"category": "3D View",
}
import bpy
import bmesh
from mathutils import Matrix, Vector
from functools import reduce
class View3DSnapCursorNormal(bpy.types.Operator):
"""Snap 3D cursor to faces orientated to average normal"""
bl_idname = "view3d.snap_cursor_to_normals"
bl_label = "Cursor to Normals"
bl_options = {'REGISTER'}
def execute(self, context):
scene = bpy.context.scene
obj = bpy.context.edit_object
if (obj == None): return {'CANCELLED'}
mat = obj.matrix_world.copy()
bm = bmesh.from_edit_mesh(obj.data)
selected_faces = [ face for face in bm.faces if face.select ]
selected_verts = [ vert for vert in bm.verts if vert.select ]
if (len(selected_faces) > 0):
normals = [ face.normal for face in selected_faces ]
orientation = reduce(lambda a, b: a + b, normals).normalized();
orientation = orientation.to_track_quat('Z', 'Y')
position = reduce(lambda a, b: a + b,
map(lambda f: f.calc_center_median(),
selected_faces))
position = position / len(selected_faces)
mat = mat @ Matrix.LocRotScale(position, orientation, None)
bpy.context.scene.cursor.location = mat.to_translation()
bpy.context.scene.cursor.rotation_euler = mat.to_euler()
else:
return {'CANCELLED'}
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(View3DSnapCursorNormal.bl_idname)
def register():
bpy.utils.register_class(View3DSnapCursorNormal)
bpy.types.VIEW3D_MT_snap.append(menu_func)
def unregister():
bpy.utils.unregister_class(View3DSnapCursorNormal)
bpy.types.VIEW3D_MT_snap.remove(menu_func)
if __name__ == "__main__":
register()