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
This commit is contained in:
Jaggz H 2022-02-15 15:42:56 +11:00 committed by Campbell Barton
parent d03905c1bc
commit e648555eb6
3 changed files with 83 additions and 0 deletions

View File

@ -42,6 +42,12 @@ else:
class SceneProperties(PropertyGroup):
use_alignxy_face_area: BoolProperty(
name="Face Areas",
description="Normalize normals proportional to face areas",
default=False,
)
export_format: EnumProperty(
name="Format",
description="Format type to export to",
@ -141,6 +147,7 @@ classes = (
operators.MESH_OT_print3d_select_report,
operators.MESH_OT_print3d_scale_to_volume,
operators.MESH_OT_print3d_scale_to_bounds,
operators.MESH_OT_print3d_align_to_xy,
operators.MESH_OT_print3d_export,
)

View File

@ -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

View File

@ -109,10 +109,15 @@ class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel):
def draw(self, context):
layout = self.layout
print_3d = context.scene.print_3d
layout.label(text="Scale To")
row = layout.row(align=True)
row.operator("mesh.print3d_scale_to_volume", text="Volume")
row.operator("mesh.print3d_scale_to_bounds", text="Bounds")
row = layout.row(align=True)
row.operator("mesh.print3d_align_to_xy", text="Align to XY Plane")
row.prop(print_3d, "use_alignxy_face_area")
class VIEW3D_PT_print3d_export(View3DPrintPanel, Panel):