finish mesh reader
This commit is contained in:
		| @ -16,20 +16,23 @@ class FaceVertexData(): | ||||
|     mPosIdx: int | ||||
|     mNmlIdx: int | ||||
|     mUvIdx: int | ||||
|  | ||||
|      | ||||
|     def __init__(self, pos: int = 0, nml: int = 0, uv: int = 0): | ||||
|         self.mPosIdx = pos | ||||
|         self.mNmlIdx = nml | ||||
|         self.mUvIdx = uv | ||||
|  | ||||
| class FaceData(): | ||||
|     mIndices: tuple[FaceVertexData, ...] | ||||
|     ## @remark List or tuple. List is convenient for adding and removing | ||||
|     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, ...] = tuple(), mtlidx: int = 0): | ||||
|      | ||||
|     def __init__(self, indices: tuple[FaceVertexData] | list[FaceVertexData] = tuple(), mtlidx: int = 0): | ||||
|         self.mIndices = indices | ||||
|         self.mMtlIdx = mtlidx | ||||
|  | ||||
|      | ||||
|     def is_indices_legal(self) -> bool: | ||||
|         return len(self.mIndices) >= 3 | ||||
|  | ||||
| @ -62,14 +65,217 @@ def _nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> t | ||||
|         pos: int = idx * 3 | ||||
|         yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2]) | ||||
|  | ||||
| class TemporaryMesh(): | ||||
|     """ | ||||
|      | ||||
|     """ | ||||
|      | ||||
|     __mBindingObject: bpy.types.Object | None | ||||
|     __mTempMesh: bpy.types.Mesh | None | ||||
|      | ||||
|     def __init__(self, binding_obj: bpy.types.Object): | ||||
|         self.__mBindingObject = binding_obj | ||||
|         self.__mTempMesh = None | ||||
|          | ||||
|         if self.__mBindingObject.data is None: | ||||
|             raise UTIL_functions.BBPException('try getting mesh from an object without mesh.') | ||||
|         self.__mTempMesh = self.__mBindingObject.to_mesh() | ||||
|      | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|      | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         self.dispose() | ||||
|      | ||||
|     def is_valid(self) -> bool: | ||||
|         if self.__mBindingObject is None: return False | ||||
|         if self.__mTempMesh is None: return False | ||||
|         return True | ||||
|      | ||||
|     def dispose(self): | ||||
|         if self.is_valid(): | ||||
|             self.__mTempMesh = None | ||||
|             self.__mBindingObject.to_mesh_clear() | ||||
|             self.__mBindingObject = None | ||||
|      | ||||
|     def get_temp_mesh(self) -> bpy.types.Mesh: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try calling invalid TemporaryMesh.') | ||||
|         return self.__mTempMesh | ||||
|  | ||||
|  | ||||
| #endregion | ||||
|  | ||||
| class MeshReader(): | ||||
|     """ | ||||
|     The passed mesh must be created by bpy.types.Object.to_mesh() and destroyed by bpy.types.Object.to_mesh_clear(). | ||||
|     Because this class must trianglate mesh. To prevent change original mesh, this operations is essential. | ||||
|     A helper class TemporaryMesh can help you do this. | ||||
|     """ | ||||
|     pass | ||||
|      | ||||
|     __mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this reader. None if this reader is invalid. | ||||
|      | ||||
|      | ||||
|      | ||||
|     def __init__(self, assoc_mesh: bpy.types.Mesh): | ||||
|         self.__mAssocMesh = assoc_mesh | ||||
|          | ||||
|         # triangulate temp mesh | ||||
|         if self.is_valid(): | ||||
|             self.__triangulate_mesh() | ||||
|             self.__mAssocMesh.calc_normals_split() | ||||
|      | ||||
|     def is_valid(self) -> bool: | ||||
|         return self.__mAssocMesh is not None | ||||
|      | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|      | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         self.dispose() | ||||
|      | ||||
|     def dispose(self): | ||||
|         if self.is_valid(): | ||||
|             # reset mesh | ||||
|             self.__mAssocMesh.free_normals_split() | ||||
|             self.__mAssocMesh = None | ||||
|      | ||||
|     def get_vertex_position_count(self) -> int: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         return len(self.__mAssocMesh.vertices) | ||||
|      | ||||
|     def get_vertex_position(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|  | ||||
|         cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3() | ||||
|         for vec in self.__mAssocMesh.vertices: | ||||
|             cache.x = vec.co.x | ||||
|             cache.y = vec.co.y | ||||
|             cache.z = vec.co.z | ||||
|             yield cache | ||||
|      | ||||
|     def get_vertex_normal_count(self) -> int: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         # return loops count, equaling with face count * 3 in theory. | ||||
|         return len(self.__mAssocMesh.loops) | ||||
|      | ||||
|     def get_vertex_normal(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|  | ||||
|         cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3() | ||||
|         for nml in self.__mAssocMesh.loops: | ||||
|             cache.x = nml.normal.x | ||||
|             cache.y = nml.normal.y | ||||
|             cache.z = nml.normal.z | ||||
|             yield cache | ||||
|  | ||||
|     def get_vertex_uv_count(self) -> int: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         if self.__mAssocMesh.uv_layers.active is None: | ||||
|             # if no uv layer, we need make a fake one | ||||
|             # return the same value with normals. | ||||
|             # it also mean create uv for each face vertex | ||||
|             return len(self.__mAssocMesh.loops) | ||||
|         else: | ||||
|             # otherwise return its size, also equaling with face count * 3 in theory | ||||
|             return len(self.__mAssocMesh.uv_layers.active.uv) | ||||
|  | ||||
|     def get_vertex_uv(self) -> typing.Iterator[UTIL_virtools_types.VxVector2]: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|  | ||||
|         cache: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2() | ||||
|         if self.__mAssocMesh.uv_layers.active is None: | ||||
|             # create a fake one | ||||
|             cache.x = 0.0 | ||||
|             cache.y = 0.0 | ||||
|             for _ in range(self.get_vertex_uv_count()): | ||||
|                 yield cache | ||||
|         else: | ||||
|             for uv in self.__mAssocMesh.uv_layers.active.uv: | ||||
|                 cache.x = uv.vector.x | ||||
|                 cache.y = uv.vector.y | ||||
|                 yield cache | ||||
|  | ||||
|     def get_material_slot_count(self) -> int: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         return len(self.__mAssocMesh.materials) | ||||
|  | ||||
|     def get_material_slot(self) -> typing.Iterator[bpy.types.Material]: | ||||
|         """ | ||||
|         @remark This generator may return None if this slot do not link to may material. | ||||
|         """ | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         for mtl in self.__mAssocMesh.materials: | ||||
|             yield mtl | ||||
|  | ||||
|     def get_face_count(self) -> int: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         return len(self.__mAssocMesh.polygons) | ||||
|  | ||||
|     def get_face(self) -> typing.Iterator[FaceData]: | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshReader.') | ||||
|          | ||||
|         # detect whether we have material | ||||
|         no_mtl: bool = self.get_material_slot_count() == 0 | ||||
|  | ||||
|         # use list as indices container for convenient adding and deleting. | ||||
|         cache: FaceData = FaceData([], 0) | ||||
|         for face in self.__mAssocMesh.polygons: | ||||
|             # confirm material use | ||||
|             # a face without mtl have 2 situations. first is the whole object do not have mtl | ||||
|             # another is this face use an empty mtl slot. | ||||
|             if no_mtl: | ||||
|                 cache.mMtlIdx = 0 | ||||
|             else: | ||||
|                 cache.mMtlIdx = face.material_index | ||||
|  | ||||
|             # resize indices | ||||
|             self.__resize_face_data_indices(cache.mIndices, face.loop_total) | ||||
|             # set indices | ||||
|             for i in range(face.loop_total): | ||||
|                 cache.mIndices[i].mPosIdx = self.__mAssocMesh.loops[face.loop_start + i].vertex_index | ||||
|                 cache.mIndices[i].mNmlIdx = face.loop_start + i | ||||
|                 cache.mIndices[i].mUvIdx = face.loop_start + i | ||||
|  | ||||
|             # return value | ||||
|             yield cache | ||||
|  | ||||
|     def __resize_face_data_indices(self, ls: list[FaceVertexData], expected_size: int) -> None: | ||||
|         diff: int = expected_size - len(ls) | ||||
|         if diff > 0: | ||||
|             # add entry | ||||
|             for _ in range(diff): | ||||
|                 ls.append(FaceVertexData()) | ||||
|         elif diff < 0: | ||||
|             # remove entry | ||||
|             for _ in range(diff): | ||||
|                 ls.pop() | ||||
|         else: | ||||
|             # no count diff, pass | ||||
|             pass | ||||
|          | ||||
|     def __triangulate_mesh(self) -> None: | ||||
|         bm = bmesh.new() | ||||
|         bm.from_mesh(self.__mAssocMesh) | ||||
|         bmesh.ops.triangulate(bm, faces = bm.faces) | ||||
|         bm.to_mesh(self.__mAssocMesh) | ||||
|         bm.free() | ||||
|  | ||||
| class MeshWriter(): | ||||
|     """ | ||||
| @ -116,7 +322,7 @@ class MeshWriter(): | ||||
|     ## Same as __mFacePosIndices, but store face vertex uv index. | ||||
|     #  Array item is int32(L). Length is equal to __mFacePosIndices | ||||
|     __mFaceUvIndices: array.array | ||||
|     ## Array item is int32(L).  | ||||
|     ## Array item is int32(L). | ||||
|     #  Length is the face count. | ||||
|     #  It indicate how much vertex need to be consumed in __mFacePosIndices, __mFaceNmlIndices and __mFaceUvIndices for one face. | ||||
|     __mFaceVertexCount: array.array | ||||
| @ -126,7 +332,7 @@ class MeshWriter(): | ||||
|     #  Each item is unique make sure by __mMtlSlotMap | ||||
|     __mMtlSlot: list[bpy.types.Material] | ||||
|     ## The map to make sure every item in __mMtlSlot is unique. | ||||
|     #  Key is bpy.types.Material  | ||||
|     #  Key is bpy.types.Material | ||||
|     #  Value is key's index in __mMtlSlot. | ||||
|     __mMtlSlotMap: dict[bpy.types.Material, int] | ||||
|      | ||||
| @ -155,7 +361,16 @@ class MeshWriter(): | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         self.dispose() | ||||
|      | ||||
|     def dispose(self): | ||||
|         if self.is_valid(): | ||||
|             # write mesh | ||||
|             self.__write_mesh() | ||||
|             # reset mesh | ||||
|             self.__mAssocMesh = None | ||||
|      | ||||
|     def add_part(self, data: MeshWriterPartData): | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshWriter.') | ||||
|         if not data.is_valid(): | ||||
|             raise UTIL_functions.BBPException('invalid mesh part data.') | ||||
|          | ||||
| @ -198,14 +413,7 @@ class MeshWriter(): | ||||
|                 self.__mFaceMtlIdx.append(0) | ||||
|             else: | ||||
|                 self.__mFaceMtlIdx.append(mtl_remap[mtl_idx]) | ||||
|              | ||||
|     def dispose(self): | ||||
|         if self.is_valid(): | ||||
|             # write mesh | ||||
|             self.__write_mesh() | ||||
|             # reset mesh | ||||
|             self.__mAssocMesh = None | ||||
|  | ||||
|      | ||||
|     def __write_mesh(self): | ||||
|         # detect status | ||||
|         if not self.is_valid(): | ||||
| @ -216,7 +424,7 @@ class MeshWriter(): | ||||
|         # push material data | ||||
|         for mtl in self.__mMtlSlot: | ||||
|             self.__mAssocMesh.materials.append(mtl) | ||||
|  | ||||
|          | ||||
|         # add corresponding count for vertex position | ||||
|         self.__mAssocMesh.vertices.add(len(self.__mVertexPos)) | ||||
|         # add loops data, it is the sum count of indices | ||||
| @ -228,7 +436,7 @@ class MeshWriter(): | ||||
|         self.__mAssocMesh.uv_layers.new(do_init = False) | ||||
|         # split normals, it is IMPORTANT | ||||
|         self.__mAssocMesh.create_normals_split() | ||||
|  | ||||
|          | ||||
|         # add vertex position data | ||||
|         self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos) | ||||
|         # add face vertex pos index data | ||||
| @ -241,7 +449,7 @@ class MeshWriter(): | ||||
|         self.__mAssocMesh.uv_layers[0].uv.foreach_set('vector', | ||||
|             list(_flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV)) | ||||
|         )   # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data | ||||
|  | ||||
|          | ||||
|         # iterate face to set face data | ||||
|         fVertexIdx: int = 0 | ||||
|         for fi in range(len(self.__mFaceVertexCount)): | ||||
| @ -250,31 +458,31 @@ class MeshWriter(): | ||||
|             # the loop_total will be auto calculated by the next loop_start. | ||||
|             # loop_total become read-only | ||||
|             self.__mAssocMesh.polygons[fi].loop_start = fVertexIdx | ||||
|  | ||||
|              | ||||
|             # set material index | ||||
|             self.__mAssocMesh.polygons[fi].material_index = self.__mFaceMtlIdx[fi] | ||||
|  | ||||
|              | ||||
|             # set auto smooth. it is IMPORTANT | ||||
|             # because it related to whether custom split normal can work | ||||
|             self.__mAssocMesh.polygons[fi].use_smooth = True | ||||
|  | ||||
|              | ||||
|             # inc vertex idx | ||||
|             fVertexIdx += self.__mFaceVertexCount[fi] | ||||
|  | ||||
|          | ||||
|         # validate mesh. | ||||
|         # it is IMPORTANT that do NOT delete custom data | ||||
|         # because we need use these data to set custom split normal later | ||||
|         self.__mAssocMesh.validate(clean_customdata = False) | ||||
|         # update mesh without mesh calc | ||||
|         self.__mAssocMesh.update(calc_edges = False, calc_edges_loose = False) | ||||
|  | ||||
|          | ||||
|         # set custom split normal data | ||||
|         self.__mAssocMesh.normals_split_custom_set( | ||||
|             tuple(_nest_custom_split_normal(self.__mFaceNmlIndices, self.__mVertexNormal)) | ||||
|         ) | ||||
|         # enable auto smooth. it is IMPORTANT | ||||
|         self.__mAssocMesh.use_auto_smooth = True | ||||
|  | ||||
|      | ||||
|     def __clear_mesh(self): | ||||
|         if not self.is_valid(): | ||||
|             raise UTIL_functions.BBPException('try to call an invalid MeshWriter.') | ||||
| @ -283,6 +491,6 @@ class MeshWriter(): | ||||
|         self.__mAssocMesh.clear_geometry() | ||||
|         # clear mtl slot because clear_geometry will not do this. | ||||
|         self.__mAssocMesh.materials.clear() | ||||
|          | ||||
|  | ||||
| class MeshUVModifier(): | ||||
|     pass | ||||
|  | ||||
		Reference in New Issue
	
	Block a user