03806d0b67
This commit implements described in the #104573. The goal is to fix the confusion of the submodule hashes change, which are not ideal for any of the supported git-module configuration (they are either always visible causing confusion, or silently staged and committed, also causing confusion). This commit replaces submodules with a checkout of addons and addons_contrib, covered by the .gitignore, and locale and developer tools are moved to the main repository. This also changes the paths: - /release/scripts are moved to the /scripts - /source/tools are moved to the /tools - /release/datafiles/locale is moved to /locale This is done to avoid conflicts when using bisect, and also allow buildbot to automatically "recover" wgen building older or newer branches/patches. Running `make update` will initialize the local checkout to the changed repository configuration. Another aspect of the change is that the make update will support Github style of remote organization (origin remote pointing to thy fork, upstream remote pointing to the upstream blender/blender.git). Pull Request #104755
413 lines
11 KiB
Python
413 lines
11 KiB
Python
# 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,
|
|
)
|