blender/scripts/startup/bl_operators/object_align.py
Brecht Van Lommel 7a395e2e7f Revert changes from main commits that were merged into blender-v4.1-release
The last good commit was f57e4c5b98c075f3dfc61faebbcb43c99a778956.

After this one more fix was committed, this one is preserved as well:
67bd678887d7f8aec9f3b23bbf1aaf29f80d0da4.
2024-03-18 15:04:12 +01:00

408 lines
11 KiB
Python

# SPDX-FileCopyrightText: 2010-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import Operator
from mathutils import Vector
def worldspace_bounds_from_object_bounds(bb_world):
# Initialize the variables with the 8th vertex
left, right, front, back, down, up = (
bb_world[7][0],
bb_world[7][0],
bb_world[7][1],
bb_world[7][1],
bb_world[7][2],
bb_world[7][2],
)
# Test against the other 7 verts
for i in range(7):
# X Range
val = bb_world[i][0]
if val < left:
left = val
if val > right:
right = val
# Y Range
val = bb_world[i][1]
if val < front:
front = val
if val > back:
back = val
# Z Range
val = bb_world[i][2]
if val < down:
down = val
if val > up:
up = val
return (Vector((left, front, up)), Vector((right, back, down)))
def worldspace_bounds_from_object_data(depsgraph, obj):
matrix_world = obj.matrix_world.copy()
# Initialize the variables with the last vertex
ob_eval = obj.evaluated_get(depsgraph)
me = ob_eval.to_mesh()
verts = me.vertices
val = matrix_world @ (verts[-1].co if verts else Vector((0.0, 0.0, 0.0)))
left, right, front, back, down, up = (
val[0],
val[0],
val[1],
val[1],
val[2],
val[2],
)
# Test against all other verts
for v in verts:
vco = matrix_world @ v.co
# X Range
val = vco[0]
if val < left:
left = val
if val > right:
right = val
# Y Range
val = vco[1]
if val < front:
front = val
if val > back:
back = val
# Z Range
val = vco[2]
if val < down:
down = val
if val > up:
up = val
ob_eval.to_mesh_clear()
return Vector((left, front, up)), Vector((right, back, down))
def align_objects(context, align_x, align_y, align_z, align_mode, relative_to, bb_quality):
depsgraph = context.evaluated_depsgraph_get()
scene = context.scene
cursor = scene.cursor.location
# We are accessing runtime data such as evaluated bounding box, so we need to
# be sure it is properly updated and valid (bounding box might be lost on operator redo).
context.view_layer.update()
Left_Front_Up_SEL = [0.0, 0.0, 0.0]
Right_Back_Down_SEL = [0.0, 0.0, 0.0]
flag_first = True
objects = []
for obj in context.selected_objects:
matrix_world = obj.matrix_world.copy()
bb_world = [matrix_world @ Vector(v) for v in obj.bound_box]
objects.append((obj, bb_world))
if not objects:
return False
for obj, bb_world in objects:
if bb_quality and obj.type == 'MESH':
GBB = worldspace_bounds_from_object_data(depsgraph, obj)
else:
GBB = worldspace_bounds_from_object_bounds(bb_world)
Left_Front_Up = GBB[0]
Right_Back_Down = GBB[1]
# Active Center
if obj == context.active_object:
center_active_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
center_active_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
center_active_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
size_active_x = (Right_Back_Down[0] - Left_Front_Up[0]) / 2.0
size_active_y = (Right_Back_Down[1] - Left_Front_Up[1]) / 2.0
size_active_z = (Left_Front_Up[2] - Right_Back_Down[2]) / 2.0
# Selection Center
if flag_first:
flag_first = False
Left_Front_Up_SEL[0] = Left_Front_Up[0]
Left_Front_Up_SEL[1] = Left_Front_Up[1]
Left_Front_Up_SEL[2] = Left_Front_Up[2]
Right_Back_Down_SEL[0] = Right_Back_Down[0]
Right_Back_Down_SEL[1] = Right_Back_Down[1]
Right_Back_Down_SEL[2] = Right_Back_Down[2]
else:
# X axis
if Left_Front_Up[0] < Left_Front_Up_SEL[0]:
Left_Front_Up_SEL[0] = Left_Front_Up[0]
# Y axis
if Left_Front_Up[1] < Left_Front_Up_SEL[1]:
Left_Front_Up_SEL[1] = Left_Front_Up[1]
# Z axis
if Left_Front_Up[2] > Left_Front_Up_SEL[2]:
Left_Front_Up_SEL[2] = Left_Front_Up[2]
# X axis
if Right_Back_Down[0] > Right_Back_Down_SEL[0]:
Right_Back_Down_SEL[0] = Right_Back_Down[0]
# Y axis
if Right_Back_Down[1] > Right_Back_Down_SEL[1]:
Right_Back_Down_SEL[1] = Right_Back_Down[1]
# Z axis
if Right_Back_Down[2] < Right_Back_Down_SEL[2]:
Right_Back_Down_SEL[2] = Right_Back_Down[2]
center_sel_x = (Left_Front_Up_SEL[0] + Right_Back_Down_SEL[0]) / 2.0
center_sel_y = (Left_Front_Up_SEL[1] + Right_Back_Down_SEL[1]) / 2.0
center_sel_z = (Left_Front_Up_SEL[2] + Right_Back_Down_SEL[2]) / 2.0
# Main Loop
for obj, bb_world in objects:
matrix_world = obj.matrix_world.copy()
bb_world = [matrix_world @ Vector(v[:]) for v in obj.bound_box]
if bb_quality and obj.type == 'MESH':
GBB = worldspace_bounds_from_object_data(depsgraph, obj)
else:
GBB = worldspace_bounds_from_object_bounds(bb_world)
Left_Front_Up = GBB[0]
Right_Back_Down = GBB[1]
center_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
center_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
center_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
positive_x = Right_Back_Down[0]
positive_y = Right_Back_Down[1]
positive_z = Left_Front_Up[2]
negative_x = Left_Front_Up[0]
negative_y = Left_Front_Up[1]
negative_z = Right_Back_Down[2]
obj_loc = obj.location
if align_x:
# Align Mode
if relative_to == 'OPT_4': # Active relative
if align_mode == 'OPT_1':
obj_x = obj_loc[0] - negative_x - size_active_x
elif align_mode == 'OPT_3':
obj_x = obj_loc[0] - positive_x + size_active_x
else: # Everything else relative
if align_mode == 'OPT_1':
obj_x = obj_loc[0] - negative_x
elif align_mode == 'OPT_3':
obj_x = obj_loc[0] - positive_x
if align_mode == 'OPT_2': # All relative
obj_x = obj_loc[0] - center_x
# Relative To
if relative_to == 'OPT_1':
loc_x = obj_x
elif relative_to == 'OPT_2':
loc_x = obj_x + cursor[0]
elif relative_to == 'OPT_3':
loc_x = obj_x + center_sel_x
elif relative_to == 'OPT_4':
loc_x = obj_x + center_active_x
obj.location[0] = loc_x
if align_y:
# Align Mode
if relative_to == 'OPT_4': # Active relative
if align_mode == 'OPT_1':
obj_y = obj_loc[1] - negative_y - size_active_y
elif align_mode == 'OPT_3':
obj_y = obj_loc[1] - positive_y + size_active_y
else: # Everything else relative
if align_mode == 'OPT_1':
obj_y = obj_loc[1] - negative_y
elif align_mode == 'OPT_3':
obj_y = obj_loc[1] - positive_y
if align_mode == 'OPT_2': # All relative
obj_y = obj_loc[1] - center_y
# Relative To
if relative_to == 'OPT_1':
loc_y = obj_y
elif relative_to == 'OPT_2':
loc_y = obj_y + cursor[1]
elif relative_to == 'OPT_3':
loc_y = obj_y + center_sel_y
elif relative_to == 'OPT_4':
loc_y = obj_y + center_active_y
obj.location[1] = loc_y
if align_z:
# Align Mode
if relative_to == 'OPT_4': # Active relative
if align_mode == 'OPT_1':
obj_z = obj_loc[2] - negative_z - size_active_z
elif align_mode == 'OPT_3':
obj_z = obj_loc[2] - positive_z + size_active_z
else: # Everything else relative
if align_mode == 'OPT_1':
obj_z = obj_loc[2] - negative_z
elif align_mode == 'OPT_3':
obj_z = obj_loc[2] - positive_z
if align_mode == 'OPT_2': # All relative
obj_z = obj_loc[2] - center_z
# Relative To
if relative_to == 'OPT_1':
loc_z = obj_z
elif relative_to == 'OPT_2':
loc_z = obj_z + cursor[2]
elif relative_to == 'OPT_3':
loc_z = obj_z + center_sel_z
elif relative_to == 'OPT_4':
loc_z = obj_z + center_active_z
obj.location[2] = loc_z
return True
from bpy.props import (
BoolProperty,
EnumProperty,
)
class AlignObjects(Operator):
"""Align objects"""
bl_idname = "object.align"
bl_label = "Align Objects"
bl_options = {'REGISTER', 'UNDO'}
bb_quality: BoolProperty(
name="High Quality",
description=(
"Enables high quality but slow calculation of the "
"bounding box for perfect results on complex "
"shape meshes with rotation/scale"
),
default=True,
)
align_mode: EnumProperty(
name="Align Mode",
description="Side of object to use for alignment",
items=(
('OPT_1', "Negative Sides", ""),
('OPT_2', "Centers", ""),
('OPT_3', "Positive Sides", ""),
),
default='OPT_2',
)
relative_to: EnumProperty(
name="Relative To",
description="Reference location to align to",
items=(
('OPT_1', "Scene Origin", "Use the scene origin as the position for the selected objects to align to"),
('OPT_2', "3D Cursor", "Use the 3D cursor as the position for the selected objects to align to"),
('OPT_3', "Selection", "Use the selected objects as the position for the selected objects to align to"),
('OPT_4', "Active", "Use the active object as the position for the selected objects to align to"),
),
default='OPT_4',
)
align_axis: EnumProperty(
name="Align",
description="Align to axis",
items=(
('X', "X", ""),
('Y', "Y", ""),
('Z', "Z", ""),
),
options={'ENUM_FLAG'},
)
@classmethod
def poll(cls, context):
return context.mode == 'OBJECT'
def execute(self, context):
align_axis = self.align_axis
ret = align_objects(
context,
'X' in align_axis,
'Y' in align_axis,
'Z' in align_axis,
self.align_mode,
self.relative_to,
self.bb_quality,
)
if not ret:
self.report({'WARNING'}, "No objects with bound-box selected")
return {'CANCELLED'}
else:
return {'FINISHED'}
classes = (
AlignObjects,
)