finish mesh reader

This commit is contained in:
yyc12345 2023-10-19 10:56:33 +08:00
parent 4f10b1a9e9
commit 16d4fc2bca

View File

@ -23,10 +23,13 @@ class FaceVertexData():
self.mUvIdx = uv self.mUvIdx = uv
class FaceData(): 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 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.mIndices = indices
self.mMtlIdx = mtlidx self.mMtlIdx = mtlidx
@ -62,15 +65,218 @@ def _nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> t
pos: int = idx * 3 pos: int = idx * 3
yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2]) 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 #endregion
class MeshReader(): class MeshReader():
""" """
The passed mesh must be created by bpy.types.Object.to_mesh() and destroyed by bpy.types.Object.to_mesh_clear(). 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. Because this class must trianglate mesh. To prevent change original mesh, this operations is essential.
A helper class TemporaryMesh can help you do this.
""" """
__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 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(): class MeshWriter():
""" """
If face do not use material, pass 0 as its material index. If face do not use material, pass 0 as its material index.
@ -155,7 +361,16 @@ class MeshWriter():
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
self.dispose() 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): 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(): if not data.is_valid():
raise UTIL_functions.BBPException('invalid mesh part data.') raise UTIL_functions.BBPException('invalid mesh part data.')
@ -199,13 +414,6 @@ class MeshWriter():
else: else:
self.__mFaceMtlIdx.append(mtl_remap[mtl_idx]) 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): def __write_mesh(self):
# detect status # detect status
if not self.is_valid(): if not self.is_valid():