yyc12345
3396947115
- add ballance map sector info in scene to indicate the maximum sector count of this map. - this adding will prevent the bug that the exported ballance map do not have successive sector groups. because original implement will not create sector group if no component in corresponding sector and previous remedy still have bug. and if this happended, ballance will show spaceship in wrong sector. this adding is the final solution of this bug. - exlarge ballance map sector info when user adding component. the enlarged value will be calculated by user input sector. - auto enlarge ballance map sector info when importing. this will give user a fluent experience when modifying existing map. - exporting map will also use ballance map sector info to pre-create successive sector group as term 2 stated. - move sector name extractor from virtools file exporting module to naming convention module.
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 = ""
|
|
) # type: ignore
|
|
|
|
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 object 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 object metadata
|
|
del bpy.types.Object.active_virtools_groups
|
|
del bpy.types.Object.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)
|