yyc12345
0bec108dcb
bring ballance virtools helper used fix_texture (renamed as fix material to correspond with the real object operated by this function) into blender plugin.
423 lines
14 KiB
Python
423 lines
14 KiB
Python
import bpy
|
|
import typing, enum
|
|
from . import UTIL_functions, UTIL_icons_manager
|
|
|
|
#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
|
|
__mNoChange: bool ##< A bool indicate whether any change happended during lifetime. If no change, skip the writing when exiting.
|
|
__mAssocObj: bpy.types.Object
|
|
__mGroupsSet: set[str]
|
|
|
|
def __init__(self, assoc: bpy.types.Object):
|
|
self.__mGroupsSet = set()
|
|
self.__mAssocObj = assoc
|
|
self.__mNoChange = True
|
|
|
|
# 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():
|
|
# if have changes,
|
|
# write to ballance elements property and reset validation
|
|
if not self.__mNoChange:
|
|
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.__mNoChange = False
|
|
self.__mGroupsSet.add(gname)
|
|
|
|
def add_groups(self, gnames: typing.Iterable[str]) -> None:
|
|
self.__check_valid()
|
|
self.__mNoChange = False
|
|
self.__mGroupsSet.update(gnames)
|
|
|
|
def remove_group(self, gname: str) -> None:
|
|
self.__check_valid()
|
|
self.__mNoChange = False
|
|
self.__mGroupsSet.discard(gname)
|
|
|
|
def remove_groups(self, gnames: typing.Iterable[str]) -> None:
|
|
self.__check_valid()
|
|
self.__mNoChange = False
|
|
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 intersect_groups(self, gnames: set[str]) -> set[str]:
|
|
self.__check_valid()
|
|
return self.__mGroupsSet.intersection(gnames)
|
|
|
|
def iterate_groups(self) -> typing.Iterator[str]:
|
|
self.__check_valid()
|
|
return iter(self.__mGroupsSet)
|
|
|
|
def clear_groups(self) -> None:
|
|
self.__check_valid()
|
|
self.__mNoChange = False
|
|
self.__mGroupsSet.clear()
|
|
|
|
def get_count(self) -> int:
|
|
self.__check_valid()
|
|
return len(self.__mGroupsSet)
|
|
|
|
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
|
|
|
|
class VirtoolsGroupsPreset(enum.Enum):
|
|
Sector_01 = "Sector_01"
|
|
Sector_02 = "Sector_02"
|
|
Sector_03 = "Sector_03"
|
|
Sector_04 = "Sector_04"
|
|
Sector_05 = "Sector_05"
|
|
Sector_06 = "Sector_06"
|
|
Sector_07 = "Sector_07"
|
|
Sector_08 = "Sector_08"
|
|
|
|
P_Extra_Life = "P_Extra_Life"
|
|
P_Extra_Point = "P_Extra_Point"
|
|
P_Trafo_Paper = "P_Trafo_Paper"
|
|
P_Trafo_Stone = "P_Trafo_Stone"
|
|
P_Trafo_Wood = "P_Trafo_Wood"
|
|
P_Ball_Paper = "P_Ball_Paper"
|
|
P_Ball_Stone = "P_Ball_Stone"
|
|
P_Ball_Wood = "P_Ball_Wood"
|
|
P_Box = "P_Box"
|
|
P_Dome = "P_Dome"
|
|
P_Modul_01 = "P_Modul_01"
|
|
P_Modul_03 = "P_Modul_03"
|
|
P_Modul_08 = "P_Modul_08"
|
|
P_Modul_17 = "P_Modul_17"
|
|
P_Modul_18 = "P_Modul_18"
|
|
P_Modul_19 = "P_Modul_19"
|
|
P_Modul_25 = "P_Modul_25"
|
|
P_Modul_26 = "P_Modul_26"
|
|
P_Modul_29 = "P_Modul_29"
|
|
P_Modul_30 = "P_Modul_30"
|
|
P_Modul_34 = "P_Modul_34"
|
|
P_Modul_37 = "P_Modul_37"
|
|
P_Modul_41 = "P_Modul_41"
|
|
|
|
PS_Levelstart = "PS_Levelstart"
|
|
PE_Levelende = "PE_Levelende"
|
|
PC_Checkpoints = "PC_Checkpoints"
|
|
PR_Resetpoints = "PR_Resetpoints"
|
|
|
|
Sound_HitID_01 = "Sound_HitID_01"
|
|
Sound_RollID_01 = "Sound_RollID_01"
|
|
Sound_HitID_02 = "Sound_HitID_02"
|
|
Sound_RollID_02 = "Sound_RollID_02"
|
|
Sound_HitID_03 = "Sound_HitID_03"
|
|
Sound_RollID_03 = "Sound_RollID_03"
|
|
|
|
DepthTestCubes = "DepthTestCubes"
|
|
|
|
Phys_Floors = "Phys_Floors"
|
|
Phys_FloorRails = "Phys_FloorRails"
|
|
Phys_FloorStopper = "Phys_FloorStopper"
|
|
|
|
Shadow = "Shadow"
|
|
|
|
_g_VtGrpPresetValues: tuple[str] = tuple(map(lambda x: x.value, VirtoolsGroupsPreset))
|
|
|
|
## Some of group names are not matched with icon name
|
|
# So we create a convertion map to convert them.
|
|
_g_GroupIconNameConvMap: dict[str, str] = {
|
|
"PS_Levelstart": "PS_FourFlames",
|
|
"PE_Levelende": "PE_Balloon",
|
|
"PC_Checkpoints": "PC_TwoFlames",
|
|
"PR_Resetpoints": "PR_Resetpoint",
|
|
|
|
"Sound_HitID_01": "SoundID_01",
|
|
"Sound_RollID_01": "SoundID_01",
|
|
"Sound_HitID_02": "SoundID_02",
|
|
"Sound_RollID_02": "SoundID_02",
|
|
"Sound_HitID_03": "SoundID_03",
|
|
"Sound_RollID_03": "SoundID_03"
|
|
}
|
|
def _get_group_icon_by_name(gp_name: str) -> int:
|
|
# try converting group name
|
|
# if not found, return self
|
|
gp_name = _g_GroupIconNameConvMap.get(gp_name, gp_name)
|
|
|
|
# get from extra group icon first
|
|
value: int | None = UTIL_icons_manager.get_group_icon(gp_name)
|
|
if value is not None: return value
|
|
|
|
# if failed, get from component. if still failed, return empty icon
|
|
value = UTIL_icons_manager.get_component_icon(gp_name)
|
|
if value is not None: return value
|
|
else: return UTIL_icons_manager.get_empty_icon()
|
|
# blender group name prop helper
|
|
_g_EnumHelper_Group: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
|
VirtoolsGroupsPreset,
|
|
lambda x: x.value, # member is string self
|
|
lambda x: VirtoolsGroupsPreset(x), # convert directly because it is StrEnum.
|
|
lambda x: x.value,
|
|
lambda _: '',
|
|
lambda x: _get_group_icon_by_name(x.value)
|
|
)
|
|
|
|
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 = _g_EnumHelper_Group.generate_items(),
|
|
)
|
|
|
|
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_EnumHelper_Group.get_selection(self.preset_group_name).value
|
|
|
|
#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_value = _get_group_icon_by_name(item.group_name))
|
|
|
|
class BBP_OT_add_virtools_group(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_group(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_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_group.bl_idname, icon='ADD', text="")
|
|
col.operator(BBP_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")
|
|
col.separator()
|
|
col.operator(BBP_OT_clear_virtools_groups.bl_idname, icon='TRASH', text="")
|
|
|
|
#endregion
|
|
|
|
def register() -> None:
|
|
# 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_group)
|
|
bpy.utils.register_class(BBP_OT_rm_virtools_group)
|
|
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() -> None:
|
|
# 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_group)
|
|
bpy.utils.unregister_class(BBP_OT_add_virtools_group)
|
|
bpy.utils.unregister_class(BBP_UL_virtools_groups)
|
|
bpy.utils.unregister_class(BBP_PG_virtools_group)
|