BallanceBlenderHelper/bbp_ng/PROP_ballance_element.py

292 lines
9.1 KiB
Python

import bpy
import os, typing, enum
from . import UTIL_functions, UTIL_blender_mesh
#region Raw Elements Operations
class BallanceElementType(enum.IntEnum):
P_Extra_Life = 0
P_Extra_Point = 1
P_Trafo_Paper = 2
P_Trafo_Stone = 3
P_Trafo_Wood = 4
P_Ball_Paper = 5
P_Ball_Stone = 6
P_Ball_Wood = 7
P_Box = 8
P_Dome = 9
P_Modul_01 = 10
P_Modul_03 = 11
P_Modul_08 = 12
P_Modul_17 = 13
P_Modul_18 = 14
P_Modul_19 = 15
P_Modul_25 = 16
P_Modul_26 = 17
P_Modul_29 = 18
P_Modul_30 = 19
P_Modul_34 = 20
P_Modul_37 = 21
P_Modul_41 = 22
PC_TwoFlames = 23
PE_Balloon = 24
PR_Resetpoint = 25
PS_FourFlames = 26
_g_ElementCount: int = len(BallanceElementType)
_g_ElementNameIdMap: dict[str, int] = dict((entry.name, entry.value) for entry in BallanceElementType)
_g_ElementIdNameMap: dict[str, int] = dict((entry.value, entry.name) for entry in BallanceElementType)
def get_ballance_element_id(name: str) -> int | None:
"""
Get Ballance element ID by its name.
@param name[in] The name of element
@return the ID of this Ballance element name distributed by this plugin. or None if providing name is invalid.
"""
return _g_ElementNameIdMap.get(name, None)
def get_ballance_element_name(id: int) -> int | None:
"""
Get Ballance element name by its ID
@param id[in] The ID of element
@return the name of this Ballance element, or None if ID is invalid.
"""
return _g_ElementIdNameMap.get(id, None)
def is_ballance_element(name: str) -> bool:
"""
Check whether providing name is Ballance element.
Just a wrapper of get_ballance_element_id
@param name[in] The name of element
@return True if providing name is Ballance element name.
"""
return get_ballance_element_id(name) is not None
#endregion
#region Ballance Elements Define & Visitor
class BBP_PG_ballance_element(bpy.types.PropertyGroup):
element_name: bpy.props.StringProperty(
name = "Element Name",
default = ""
)
mesh_ptr: bpy.props.PointerProperty(
name = "Mesh",
type = bpy.types.Mesh
)
def get_ballance_elements() -> bpy.types.CollectionProperty:
return bpy.context.scene.ballance_elements
#endregion
#region
def load_element(mesh: bpy.types.Mesh, element_id: int) -> None:
pass
#endregion
#region Ballance Elements Operation Help Class & Functions
class BallanceElementsHelper():
"""
The helper of Ballance elements processing.
All element operations, including getting or setting, must be manipulated by this class.
You should NOT operate Ballance Elements property (in Scene) directly.
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
This class frequently used in importing stage to create element placeholder.
"""
__mSingletonMutex: typing.ClassVar[bool] = False
__mIsValid: bool
__mElementMap: dict[int, bpy.types.Mesh]
def __init__(self):
self.__mElementMap = {}
# check singleton
if BallanceElementsHelper.__mSingletonMutex:
self.__mIsValid = False
raise UTIL_functions.BBPException('BallanceElementsHelper is mutex.')
# set validation and read ballance elements property
BallanceElementsHelper.__mSingletonMutex = True
self.__mIsValid = True
self.__read_from_ballance_element()
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_ballance_elements()
self.__mIsValid = False
self.__mSingletonMutex = False
def get_element(self, element_id: int) -> bpy.types.Mesh:
if not self.is_valid():
raise UTIL_functions.BBPException('calling invalid BallanceElementsHelper')
# get exist one
mesh: bpy.types.Mesh | None = self.__mElementMap.get(element_id, None)
if mesh is not None:
return mesh
# if no existing one, create new one
new_mesh_name: str | None = get_ballance_element_id(element_id)
if new_mesh_name is None:
raise UTIL_functions.BBPException('invalid element id')
new_mesh: bpy.types.Mesh = bpy.data.meshes.new(get_ballance_element_name(element_id))
load_element(new_mesh, element_id)
self.__mElementMap[element_id] = new_mesh
return new_mesh
def __write_to_ballance_elements(self) -> None:
elements: bpy.types.CollectionProperty = get_ballance_elements()
elements.clear()
for eleid, elemesh in self.__mElementMap.items():
name: str | None = get_ballance_element_name(eleid)
if name is None:
continue
item: BBP_PG_ballance_element = elements.add()
item.element_name = name
item.mesh_ptr = elemesh
def __read_from_ballance_element(self) -> None:
elements: bpy.types.CollectionProperty = get_ballance_elements()
self.__mElementMap.clear()
item: BBP_PG_ballance_element
for item in elements:
# check requirements
if item.mesh_ptr is None: continue
mesh_id: int | None = get_ballance_element_id(item.element_name)
if mesh_id is None: continue
# add into map
self.__mElementMap[mesh_id] = item.mesh_ptr
def reset_ballance_elements() -> None:
invalid_idx: list[int] = []
elements: bpy.types.CollectionProperty = get_ballance_elements()
# re-load all elements
index: int = 0
item: BBP_PG_ballance_element
for item in elements:
eleid: int | None = get_ballance_element_id(item.element_name)
# load or record invalid entry
if eleid is None or item.mesh_ptr is None:
invalid_idx.append(index)
else:
load_element(item.mesh_ptr, eleid)
# inc counter
index += 1
# remove invalid one with reversed order
invalid_idx.reverse()
for idx in invalid_idx:
elements.remove(idx)
#endregion
#region Ballance Elements Representation
class BBP_UL_ballance_elements(bpy.types.UIList):
def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_ballance_element, icon, active_data, active_propname):
if item.element_name != "" and item.mesh_ptr is not None:
layout.label(text = item.element_name)
layout.label(text = item.mesh_ptr, icon = 'MESH_DATA')
class BBP_OT_reset_ballance_elements(bpy.types.Operator):
"""Reset all Meshes of Loaded Ballance Elements to Original Geometry."""
bl_idname = "bbp.reset_ballance_elements"
bl_label = "Reset Ballance Elements"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
reset_ballance_elements()
return {'FINISHED'}
class BBP_PT_ballance_elements(bpy.types.Panel):
"""Show Ballance Elements Properties."""
bl_label = "Ballance Elements"
bl_idname = "BBP_PT_ballance_elements"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
@classmethod
def poll(cls, context):
return context.scene is not None
def draw(self, context):
layout: bpy.types.UILayout = self.layout
target: bpy.types.Scene = context.scene
col = layout.column()
# show restore operator
opercol = col.column()
opercol.operator(BBP_OT_reset_ballance_elements.bl_idname, icon='LOOP_BACK')
# show list but not allowed to edit
listcol = col.column()
listcol.enabled = False
listcol.template_list(
"BBP_UL_ballance_elements", "",
target, "ballance_elements",
target, "active_ballance_elements",
# default row height is a half of the count of all elements
# limit the max row height to the the count of all elements
rows = _g_ElementCount // 2,
maxrows = _g_ElementCount,
)
#endregion
def register():
# register all classes
bpy.utils.register_class(BBP_PG_ballance_element)
bpy.utils.register_class(BBP_UL_ballance_elements)
bpy.utils.register_class(BBP_OT_reset_ballance_elements)
bpy.utils.register_class(BBP_PT_ballance_elements)
# add into scene metadata
bpy.types.Scene.ballance_elements = bpy.props.CollectionProperty(type = BBP_PG_ballance_element)
bpy.types.Scene.active_ballance_elements = bpy.props.IntProperty()
def unregister():
# del from scene metadata
del bpy.types.Scene.active_ballance_elements
del bpy.types.Scene.ballance_elements
bpy.utils.unregister_class(BBP_PT_ballance_elements)
bpy.utils.unregister_class(BBP_OT_reset_ballance_elements)
bpy.utils.unregister_class(BBP_UL_ballance_elements)
bpy.utils.unregister_class(BBP_PG_ballance_element)