It looks like some obscure bug in the underlying undo system, but much of it can be circumvented by replacing many of the operator calls with lower level functions. It usually simplifies the execution chain and often also guarantees that Blender doesn’t inadvertently push an undo at stage where it does weird things when operator redo is being run.
An issue with your script is that you’re toggling modes. This is hit or miss with multiple operators and undo.
If you’re trying to align a mesh to a surface while supporting operator redo, it’s best to do a low-level version that doesn’t include the usage of transform orientations. It’s better to create the transform using a matrix.
The rundown of the example below:
- Use bmesh to get the reference face’s normal and center point and convert these to a matrix
- Add the cylinder in edit mode
- Transform the cylinder’s vertices using the new matrix
Example:
import bpy
import bmesh
class MESH_OT_add_cylinder_on_surface(bpy.types.Operator):
bl_idname = 'mesh.add_cylinder_on_face'
bl_label = 'Add Cylinder On Face'
bl_options = {'REGISTER', 'UNDO'}
verts: bpy.props.IntProperty(min=3, default=32)
@classmethod
def poll(self, context):
a = context.area.type == 'VIEW_3D'
b = context.objects_in_mode
return a and b
def execute(self, context):
bm = bmesh.from_edit_mesh(context.object.data)
mat = None
# for purpose of example, get the matrix from a single face.
# could be expanded to take a normal average of selected faces.
for face in bm.faces:
if face.select:
mat = face.normal.to_track_quat('Z', 'Y').to_matrix().to_4x4()
mat.translation = face.calc_center_median()
break
if mat:
bpy.ops.mesh.primitive_cylinder_add(vertices=self.verts)
# at this point the new mesh is already selected (and we still have the bmesh)
for vert in bm.verts:
if vert.select:
# subtract cursor location since the add
# primitve operator uses it as mesh origin
vert.co = mat @ (vert.co - context.scene.cursor.location)
bm.normal_update()
return {'FINISHED'}
def register():
bpy.utils.register_class(MESH_OT_add_cylinder_on_surface)
if __name__ == '__main__':
register()
Edit: Simplified a bit