# Align all selected faces with the last selected face

I have written this script to try and align the orientation of all selected faces with the last selected face:

import bpy
import bmesh
import mathutils

context = bpy.context
ob = context.object
me = ob.data
#Get the translation of the object in world space
mw = ob.matrix_world.copy()

selfaces =[]

bpy.ops.object.mode_set(mode='EDIT')  #Processing must be done in EDIT mode
bm = bmesh.from_edit_mesh(me)

#Show face selection order
for e in bm.select_history:
if isinstance(e, bmesh.types.BMFace) and e.select:
selfaces.append(e)

# Get normal of last selected face
axis_src = selfaces[-1].normal
# local z-axis
axis_dst = mathutils.Vector((0, 0, 1))

#Rotate all faces to match the orientation of the last selected
for f in selfaces[:-1]:
diff = axis_src.rotation_difference(f.normal).to_matrix().inverted()
print(diff)
bmesh.ops.rotate(bm, cent=f.calc_center_median(), matrix=diff, verts=f.verts)

bmesh.update_edit_mesh(me)


It is aligning the faces as desired. But it also seems to mirror the vertices along the y axis intersecting the centre of the object. Can anyone figure out where this is happening? I have looked at the rotation matrices for this and it looks like it is just applying the 45 degree and -45 degree rotation around the y axis.

Flip if normals are opposing. Speculating somewhat re the issue here, first would suggest

diff = f.normal.rotation_difference(axis_src).to_matrix()


The rotation created is an angle about an axis, eg for cube if select top and bottom face, to rotate Z to -Z,, would require a rotation of 180 degrees about any axis on XY plane. An example print of rotation difference quaternion to angle axis,

(Vector((-0.7071068286895752, -0.7071068286895752, -0.0)), 3.1415927410125732)


ie a rotation of 180 degrees about vector (-1, -1, 0)

For this case it may be more logical to not rotate at all, as it is arguable that despite facing different directions the top and bottom cube faces are "aligned".

Taking the non acitve face and winding around a full 180 will produce the crossings on vertical edges as shown in question

Perhaps could look at dot product and if the normals are going in opposite directions, flip, or ignore, an example of flip.

#Rotate all faces to match the orientation of the last selected
for f in selfaces[:-1]:
n = f.normal
# print the angle axis of unflipped
q = n.rotation_difference(axis_src)
print(q.to_axis_angle())
print(f.normal.dot(axis_src))
# normals opposite so flip
if n.dot(axis_src) < 0:
n.negate()
q = n.rotation_difference(axis_src)
diff = q.to_matrix()
print(diff)
bmesh.ops.rotate(bm, cent=f.calc_center_median(), matrix=diff, verts=f.verts)


NOTE may require a more robust test for orthogonal face normals where dot product $$\approx$$ 0

If any selected faces are connected to the active face the resultant faces may not be aligned. An option here would be to look for these and hinge on the joining edge(s), or single vertex, rather than face center.

    verts = [v for v in f.verts if selfaces[-1]  in v.link_faces]
cent = (
sum((v.co for v in verts), mathutils.Vector()) / len(verts)
if verts else f.calc_center_median()
)
bmesh.ops.rotate(
bm,
cent=cent,
matrix=diff,
verts=f.verts,
)

• Thank you, this was really helpful and was exactly what I was looking for Sep 24, 2021 at 21:57