BallanceBlenderHelper/bbp_ng/UTIL_functions.py
yyc12345 84e7e8380f feat: hide some operators in edit mode and add some notify for grouping oper.
- now grouping, ungrouping clear groups operators will show a report at the bottom of blender to indicate their work have done.
- disable fix material, 3ds max align, select by virtools group operators in edit mode to prevent any possible undefined behavior.
2024-04-22 21:06:41 +08:00

174 lines
6.4 KiB
Python

import bpy, mathutils
import math, typing, enum, sys
class BBPException(Exception):
"""
The exception thrown by Ballance Blender Plugin
"""
pass
def clamp_float(v: float, min_val: float, max_val: float) -> float:
"""!
@brief Clamp a float value
@param v[in] The value need to be clamp.
@param min_val[in] The allowed minium value, including self.
@param max_val[in] The allowed maxium value, including self.
@return Clamped value.
"""
if (max_val < min_val): raise BBPException("Invalid range of clamp_float().")
if (v < min_val): return min_val
elif (v > max_val): return max_val
else: return v
def clamp_int(v: int, min_val: int, max_val: int) -> int:
"""!
@brief Clamp a int value
@param v[in] The value need to be clamp.
@param min_val[in] The allowed minium value, including self.
@param max_val[in] The allowed maxium value, including self.
@return Clamped value.
"""
if (max_val < min_val): raise BBPException("Invalid range of clamp_int().")
if (v < min_val): return min_val
elif (v > max_val): return max_val
else: return v
def message_box(message: tuple[str, ...], title: str, icon: str):
"""
Show a message box in Blender. Non-block mode.
@param message[in] The text this message box displayed. Each item in this param will show as a single line.
@param title[in] Message box title text.
@param icon[in] The icon this message box displayed.
"""
def draw(self, context: bpy.types.Context):
layout = self.layout
for item in message:
layout.label(text=item, translate=False)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def move_to_cursor(obj: bpy.types.Object):
# use obj.matrix_world to move, not obj.location because this bug:
# https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation
# the update of matrix_world after setting location is not immediately.
# and calling update() function for view_layer for the translation of each object is not suit for too much objects.
# obj.location = bpy.context.scene.cursor.location
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.Translation(bpy.context.scene.cursor.location - obj.location)
def add_into_scene_and_move_to_cursor(obj: bpy.types.Object):
view_layer = bpy.context.view_layer
collection = view_layer.active_layer_collection.collection
collection.objects.link(obj)
move_to_cursor(obj)
def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
# deselect all objects first
bpy.ops.object.select_all(action = 'DESELECT')
# if no objects, return
if len(objs) == 0: return
# set selection for each object
for obj in objs:
obj.select_set(True)
# select first object as active object
bpy.context.view_layer.objects.active = objs[0]
def is_in_object_mode() -> bool:
# get active object from context
obj = bpy.context.active_object
# if there is no active object, we think it is in object mode
if obj is None: return True
# simply check active object mode
return obj.mode == 'OBJECT'
class EnumPropHelper():
"""
These class contain all functions related to EnumProperty, including generating `items`,
parsing data from EnumProperty string value and getting EnumProperty acceptable string format from data.
"""
# define some type hint
_TFctToStr = typing.Callable[[typing.Any], str]
_TFctFromStr = typing.Callable[[str], typing.Any]
_TFctName = typing.Callable[[typing.Any], str]
_TFctDesc = typing.Callable[[typing.Any], str]
_TFctIcon = typing.Callable[[typing.Any], str | int]
# define class member
__mCollections: typing.Iterable[typing.Any]
__mFctToStr: _TFctToStr
__mFctFromStr: _TFctFromStr
__mFctName: _TFctName
__mFctDesc: _TFctDesc
__mFctIcon: _TFctIcon
def __init__(
self,
collections_: typing.Iterable[typing.Any],
fct_to_str: _TFctToStr,
fct_from_str: _TFctFromStr,
fct_name: _TFctName,
fct_desc: _TFctDesc,
fct_icon: _TFctIcon):
"""
Initialize a EnumProperty helper.
@param collections_ [in] The collection all available enum property entries contained.
It can be enum.Enum or a simple list/tuple/dict.
@param fct_to_str [in] A function pointer converting data collection member to its string format.
For enum.IntEnum, it can be simply `lambda x: str(x.value)`
@param fct_from_str [in] A function pointer getting data collection member from its string format.
For enum.IntEnum, it can be simply `lambda x: TEnum(int(x))`
@param fct_name [in] A function pointer converting data collection member to its display name.
@param fct_desc [in] Same as `fct_name` but return description instead. Return empty string, not None if no description.
@param fct_icon [in] Same as `fct_name` but return the used icon instead. Return empty string if no icon.
"""
# assign member
self.__mCollections = collections_
self.__mFctToStr = fct_to_str
self.__mFctFromStr = fct_from_str
self.__mFctName = fct_name
self.__mFctDesc = fct_desc
self.__mFctIcon = fct_icon
def generate_items(self) -> tuple[tuple[str, str, str, int | str, int], ...]:
"""
Generate a tuple which can be applied to Blender EnumProperty's "items".
"""
# blender enum prop item format:
# (token, display name, descriptions, icon, index)
return tuple(
(
self.__mFctToStr(member), # call to_str as its token.
self.__mFctName(member),
self.__mFctDesc(member),
self.__mFctIcon(member),
idx # use hardcode index, not the collection member self.
) for idx, member in enumerate(self.__mCollections)
)
def get_selection(self, prop: str) -> typing.Any:
"""
Return collection member from given Blender EnumProp string data.
"""
# call from_str fct ptr
return self.__mFctFromStr(prop)
def to_selection(self, val: typing.Any) -> str:
"""
Parse collection member to Blender EnumProp acceptable string format.
"""
# call to_str fct ptr
return self.__mFctToStr(val)