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