diff options
author | Jaggz H <jaggz> | 2022-02-15 07:42:56 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2022-02-15 07:53:39 +0300 |
commit | e648555eb6b11be56b08f2d10a16c3782271df92 (patch) | |
tree | e1b971e59f99f1719d6bd660e3179ed436265ca7 /object_print3d_utils/operators.py | |
parent | d03905c1bc0ddd447966c8e30a2a2567ecdd409a (diff) |
3D Print Toolbox: Add Align to XY Plane
Allow an object to be rotated so one face/selection can lie flat -
parallel to the - XY plane. This is useful for 3d printing setup. The
button is added in the 3d Print tools addon, in the transform section.
Reviewed By: campbellbarton
Ref D13094
Diffstat (limited to 'object_print3d_utils/operators.py')
-rw-r--r-- | object_print3d_utils/operators.py | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index 6da53219..3f66f07b 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -724,6 +724,77 @@ class MESH_OT_print3d_scale_to_bounds(Operator): return wm.invoke_props_dialog(self) +class MESH_OT_print3d_align_to_xy(Operator): + bl_idname = "mesh.print3d_align_to_xy" + bl_label = "Align (rotate) object to XY plane" + bl_description = ( + "Rotates entire object (not mesh) so the selected faces/vertices lie, on average, parallel to the XY plane " + "(it does not adjust Z location)" + ) + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + # FIXME: Undo is inconsistent. + # FIXME: Would be nicer if rotate could pick some object-local axis. + + from mathutils import Vector + + print_3d = context.scene.print_3d + face_areas = print_3d.use_alignxy_face_area + + self.context = context + mode_orig = context.mode + skip_invalid = [] + + for obj in context.selected_objects: + orig_loc = obj.location.copy() + orig_scale = obj.scale.copy() + + # When in edit mode, do as the edit mode does. + if mode_orig == 'EDIT_MESH': + bm = bmesh.from_edit_mesh(obj.data) + faces = [f for f in bm.faces if f.select] + else: + faces = [p for p in obj.data.polygons if p.select] + + face_count = len(faces) + if face_count < 1: + skip_invalid.append(obj.name) + continue + + # Rotate object so average normal of selected faces points down. + normal = Vector((0.0, 0.0, 0.0)) + if face_areas: + for face in faces: + normal += (face.normal * face.calc_area()) + else: + for face in faces: + normal += face.normal + normal = normal.normalized() + normal.rotate(obj.matrix_world) # local -> world. + offset = normal.rotation_difference(Vector((0.0, 0.0, -1.0))) + offset = offset.to_matrix().to_4x4() + obj.matrix_world = offset @ obj.matrix_world + obj.scale = orig_scale + obj.location = orig_loc + + if len(skip_invalid) > 0: + for name in skip_invalid: + print(f"Align to XY: Skipping object {name}. No faces selected.") + if len(skip_invalid) == 1: + self.report({'WARNING'}, f"Skipping object {skip_invalid[0]}. No faces selected.") + else: + self.report({'WARNING'}, f"Skipping some objects. No faces selected. See terminal.") + return {'FINISHED'} + + def invoke(self, context, event): + if context.mode in {'EDIT_MESH', 'OBJECT'}: + pass + else: + return {'CANCELLED'} + return self.execute(context) + + # ------ # Export |