import bpy import typing from . import UTIL_functions #region Virtools Groups Define & Help Class class BBP_PG_virtools_group(bpy.types.PropertyGroup): group_name: bpy.props.StringProperty( name = "Group Name", default = "" ) def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty: return obj.virtools_groups def get_active_virtools_groups(obj: bpy.types.Object) -> int: return obj.active_virtools_groups def set_active_virtools_groups(obj: bpy.types.Object, val: int) -> None: obj.active_virtools_groups = val class VirtoolsGroupsHelper(): """ A helper for object's Virtools groups adding, removal and checking. All Virtools group operations should be done by this class. Do NOT manipulate object's Virtools group properties directly. """ __mSingletonMutex: typing.ClassVar[bool] = False __mIsValid: bool __mAssocObj: bpy.types.Object __mGroupsSet: set[str] def __init__(self, assoc: bpy.types.Object): self.__mGroupsSet = set() self.__mAssocObj = assoc # check singleton if VirtoolsGroupsHelper.__mSingletonMutex: self.__mIsValid = False raise UTIL_functions.BBPException('VirtoolsGroupsHelper is mutex.') # set validation and read ballance elements property VirtoolsGroupsHelper.__mSingletonMutex = True self.__mIsValid = True self.__read_from_virtools_groups() def is_valid(self) -> bool: return self.__mIsValid def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.dispose() def dispose(self) -> None: if self.is_valid(): # write to ballance elements property and reset validation self.__write_to_virtools_groups() self.__mIsValid = False VirtoolsGroupsHelper.__mSingletonMutex = False def __check_valid(self) -> None: if not self.is_valid(): raise UTIL_functions.BBPException('calling invalid VirtoolsGroupsHelper') def add_group(self, gname: str) -> None: self.__check_valid() self.__mGroupsSet.add(gname) def add_groups(self, gnames: typing.Iterable[str]) -> None: self.__check_valid() self.__mGroupsSet.update(gnames) def remove_group(self, gname: str) -> None: self.__check_valid() self.__mGroupsSet.discard(gname) def remove_groups(self, gnames: typing.Iterable[str]) -> None: self.__check_valid() for gname in gnames: self.__mGroupsSet.discard(gname) def contain_group(self, gname: str) -> bool: self.__check_valid() return gname in self.__mGroupsSet def contain_groups(self, gnames: typing.Iterable[str]) -> bool: """ Check existing intersection between group names and given collection. In other words, check whether group name of given paramter is in group names with OR operator. @param gnames[in] Iterable group names to check. @return return True if the length of the intersection between group names and given group names is not zero. """ self.__check_valid() for gname in gnames: if gname in self.__mGroupsSet: return True return False def clear_all_groups(self): self.__check_valid() self.__mGroupsSet.clear() def __write_to_virtools_groups(self) -> None: groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj) sel: int = get_active_virtools_groups(self.__mAssocObj) groups.clear() for gname in self.__mGroupsSet: item: BBP_PG_virtools_group = groups.add() item.group_name = gname # restore selection if necessary if sel >= len(self.__mGroupsSet): sel = len(self.__mGroupsSet) - 1 if sel < 0: sel = 0 set_active_virtools_groups(self.__mAssocObj, sel) def __read_from_virtools_groups(self) -> None: groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj) self.__mGroupsSet.clear() item: BBP_PG_virtools_group for item in groups: self.__mGroupsSet.add(item.group_name) #endregion #region Preset Group Names _g_VirtoolsGroupsPreset: tuple[str] = ( "Sector_01", "Sector_02", "Sector_03", "Sector_04", "Sector_05", "Sector_06", "Sector_07", "Sector_08", "P_Extra_Life", "P_Extra_Point", "P_Trafo_Paper", "P_Trafo_Stone", "P_Trafo_Wood", "P_Ball_Paper", "P_Ball_Stone", "P_Ball_Wood", "P_Box", "P_Dome", "P_Modul_01", "P_Modul_03", "P_Modul_08", "P_Modul_17", "P_Modul_18", "P_Modul_19", "P_Modul_25", "P_Modul_26", "P_Modul_29", "P_Modul_30", "P_Modul_34", "P_Modul_37", "P_Modul_41", "PS_Levelstart", "PE_Levelende", "PC_Checkpoints", "PR_Resetpoints", "Sound_HitID_01", "Sound_RollID_01", "Sound_HitID_02", "Sound_RollID_02", "Sound_HitID_03", "Sound_RollID_03", "DepthTestCubes", "Phys_Floors", "Phys_FloorRails", "Phys_FloorStopper", "Shadow" ) class SharedGroupNameInputProperties(): group_name_source: bpy.props.EnumProperty( name = "Group Name Source", items = ( ('DEFINED', "Predefined", "Pre-defined group name."), ('CUSTOM', "Custom", "User specified group name."), ), ) preset_group_name: bpy.props.EnumProperty( name="Group Name", description="Pick vanilla Ballance group name.", items=tuple( # token, display name, descriptions, icon, index (str(idx), grp, "", "", idx) for idx, grp in enumerate(_g_VirtoolsGroupsPreset) ), ) custom_group_name: bpy.props.StringProperty( name = "Custom Group Name", description = "Input your custom group name.", default = "", ) def draw_group_name_input(self, layout: bpy.types.UILayout) -> None: layout.prop(self, 'group_name_source', expand=True) if (self.group_name_source == 'CUSTOM'): layout.prop(self, 'custom_group_name') else: layout.prop(self, 'preset_group_name') def general_get_group_name(self) -> str: if self.group_name_source == 'CUSTOM': return self.custom_group_name else: return _g_VirtoolsGroupsPreset[int(self.preset_group_name)] #endregion #region Display Panel and Simple Operator class BBP_UL_virtools_groups(bpy.types.UIList): def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_virtools_group, icon, active_data, active_propname): layout.label(text = item.group_name, translate = False, icon = 'GROUP') class BBP_OT_add_virtools_groups(bpy.types.Operator, SharedGroupNameInputProperties): """Add a Virtools Group for Active Object.""" bl_idname = "bbp.add_virtools_groups" bl_label = "Add to Virtools Groups" bl_options = {'UNDO'} @classmethod def poll(self, context: bpy.types.Context): return context.object is not None def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) def execute(self, context): # add group with VirtoolsGroupsHelper(context.object) as hlp: hlp.add_group(self.general_get_group_name()) return {'FINISHED'} def draw(self, context): self.draw_group_name_input(self.layout) class BBP_OT_rm_virtools_groups(bpy.types.Operator): """Remove a Virtools Group for Active Object.""" bl_idname = "bbp.rm_virtools_groups" bl_label = "Remove from Virtools Groups" bl_options = {'UNDO'} ## This class is slightly unique. # Because we need get user selected group name first. # Then pass it to helper. @classmethod def poll(self, context: bpy.types.Context): if context.object is None: return False obj = context.object gp = get_virtools_groups(obj) active_gp = get_active_virtools_groups(obj) return active_gp >= 0 and active_gp < len(gp) def execute(self, context): # get selected group name first obj = context.object item: BBP_PG_virtools_group = get_virtools_groups(obj)[get_active_virtools_groups(obj)] gname: str = item.group_name # then delete it with VirtoolsGroupsHelper(obj) as hlp: hlp.remove_group(gname) return {'FINISHED'} class BBP_OT_clear_virtools_groups(bpy.types.Operator): """Clear All Virtools Group for Active Object.""" bl_idname = "bbp.clear_virtools_groups" bl_label = "Clear Virtools Groups" bl_options = {'UNDO'} @classmethod def poll(self, context: bpy.types.Context): return context.object is not None def invoke(self, context, event): wm = context.window_manager return wm.invoke_confirm(self, event) def execute(self, context): with VirtoolsGroupsHelper(context.object) as hlp: hlp.clear_all_groups() return {'FINISHED'} class BBP_PT_virtools_groups(bpy.types.Panel): """Show Virtools Groups Properties.""" bl_label = "Virtools Groups" bl_idname = "BBP_PT_virtools_groups" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "object" @classmethod def poll(cls, context): return context.object is not None def draw(self, context): layout = self.layout target = bpy.context.active_object row = layout.row() row.template_list( "BBP_UL_virtools_groups", "", target, "virtools_groups", target, "active_virtools_groups", rows = 6, maxrows = 6, ) col = row.column(align=True) col.operator(BBP_OT_add_virtools_groups.bl_idname, icon='ADD', text="") col.operator(BBP_OT_rm_virtools_groups.bl_idname, icon='REMOVE', text="") col.separator() col.operator(BBP_OT_clear_virtools_groups.bl_idname, icon='TRASH', text="") #endregion def register(): # register all classes bpy.utils.register_class(BBP_PG_virtools_group) bpy.utils.register_class(BBP_UL_virtools_groups) bpy.utils.register_class(BBP_OT_add_virtools_groups) bpy.utils.register_class(BBP_OT_rm_virtools_groups) bpy.utils.register_class(BBP_OT_clear_virtools_groups) bpy.utils.register_class(BBP_PT_virtools_groups) # add into scene metadata bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group) bpy.types.Object.active_virtools_groups = bpy.props.IntProperty() def unregister(): # del from scene metadata del bpy.types.Scene.active_virtools_groups del bpy.types.Scene.virtools_groups bpy.utils.unregister_class(BBP_PT_virtools_groups) bpy.utils.unregister_class(BBP_OT_clear_virtools_groups) bpy.utils.unregister_class(BBP_OT_rm_virtools_groups) bpy.utils.unregister_class(BBP_OT_add_virtools_groups) bpy.utils.unregister_class(BBP_UL_virtools_groups) bpy.utils.unregister_class(BBP_PG_virtools_group)