Files
blender/release/scripts/startup/bl_operators/object_align.py
Sergey Sharybin 76e7c039ec Fix T52140: Align objects centers using origin for text
The issue was caused by operator redo which frees all object's evaluated data,
including bounding box. This bounding box can not be reconstructed properly
without full curve evaluation (need to at least convert font to nurbs, which is
not cheap already).
2017-09-13 17:40:11 +05:00

427 lines
12 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8-80 compliant>
import bpy
from bpy.types import Operator
from mathutils import Vector
def GlobalBB_LQ(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 GlobalBB_HQ(obj):
matrix_world = obj.matrix_world.copy()
# Initialize the variables with the last vertex
me = obj.to_mesh(scene=bpy.context.scene, apply_modifiers=True, settings='PREVIEW')
verts = me.vertices
val = matrix_world * verts[-1].co
left, right, front, back, down, up = (val[0],
val[0],
val[1],
val[1],
val[2],
val[2],
)
# Test against all other verts
for i in range(len(verts) - 1):
vco = matrix_world * verts[i].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
bpy.data.meshes.remove(me)
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):
scene = context.scene
space = context.space_data
cursor = (space if space and space.type == 'VIEW_3D' else 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).
scene.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 = GlobalBB_HQ(obj)
else:
GBB = GlobalBB_LQ(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 = GlobalBB_HQ(obj)
else:
GBB = GlobalBB_LQ(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 (
EnumProperty,
BoolProperty
)
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 calculation of the "
"bounding box for perfect results on complex "
"shape meshes with rotation/scale (Slow)"),
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,
)