feat: allow 3D Cursor as align source in legacy align operator.
- allow 3D Cursor as align source in legacy align operator. this feature is requested by Zzq. - add icon for legacy align.
This commit is contained in:
@ -9,11 +9,11 @@ class AlignMode(enum.IntEnum):
|
|||||||
BBoxCenter = enum.auto()
|
BBoxCenter = enum.auto()
|
||||||
AxisCenter = enum.auto()
|
AxisCenter = enum.auto()
|
||||||
Max = enum.auto()
|
Max = enum.auto()
|
||||||
_g_AlignModeDesc: dict[AlignMode, tuple[str, str]] = {
|
_g_AlignModeDesc: dict[AlignMode, tuple[str, str, str]] = {
|
||||||
AlignMode.Min: ("Min", "The min value in specified axis."),
|
AlignMode.Min: ("Min", "The min value in specified axis.", "REMOVE"),
|
||||||
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis."),
|
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis.", "SHADING_BBOX"),
|
||||||
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis."),
|
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis.", "OBJECT_ORIGIN"),
|
||||||
AlignMode.Max: ("Max", "The max value in specified axis."),
|
AlignMode.Max: ("Max", "The max value in specified axis.", "ADD"),
|
||||||
}
|
}
|
||||||
_g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
_g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
||||||
AlignMode,
|
AlignMode,
|
||||||
@ -21,7 +21,23 @@ _g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
|||||||
lambda x: AlignMode(int(x)),
|
lambda x: AlignMode(int(x)),
|
||||||
lambda x: _g_AlignModeDesc[x][0],
|
lambda x: _g_AlignModeDesc[x][0],
|
||||||
lambda x: _g_AlignModeDesc[x][1],
|
lambda x: _g_AlignModeDesc[x][1],
|
||||||
lambda _: ''
|
lambda x: _g_AlignModeDesc[x][2]
|
||||||
|
)
|
||||||
|
|
||||||
|
class CurrentInstance(enum.IntEnum):
|
||||||
|
ActiveObject = enum.auto()
|
||||||
|
Cursor = enum.auto()
|
||||||
|
_g_CurrentInstanceDesc: dict[CurrentInstance, tuple[str, str, str]] = {
|
||||||
|
CurrentInstance.ActiveObject: ("Active Object", "Use Active Object as Current Object", "OBJECT_DATA"),
|
||||||
|
CurrentInstance.Cursor: ("3D Cursor", "Use 3D Cursor as Current Object", "CURSOR"),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_CurrentInstance = UTIL_functions.EnumPropHelper(
|
||||||
|
CurrentInstance,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: CurrentInstance(int(x)),
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][0],
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][1],
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][2]
|
||||||
)
|
)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -55,14 +71,23 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
|
|||||||
default = False,
|
default = False,
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
current_instance: bpy.props.EnumProperty(
|
||||||
|
name = "Current Instance",
|
||||||
|
description = "Decide which instance should be used as Current Object",
|
||||||
|
items = _g_EnumHelper_CurrentInstance.generate_items(),
|
||||||
|
default = _g_EnumHelper_CurrentInstance.to_selection(CurrentInstance.ActiveObject),
|
||||||
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
|
) # type: ignore
|
||||||
current_align_mode: bpy.props.EnumProperty(
|
current_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Current Object (Active Object)",
|
name = "Current Object",
|
||||||
|
description = "The align mode applied to Current Object",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
target_align_mode: bpy.props.EnumProperty(
|
target_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Target Objects (Selected Objects)",
|
name = "Target Objects",
|
||||||
|
description = "The align mode applied to Target Objects (selected objects except active object if Current Instance is active object)",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
@ -148,7 +173,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# get processed objects
|
# get processed objects
|
||||||
(current_obj, target_objs) = _prepare_objects()
|
(current_obj, current_cursor, target_objs) = _prepare_objects()
|
||||||
# YYC MARK:
|
# YYC MARK:
|
||||||
# This statement is VERY IMPORTANT.
|
# This statement is VERY IMPORTANT.
|
||||||
# If this statement is not presented, Blender will return identity matrix
|
# If this statement is not presented, Blender will return identity matrix
|
||||||
@ -162,7 +187,8 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
for entry in histories:
|
for entry in histories:
|
||||||
_align_objects(
|
_align_objects(
|
||||||
current_obj, target_objs,
|
_g_EnumHelper_CurrentInstance.get_selection(entry.current_instance),
|
||||||
|
current_obj, current_cursor, target_objs,
|
||||||
entry.align_x, entry.align_y, entry.align_z,
|
entry.align_x, entry.align_y, entry.align_z,
|
||||||
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
|
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
|
||||||
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
|
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
|
||||||
@ -185,10 +211,21 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
row.prop(entry, "align_y", toggle = 1)
|
row.prop(entry, "align_y", toggle = 1)
|
||||||
row.prop(entry, "align_z", toggle = 1)
|
row.prop(entry, "align_z", toggle = 1)
|
||||||
|
|
||||||
# show mode
|
# show current instance
|
||||||
col.separator()
|
col.separator()
|
||||||
col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw')
|
col.label(text='Current Instance', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
col.prop(entry, "current_align_mode", expand = True)
|
# it should be shown in horizon so we create a new sublayout
|
||||||
|
row = col.row()
|
||||||
|
row.prop(entry, 'current_instance', expand=True)
|
||||||
|
|
||||||
|
# show instance and mode
|
||||||
|
col.separator()
|
||||||
|
# only show current object mode if current instance is active object,
|
||||||
|
# because there is no mode for 3d cursor.
|
||||||
|
current_instnce = _g_EnumHelper_CurrentInstance.get_selection(entry.current_instance)
|
||||||
|
if current_instnce == CurrentInstance.ActiveObject:
|
||||||
|
col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
|
col.prop(entry, "current_align_mode", expand = True)
|
||||||
col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw')
|
col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
col.prop(entry, "target_align_mode", expand = True)
|
col.prop(entry, "target_align_mode", expand = True)
|
||||||
|
|
||||||
@ -206,44 +243,66 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
#region Core Functions
|
#region Core Functions
|
||||||
|
|
||||||
def _check_align_requirement() -> bool:
|
def _check_align_requirement() -> bool:
|
||||||
# if we are not in object mode, do not do legacy align
|
# If we are not in object mode, do not do legacy align
|
||||||
if not UTIL_functions.is_in_object_mode():
|
if not UTIL_functions.is_in_object_mode():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check current obj
|
# YYC MARK:
|
||||||
|
# We still need to check active object (as current object)
|
||||||
|
# although we can choose align with active object or 3d cursor.
|
||||||
|
# Because we can not make any promise that user will
|
||||||
|
# select Active Object or 3D Cursor as current object before executing this operator.
|
||||||
if bpy.context.active_object is None:
|
if bpy.context.active_object is None:
|
||||||
return False
|
return False
|
||||||
# check target obj with filter of current obj
|
|
||||||
length = len(bpy.context.selected_objects)
|
# YYC MARK:
|
||||||
if bpy.context.active_object in bpy.context.selected_objects:
|
# Roughly check selected objects.
|
||||||
length -= 1
|
# We do not need exclude active object from selected objects,
|
||||||
return length != 0
|
# because active object may be moved when 3D Cursor is current object.
|
||||||
|
if len(bpy.context.selected_objects) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _prepare_objects() -> tuple[bpy.types.Object, set[bpy.types.Object]]:
|
def _prepare_objects() -> tuple[bpy.types.Object, mathutils.Vector, list[bpy.types.Object]]:
|
||||||
# get current object
|
# Fetch current object
|
||||||
current_obj: bpy.types.Object = bpy.context.active_object
|
current_obj = typing.cast(bpy.types.Object, bpy.context.active_object)
|
||||||
|
|
||||||
# get target objects
|
# Fetch 3d cursor location
|
||||||
target_objs: set[bpy.types.Object] = set(bpy.context.selected_objects)
|
current_cursor: mathutils.Vector = bpy.context.scene.cursor.location
|
||||||
# remove active one
|
|
||||||
if current_obj in target_objs:
|
# YYC MARK:
|
||||||
target_objs.remove(current_obj)
|
# Fetch target objects and do NOT remove active object from it.
|
||||||
|
# because active object will be moved when current instance is 3D Cursor.
|
||||||
|
target_objs: list[bpy.types.Object] = bpy.context.selected_objects[:]
|
||||||
|
|
||||||
# return value
|
# return value
|
||||||
return (current_obj, target_objs)
|
return (current_obj, current_cursor, target_objs)
|
||||||
|
|
||||||
def _align_objects(
|
def _align_objects(
|
||||||
current_obj: bpy.types.Object, target_objs: set[bpy.types.Object],
|
current_instance: CurrentInstance,
|
||||||
|
current_obj: bpy.types.Object, current_cursor: mathutils.Vector, target_objs: list[bpy.types.Object],
|
||||||
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
|
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
|
||||||
# if no align, skip
|
# if no align, skip
|
||||||
if not (align_x or align_y or align_z):
|
if not (align_x or align_y or align_z):
|
||||||
return
|
return
|
||||||
|
|
||||||
# calc current object data
|
# calc current object data
|
||||||
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_mode)
|
current_obj_ref: mathutils.Vector
|
||||||
|
match current_instance:
|
||||||
|
case CurrentInstance.ActiveObject:
|
||||||
|
current_obj_ref = _get_object_ref_point(current_obj, current_mode)
|
||||||
|
case CurrentInstance.Cursor:
|
||||||
|
current_obj_ref = current_cursor
|
||||||
|
|
||||||
# process each target obj
|
# process each target obj
|
||||||
for target_obj in target_objs:
|
for target_obj in target_objs:
|
||||||
|
# YYC MARK:
|
||||||
|
# If we use active object as current instance, we need exclude it from target objects,
|
||||||
|
# because there is no pre-exclude considering the scenario that 3D Cursor is current instance.
|
||||||
|
if current_instance == CurrentInstance.ActiveObject and current_obj == target_obj:
|
||||||
|
continue
|
||||||
|
|
||||||
# calc target object data
|
# calc target object data
|
||||||
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
|
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
|
||||||
# build translation transform
|
# build translation transform
|
||||||
@ -256,21 +315,21 @@ def _align_objects(
|
|||||||
# apply translation transform to left side (add into original matrix)
|
# apply translation transform to left side (add into original matrix)
|
||||||
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
|
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
|
||||||
|
|
||||||
bpy.context.scene.update_tag
|
|
||||||
|
|
||||||
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
|
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
|
||||||
ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
ref_pos = mathutils.Vector((0, 0, 0))
|
||||||
|
|
||||||
# calc bounding box data
|
# calc bounding box data
|
||||||
corners: tuple[mathutils.Vector] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
|
corners: tuple[mathutils.Vector, ...] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
|
||||||
bbox_min_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
bbox_min_corner = mathutils.Vector((
|
||||||
bbox_min_corner.x = min((vec.x for vec in corners))
|
min((vec.x for vec in corners)),
|
||||||
bbox_min_corner.y = min((vec.y for vec in corners))
|
min((vec.y for vec in corners)),
|
||||||
bbox_min_corner.z = min((vec.z for vec in corners))
|
min((vec.z for vec in corners)),
|
||||||
bbox_max_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
))
|
||||||
bbox_max_corner.x = max((vec.x for vec in corners))
|
bbox_max_corner = mathutils.Vector((
|
||||||
bbox_max_corner.y = max((vec.y for vec in corners))
|
max((vec.x for vec in corners)),
|
||||||
bbox_max_corner.z = max((vec.z for vec in corners))
|
max((vec.y for vec in corners)),
|
||||||
|
max((vec.z for vec in corners)),
|
||||||
|
))
|
||||||
|
|
||||||
# return value by given align mode
|
# return value by given align mode
|
||||||
match(mode):
|
match(mode):
|
||||||
|
Reference in New Issue
Block a user