yyc12345
f4d3e48be2
- add TinyMutex to resolve the issue that we can not operate the virtools group infos of 2 individual objects. * use this new class for all class need resource mutex, such as ballance element, bme materials and etc. - add CollectionVisitor to resolve that blender have bad document and type hint for runtime bpy.types.CollectionProperty. * now all visit to CollectionProperty are delegated by this class.
397 lines
14 KiB
Python
397 lines
14 KiB
Python
import bpy
|
|
import os, typing, enum, array
|
|
from . import PROP_virtools_mesh
|
|
from . import UTIL_functions, UTIL_file_io, UTIL_blender_mesh, UTIL_virtools_types, UTIL_icons_manager
|
|
|
|
#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)
|
|
|
|
def get_ballance_element_type_from_id(id: int) -> BallanceElementType | None:
|
|
"""
|
|
Get Ballance element type by its id.
|
|
|
|
@param id[in] The id of element
|
|
@return the type of this Ballance element name distributed by this plugin. or None if providing id is invalid.
|
|
"""
|
|
try:
|
|
return BallanceElementType(id) # https://docs.python.org/zh-cn/3/library/enum.html#enum.EnumType.__call__
|
|
except ValueError:
|
|
return None
|
|
|
|
def get_ballance_element_type_from_name(name: str) -> BallanceElementType | None:
|
|
"""
|
|
Get Ballance element type by its name.
|
|
|
|
@param name[in] The name of element
|
|
@return the type of this Ballance element name distributed by this plugin. or None if providing name is invalid.
|
|
"""
|
|
try:
|
|
return BallanceElementType[name] # https://docs.python.org/zh-cn/3/library/enum.html#enum.EnumType.__getitem__
|
|
except KeyError:
|
|
return None
|
|
|
|
def get_ballance_element_id(ty: BallanceElementType) -> int:
|
|
"""
|
|
Get Ballance element id by its type
|
|
|
|
@param ty[in] The type of element
|
|
@return the id of this Ballance element.
|
|
"""
|
|
return ty.value
|
|
|
|
def get_ballance_element_name(ty: BallanceElementType) -> str:
|
|
"""
|
|
Get Ballance element name by its type
|
|
|
|
@param ty[in] The type of element
|
|
@return the name of this Ballance element.
|
|
"""
|
|
return ty.name
|
|
|
|
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_type_from_name(name) is not None
|
|
|
|
#endregion
|
|
|
|
#region Ballance Elements Define & Visitor
|
|
|
|
class BBP_PG_ballance_element(bpy.types.PropertyGroup):
|
|
element_id: bpy.props.IntProperty(
|
|
name = "Element Id",
|
|
default = 0
|
|
) # type: ignore
|
|
|
|
mesh_ptr: bpy.props.PointerProperty(
|
|
name = "Mesh",
|
|
type = bpy.types.Mesh
|
|
) # type: ignore
|
|
|
|
def get_ballance_elements(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_ballance_element]:
|
|
return UTIL_functions.CollectionVisitor(scene.ballance_elements)
|
|
|
|
#endregion
|
|
|
|
#region Element Loader
|
|
|
|
def _save_element(mesh: bpy.types.Mesh, filename: str) -> None:
|
|
# todo: if we need add element placeholder save operator,
|
|
# write this function and call this function in operator.
|
|
pass
|
|
|
|
def _load_element(mesh: bpy.types.Mesh, element_type: BallanceElementType) -> None:
|
|
# resolve mesh path
|
|
element_name: str = get_ballance_element_name(element_type)
|
|
element_filename: str = os.path.join(
|
|
os.path.dirname(__file__),
|
|
"meshes",
|
|
element_name + '.bin'
|
|
)
|
|
|
|
# open file and read
|
|
with open(element_filename, 'rb') as fmesh:
|
|
# prepare container
|
|
vpos: array.array = array.array('f')
|
|
vnml: array.array = array.array('f')
|
|
face: array.array = array.array('L')
|
|
|
|
# read data
|
|
# position is vector3
|
|
vpos_count = UTIL_file_io.read_uint32(fmesh)
|
|
vpos.extend(UTIL_file_io.read_float_array(fmesh, vpos_count * 3))
|
|
# normal is vector3
|
|
vnml_count = UTIL_file_io.read_uint32(fmesh)
|
|
vnml.extend(UTIL_file_io.read_float_array(fmesh, vnml_count * 3))
|
|
# each face use 6 uint32 to describe,
|
|
# they are: pos1, nml1, pos2, nml2, pos3, nml3.
|
|
# each item is a 0 based index refering to corresponding list
|
|
face_count = UTIL_file_io.read_uint32(fmesh)
|
|
face.extend(UTIL_file_io.read_uint32_array(fmesh, face_count * 6))
|
|
|
|
# open mesh writer and write data
|
|
with UTIL_blender_mesh.MeshWriter(mesh) as writer:
|
|
# prepare writer essential function
|
|
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
|
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
|
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
|
for i in range(vpos_count):
|
|
idx: int = i * 3
|
|
v.x = vpos[idx]
|
|
v.y = vpos[idx + 1]
|
|
v.z = vpos[idx + 2]
|
|
# conv co
|
|
UTIL_virtools_types.vxvector3_conv_co(v)
|
|
yield v
|
|
mesh_part.mVertexPosition = vpos_iterator()
|
|
def vnml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
|
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
|
for i in range(vnml_count):
|
|
idx: int = i * 3
|
|
v.x = vnml[idx]
|
|
v.y = vnml[idx + 1]
|
|
v.z = vnml[idx + 2]
|
|
# conv co
|
|
UTIL_virtools_types.vxvector3_conv_co(v)
|
|
yield v
|
|
mesh_part.mVertexNormal = vnml_iterator()
|
|
def vuv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
|
# no uv, no need to conv co
|
|
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
|
yield v
|
|
mesh_part.mVertexUV = vuv_iterator()
|
|
mesh_part.mMaterial = iter(tuple())
|
|
def face_iterator() -> typing.Iterator[UTIL_blender_mesh.FaceData]:
|
|
# create face data with 3 placeholder
|
|
f: UTIL_blender_mesh.FaceData = UTIL_blender_mesh.FaceData(
|
|
[UTIL_blender_mesh.FaceVertexData() for i in range(3)]
|
|
)
|
|
for i in range(face_count):
|
|
idx: int = i * 6
|
|
f.mIndices[0].mPosIdx = face[idx]
|
|
f.mIndices[0].mNmlIdx = face[idx + 1]
|
|
f.mIndices[1].mPosIdx = face[idx + 2]
|
|
f.mIndices[1].mNmlIdx = face[idx + 3]
|
|
f.mIndices[2].mPosIdx = face[idx + 4]
|
|
f.mIndices[2].mNmlIdx = face[idx + 5]
|
|
# conv co
|
|
f.conv_co()
|
|
yield f
|
|
mesh_part.mFace = face_iterator()
|
|
|
|
writer.add_ingredient(mesh_part)
|
|
|
|
# end of with writer
|
|
# write mesh data
|
|
|
|
# set other mesh settings
|
|
# generated mesh always use lit mode.
|
|
mesh_settings: PROP_virtools_mesh.RawVirtoolsMesh = PROP_virtools_mesh.RawVirtoolsMesh()
|
|
mesh_settings.mLitMode = UTIL_virtools_types.VXMESH_LITMODE.VX_LITMESH
|
|
PROP_virtools_mesh.set_raw_virtools_mesh(mesh, mesh_settings)
|
|
|
|
# end of with fmesh
|
|
# close file
|
|
|
|
|
|
#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[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
|
|
__mIsValid: bool
|
|
__mAssocScene: bpy.types.Scene
|
|
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]
|
|
|
|
def __init__(self, assoc: bpy.types.Scene):
|
|
self.__mElementMap = {}
|
|
self.__mAssocScene = assoc
|
|
self.__mIsValid = False
|
|
|
|
# check singleton
|
|
BallanceElementsHelper.__mSingletonMutex.lock(self.__mAssocScene)
|
|
# set validation and read ballance elements property
|
|
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
|
|
BallanceElementsHelper.__mSingletonMutex.unlock(self.__mAssocScene)
|
|
|
|
def get_element(self, element_type: BallanceElementType) -> 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_type, None)
|
|
if mesh is not None:
|
|
return mesh
|
|
|
|
# if no existing one, create new one
|
|
new_mesh_name: str = get_ballance_element_name(element_type)
|
|
new_mesh: bpy.types.Mesh = bpy.data.meshes.new(new_mesh_name)
|
|
|
|
_load_element(new_mesh, element_type)
|
|
self.__mElementMap[element_type] = new_mesh
|
|
return new_mesh
|
|
|
|
def reset_elements(self) -> None:
|
|
if not self.is_valid():
|
|
raise UTIL_functions.BBPException('calling invalid BallanceElementsHelper')
|
|
|
|
# reload all items
|
|
for elety, elemesh in self.__mElementMap.items():
|
|
_load_element(elemesh, elety)
|
|
|
|
def __write_to_ballance_elements(self) -> None:
|
|
elements = get_ballance_elements(self.__mAssocScene)
|
|
elements.clear()
|
|
|
|
for elety, elemesh in self.__mElementMap.items():
|
|
item: BBP_PG_ballance_element = elements.add()
|
|
item.element_id = get_ballance_element_id(elety)
|
|
item.mesh_ptr = elemesh
|
|
|
|
def __read_from_ballance_element(self) -> None:
|
|
elements = get_ballance_elements(self.__mAssocScene)
|
|
self.__mElementMap.clear()
|
|
|
|
for item in elements:
|
|
# check requirements
|
|
if item.mesh_ptr is None: continue
|
|
element_type: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
|
if element_type is None: continue
|
|
|
|
# add into map
|
|
self.__mElementMap[element_type] = item.mesh_ptr
|
|
|
|
#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):
|
|
# check requirements
|
|
elety: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
|
if elety is None or item.mesh_ptr is None: return
|
|
|
|
# draw list item
|
|
layout.label(text = get_ballance_element_name(elety), translate = False)
|
|
layout.label(text = item.mesh_ptr.name, translate = False, 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):
|
|
with BallanceElementsHelper(context.scene) as helper:
|
|
helper.reset_elements()
|
|
# show a window to let user know, not silence
|
|
UTIL_functions.message_box(
|
|
('Reset OK.', ),
|
|
"Reset Result",
|
|
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
|
)
|
|
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() -> None:
|
|
# 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() -> None:
|
|
# 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)
|