finish mesh reader
This commit is contained in:
parent
4f10b1a9e9
commit
16d4fc2bca
@ -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():
|
||||||
|
Loading…
Reference in New Issue
Block a user