diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index c22ecc9..93b8b1a 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -57,6 +57,11 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool self.draw_virtools_params(layout) layout.prop(self, 'compress_level') +_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject] +_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh] +_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial] +_TTexturePair = tuple[bpy.types.Image, bmap.BMTexture] + def _export_virtools(file_name_: str, encodings_: tuple[str], compress_level_: int, export_objects: tuple[bpy.types.Object, ...]) -> None: # create temp folder with tempfile.TemporaryDirectory() as vt_temp_folder: @@ -71,13 +76,19 @@ def _export_virtools(file_name_: str, encodings_: tuple[str], compress_level_: i # prepare progress reporter with ProgressReport(wm = bpy.context.window_manager) as progress: # prepare 3dobject - obj3d_crets: tuple[tuple[bpy.types.Object, bmap.BM3dObject], ...] = _prepare_virtools_3dobjects( + obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects( writer, progress, export_objects) # export group and 3dobject by prepared 3dobject _export_virtools_groups(writer, progress, obj3d_crets) - mesh_crets: tuple[tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh], ...] = _export_virtools_3dobjects( + mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects( writer, progress, obj3d_crets) - + # export mesh + material_crets: tuple[_TMaterialPair, ...] = _export_virtools_meshes( + writer, progress, mesh_crets) + # export material + texture_crets: tuple[_TTexturePair, ...] = _export_virtools_materials( + writer, progress, material_crets) + # save document _save_virtools_document( @@ -87,12 +98,12 @@ def _prepare_virtools_3dobjects( writer: bmap.BMFileWriter, progress: ProgressReport, export_objects: tuple[bpy.types.Object] - ) -> tuple[tuple[bpy.types.Object, bmap.BM3dObject], ...]: + ) -> tuple[_TObj3dPair, ...]: # this function only create equvalent entries in virtools engine and do not export anything # because _export_virtools_3dobjects() and _export_virtools_groups() are need use the return value of this function # create 3dobject hashset and result - obj3d_crets: list[tuple[bpy.types.Object, bmap.BM3dObject]] = [] + obj3d_crets: list[_TObj3dPair] = [] obj3d_cret_set: set[bpy.types.Object] = set() # start saving progress.enter_substeps(len(export_objects), "Creating 3dObjects") @@ -116,7 +127,7 @@ def _prepare_virtools_3dobjects( def _export_virtools_groups( writer: bmap.BMFileWriter, progress: ProgressReport, - obj3d_crets: tuple[tuple[bpy.types.Object, bmap.BM3dObject], ...] + obj3d_crets: tuple[_TObj3dPair, ...] ) -> None: # create virtools group group_cret_map: dict[str, bmap.BMGroup] = {} @@ -146,10 +157,10 @@ def _export_virtools_groups( def _export_virtools_3dobjects( writer: bmap.BMFileWriter, progress: ProgressReport, - obj3d_crets: tuple[tuple[bpy.types.Object, bmap.BM3dObject], ...] - ) -> tuple[tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh], ...]: + obj3d_crets: tuple[_TObj3dPair, ...] + ) -> tuple[_TMeshPair, ...]: # create virtools mesh - mesh_crets: list[tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]] = [] + mesh_crets: list[_TMeshPair] = [] mesh_cret_map: dict[bpy.types.Mesh, bmap.BMMesh] = {} # start saving progress.enter_substeps(len(obj3d_crets), "Saving 3dObjects") @@ -187,7 +198,143 @@ def _export_virtools_3dobjects( # leave progress and return progress.leave_substeps() + return tuple(mesh_crets) +def _export_virtools_meshes( + writer: bmap.BMFileWriter, + progress: ProgressReport, + mesh_crets: tuple[_TMeshPair, ...] + ) -> tuple[_TMaterialPair, ...]: + # create virtools mesh + material_crets: list[_TMaterialPair] = [] + material_cret_map: dict[bpy.types.Material, bmap.BMMaterial] = {} + # start saving + progress.enter_substeps(len(mesh_crets), "Saving Meshes") + + # iterate meshes + for obj3d, mesh, vtmesh in mesh_crets: + # we need use temporary mesh function to visit triangulated meshes + # so we ignore mesh factor and use obj3d to create temp mesh to get data + # open temp mesh helper + with UTIL_blender_mesh.TemporaryMesh(obj3d) as tempmesh: + # sync mesh name, lit mode + vtmesh.set_name(mesh.name) + mesh_settings: PROP_virtools_mesh.RawVirtoolsMesh = PROP_virtools_mesh.get_raw_virtools_mesh(mesh) + vtmesh.set_lit_mode(mesh_settings.mLitMode) + + # sync mesh main data + # open mesh visitor + with UTIL_blender_mesh.MeshReader(tempmesh.get_temp_mesh()) as mesh_visitor: + # construct data provider + def pos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]: + for v in mesh_visitor.get_vertex_position(): + UTIL_virtools_types.vxvector3_conv_co(v) + yield v + def nml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]: + for v in mesh_visitor.get_vertex_normal(): + UTIL_virtools_types.vxvector3_conv_co(v) + yield v + def uv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]: + for v in mesh_visitor.get_vertex_uv(): + UTIL_virtools_types.vxvector2_conv_co(v) + yield v + + # construct mtl slot + def mtl_iterator() -> typing.Iterator[bmap.BMMaterial | None]: + for mtl in mesh_visitor.get_material_slot(): + if mtl is None: yield None + else: + # get existing one or create new one + vtmaterial: bmap.BMMaterial | None = material_cret_map.get(mtl, None) + if vtmaterial is None: + vtmaterial = writer.create_material() + material_crets.append((mtl, vtmaterial)) + material_cret_map[mtl] = vtmaterial + # yield data + yield vtmaterial + + def face_idx_iterator(idx_type: int) -> typing.Iterator[UTIL_virtools_types.CKFaceIndices]: + data: UTIL_virtools_types.CKFaceIndices = UTIL_virtools_types.CKFaceIndices() + for fidx in mesh_visitor.get_face(): + # swap indices + fidx.conv_co() + # set data by specific index + match(idx_type): + case 0: data.i1, data.i2, data.i3 = fidx.mIndices[0].mPosIdx, fidx.mIndices[1].mPosIdx, fidx.mIndices[2].mPosIdx + case 1: data.i1, data.i2, data.i3 = fidx.mIndices[0].mNmlIdx, fidx.mIndices[1].mNmlIdx, fidx.mIndices[2].mNmlIdx + case 2: data.i1, data.i2, data.i3 = fidx.mIndices[0].mUvIdx, fidx.mIndices[1].mUvIdx, fidx.mIndices[2].mUvIdx + case _: raise UTIL_functions.BBPException('invalid index type.') + # yield data + yield data + def face_mtl_iterator() -> typing.Iterator[int]: + for fidx in mesh_visitor.get_face(): + yield fidx.mMtlIdx + + # create virtools mesh transition + # and write into mesh + with bmap.BMMeshTrans() as mesh_trans: + # prepare vertices + mesh_trans.prepare_vertex( + mesh_visitor.get_vertex_position_count(), + pos_iterator() + ) + mesh_trans.prepare_normal( + mesh_visitor.get_vertex_normal_count(), + nml_iterator() + ) + mesh_trans.prepare_uv( + mesh_visitor.get_vertex_uv_count(), + uv_iterator() + ) + # prepare mtl slots + mesh_trans.prepare_mtl_slot( + mesh_visitor.get_material_slot_count(), + mtl_iterator() + ) + # prepare face + mesh_trans.prepare_face( + mesh_visitor.get_face_count(), + face_idx_iterator(0), + face_idx_iterator(1), + face_idx_iterator(2), + face_mtl_iterator() + ) + + # parse to vtmesh + mesh_trans.parse(writer, vtmesh) + + # end of mesh trans + # end of mesh visitor + # end of temp mesh + + # step + progress.step() + + # leave progress and return + progress.leave_substeps() + return tuple(material_crets) + +def _export_virtools_materials( + writer: bmap.BMFileWriter, + progress: ProgressReport, + material_crets: tuple[_TMaterialPair, ...] + ) -> tuple[_TTexturePair, ...]: + # create virtools mesh + texture_crets: list[_TTexturePair] = [] + texture_cret_map: dict[bpy.types.Image, bmap.BMTexture] = {} + # start saving + progress.enter_substeps(len(material_crets), "Saving Materials") + + for material, vtmaterial in material_crets: + # set name + vtmaterial.set_name(material.name) + + # step + progress.step() + + # leave progress and return + progress.leave_substeps() + return tuple(texture_crets) def _save_virtools_document( writer: bmap.BMFileWriter, diff --git a/bbp_ng/PyBMap/bmap_wrapper.py b/bbp_ng/PyBMap/bmap_wrapper.py index 8ac79d9..9a3168e 100644 --- a/bbp_ng/PyBMap/bmap_wrapper.py +++ b/bbp_ng/PyBMap/bmap_wrapper.py @@ -115,7 +115,11 @@ def _vxvector2_iterator(pvector: bmap.bm_VxVector2_p, count: int) -> typing.Iter idx += 2 yield ret -def _ckfaceindices_assigner(pindices: bmap.bm_CKDWORD_p, count: int, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None: +# bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p is just a type hint +# wo do not need distinguish them in code. +# because the type of pindices is decided by runtime. + +def _ckfaceindices_assigner(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None: idx: int = 0 for _ in range(count): userindices: virtools_types.CKFaceIndices = next(itor) @@ -124,7 +128,7 @@ def _ckfaceindices_assigner(pindices: bmap.bm_CKDWORD_p, count: int, itor: typin pindices[idx + 2] = userindices.i3 idx += 3 -def _ckfaceindices_iterator(pindices: bmap.bm_CKDWORD_p, count: int) -> typing.Iterator[virtools_types.CKFaceIndices]: +def _ckfaceindices_iterator(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int) -> typing.Iterator[virtools_types.CKFaceIndices]: ret: virtools_types.CKFaceIndices = virtools_types.CKFaceIndices() idx: int = 0 for _ in range(count): @@ -175,12 +179,12 @@ class BMObject(_AbstractCKObject): else: return name.value.decode(g_BMapEncoding) - def set_name(self, name: str | None) -> None: + def set_name(self, name_: str | None) -> None: name: bmap.bm_CKSTRING - if name is None: + if name_ is None: name = bmap.bm_CKSTRING(0) else: - name = bmap.bm_CKSTRING(name.encode(g_BMapEncoding)) + name = bmap.bm_CKSTRING(name_.encode(g_BMapEncoding)) bmap.BMObject_SetName(self._get_pointer(), self._get_ckid(), name) class BMTexture(BMObject): @@ -415,8 +419,8 @@ class BMMesh(BMObject): bmap.BMMesh_GetLitMode(self._get_pointer(), self._get_ckid(), ctypes.byref(mode)) return virtools_types.VXMESH_LITMODE(mode.value) - def set_lit_mode(self, mode: virtools_types.VXMESH_LITMODE) -> None: - mode: bmap.bm_enum = bmap.bm_enum(mode.value) + def set_lit_mode(self, mode_: virtools_types.VXMESH_LITMODE) -> None: + mode: bmap.bm_enum = bmap.bm_enum(mode_.value) bmap.BMMesh_SetLitMode(self._get_pointer(), self._get_ckid(), mode) def get_vertex_count(self) -> int: @@ -535,9 +539,9 @@ class BM3dObject(BMObject): ret.from_const(tuple(flat[i] for i in range(16))) return ret - def set_world_matrix(self, mat: virtools_types.VxMatrix) -> None: + def set_world_matrix(self, mat_: virtools_types.VxMatrix) -> None: # star syntax expand the tuple as the argument. - mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix(*(mat.to_const())) + mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix(*(mat_.to_const())) bmap.BM3dObject_SetWorldMatrix(self._get_pointer(), self._get_ckid(), mat) def get_current_mesh(self) -> BMMesh | None: @@ -819,7 +823,7 @@ class BMMeshTrans(_AbstractPointer): idx: int = 0 for _ in range(count): - usermtl: BMMaterial = next(itor) + usermtl: BMMaterial | None = next(itor) if usermtl is None: raw_ckid[idx] = g_InvalidCKID else: diff --git a/bbp_ng/UTIL_blender_mesh.py b/bbp_ng/UTIL_blender_mesh.py index d0d6dbf..6513d6f 100644 --- a/bbp_ng/UTIL_blender_mesh.py +++ b/bbp_ng/UTIL_blender_mesh.py @@ -24,12 +24,12 @@ class FaceVertexData(): class FaceData(): ## @remark List or tuple. List is convenient for adding and removing - mIndices: tuple[FaceVertexData] | list[FaceVertexData] + mIndices: tuple[FaceVertexData, ...] | list[FaceVertexData] ## Face used material slot index # @remark If material slot is empty, or this face do not use material, set this value to 0. mMtlIdx: int - def __init__(self, indices: tuple[FaceVertexData] | list[FaceVertexData] = tuple(), mtlidx: int = 0): + def __init__(self, indices: tuple[FaceVertexData, ...] | list[FaceVertexData] = tuple(), mtlidx: int = 0): self.mIndices = indices self.mMtlIdx = mtlidx