update custom normals importing
This commit is contained in:
parent
fd17d3b5c9
commit
d128ffcde5
@ -32,7 +32,7 @@ class FaceData():
|
|||||||
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.mIndices = indices
|
||||||
self.mMtlIdx = mtlidx
|
self.mMtlIdx = mtlidx
|
||||||
|
|
||||||
def conv_co(self) -> None:
|
def conv_co(self) -> None:
|
||||||
"""
|
"""
|
||||||
Change indice order between Virtools and Blender
|
Change indice order between Virtools and Blender
|
||||||
@ -93,10 +93,14 @@ def _flat_face_uv_index(uv_idx: array.array, uv_array: array.array) -> typing.It
|
|||||||
yield uv_array[pos]
|
yield uv_array[pos]
|
||||||
yield uv_array[pos + 1]
|
yield uv_array[pos + 1]
|
||||||
|
|
||||||
def _nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]:
|
def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]:
|
||||||
for idx in nml_idx:
|
# following statement create a iterator for normals array by `iter(nml_array)`
|
||||||
pos: int = idx * 3
|
# then triple it, because iterator is a reference type, so 3 items of this tuple is pointed to the same iterator and share the same iteration progress.
|
||||||
yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2])
|
# then use star macro to pass it to zip, it will cause zip receive 3 params pointing to the same iterator.
|
||||||
|
# now zip() will call 3 param‘s __next__() function from left to right.
|
||||||
|
# zip will get following iteration result because all iterator are the same one: (0, 1, 2), (3, 4, 5) and etc (number is index to corresponding value).
|
||||||
|
# finally, use tuple to expand it to a tuple, not a generator.
|
||||||
|
return tuple(zip(*(iter(nml_array), ) * 3))
|
||||||
|
|
||||||
class TemporaryMesh():
|
class TemporaryMesh():
|
||||||
"""
|
"""
|
||||||
@ -179,7 +183,7 @@ class MeshReader():
|
|||||||
def get_vertex_position(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
def get_vertex_position(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||||
for vec in self.__mAssocMesh.vertices:
|
for vec in self.__mAssocMesh.vertices:
|
||||||
cache.x = vec.co.x
|
cache.x = vec.co.x
|
||||||
@ -197,14 +201,14 @@ class MeshReader():
|
|||||||
def get_vertex_normal(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
def get_vertex_normal(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||||
for nml in self.__mAssocMesh.loops:
|
for nml in self.__mAssocMesh.loops:
|
||||||
cache.x = nml.normal.x
|
cache.x = nml.normal.x
|
||||||
cache.y = nml.normal.y
|
cache.y = nml.normal.y
|
||||||
cache.z = nml.normal.z
|
cache.z = nml.normal.z
|
||||||
yield cache
|
yield cache
|
||||||
|
|
||||||
def get_vertex_uv_count(self) -> int:
|
def get_vertex_uv_count(self) -> int:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
@ -217,11 +221,11 @@ class MeshReader():
|
|||||||
else:
|
else:
|
||||||
# otherwise return its size, also equaling with face count * 3 in theory
|
# otherwise return its size, also equaling with face count * 3 in theory
|
||||||
return len(self.__mAssocMesh.uv_layers.active.uv)
|
return len(self.__mAssocMesh.uv_layers.active.uv)
|
||||||
|
|
||||||
def get_vertex_uv(self) -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
def get_vertex_uv(self) -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
cache: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
cache: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||||
if self.__mAssocMesh.uv_layers.active is None:
|
if self.__mAssocMesh.uv_layers.active is None:
|
||||||
# create a fake one
|
# create a fake one
|
||||||
@ -234,13 +238,13 @@ class MeshReader():
|
|||||||
cache.x = uv.vector.x
|
cache.x = uv.vector.x
|
||||||
cache.y = uv.vector.y
|
cache.y = uv.vector.y
|
||||||
yield cache
|
yield cache
|
||||||
|
|
||||||
def get_material_slot_count(self) -> int:
|
def get_material_slot_count(self) -> int:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
return len(self.__mAssocMesh.materials)
|
return len(self.__mAssocMesh.materials)
|
||||||
|
|
||||||
def get_material_slot(self) -> typing.Iterator[bpy.types.Material | None]:
|
def get_material_slot(self) -> typing.Iterator[bpy.types.Material | None]:
|
||||||
"""
|
"""
|
||||||
@remark This generator may return None if this slot do not link to may material.
|
@remark This generator may return None if this slot do not link to may material.
|
||||||
@ -250,20 +254,20 @@ class MeshReader():
|
|||||||
|
|
||||||
for mtl in self.__mAssocMesh.materials:
|
for mtl in self.__mAssocMesh.materials:
|
||||||
yield mtl
|
yield mtl
|
||||||
|
|
||||||
def get_face_count(self) -> int:
|
def get_face_count(self) -> int:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
return len(self.__mAssocMesh.polygons)
|
return len(self.__mAssocMesh.polygons)
|
||||||
|
|
||||||
def get_face(self) -> typing.Iterator[FaceData]:
|
def get_face(self) -> typing.Iterator[FaceData]:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
# detect whether we have material
|
# detect whether we have material
|
||||||
no_mtl: bool = self.get_material_slot_count() == 0
|
no_mtl: bool = self.get_material_slot_count() == 0
|
||||||
|
|
||||||
# use list as indices container for convenient adding and deleting.
|
# use list as indices container for convenient adding and deleting.
|
||||||
cache: FaceData = FaceData([], 0)
|
cache: FaceData = FaceData([], 0)
|
||||||
for face in self.__mAssocMesh.polygons:
|
for face in self.__mAssocMesh.polygons:
|
||||||
@ -274,7 +278,7 @@ class MeshReader():
|
|||||||
cache.mMtlIdx = 0
|
cache.mMtlIdx = 0
|
||||||
else:
|
else:
|
||||||
cache.mMtlIdx = face.material_index
|
cache.mMtlIdx = face.material_index
|
||||||
|
|
||||||
# resize indices
|
# resize indices
|
||||||
self.__resize_face_data_indices(cache.mIndices, face.loop_total)
|
self.__resize_face_data_indices(cache.mIndices, face.loop_total)
|
||||||
# set indices
|
# set indices
|
||||||
@ -282,10 +286,10 @@ class MeshReader():
|
|||||||
cache.mIndices[i].mPosIdx = self.__mAssocMesh.loops[face.loop_start + i].vertex_index
|
cache.mIndices[i].mPosIdx = self.__mAssocMesh.loops[face.loop_start + i].vertex_index
|
||||||
cache.mIndices[i].mNmlIdx = face.loop_start + i
|
cache.mIndices[i].mNmlIdx = face.loop_start + i
|
||||||
cache.mIndices[i].mUvIdx = face.loop_start + i
|
cache.mIndices[i].mUvIdx = face.loop_start + i
|
||||||
|
|
||||||
# return value
|
# return value
|
||||||
yield cache
|
yield cache
|
||||||
|
|
||||||
def __resize_face_data_indices(self, ls: list[FaceVertexData], expected_size: int) -> None:
|
def __resize_face_data_indices(self, ls: list[FaceVertexData], expected_size: int) -> None:
|
||||||
diff: int = expected_size - len(ls)
|
diff: int = expected_size - len(ls)
|
||||||
if diff > 0:
|
if diff > 0:
|
||||||
@ -299,7 +303,7 @@ class MeshReader():
|
|||||||
else:
|
else:
|
||||||
# no count diff, pass
|
# no count diff, pass
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __triangulate_mesh(self) -> None:
|
def __triangulate_mesh(self) -> None:
|
||||||
bm: bmesh.types.BMesh = bmesh.new()
|
bm: bmesh.types.BMesh = bmesh.new()
|
||||||
bm.from_mesh(self.__mAssocMesh)
|
bm.from_mesh(self.__mAssocMesh)
|
||||||
@ -432,7 +436,7 @@ class MeshWriter():
|
|||||||
# push material data
|
# push material data
|
||||||
for mtl in self.__mMtlSlot:
|
for mtl in self.__mMtlSlot:
|
||||||
self.__mAssocMesh.materials.append(mtl)
|
self.__mAssocMesh.materials.append(mtl)
|
||||||
|
|
||||||
# add corresponding count for vertex position
|
# add corresponding count for vertex position
|
||||||
self.__mAssocMesh.vertices.add(len(self.__mVertexPos) // 3)
|
self.__mAssocMesh.vertices.add(len(self.__mVertexPos) // 3)
|
||||||
# add loops data, it is the sum count of indices
|
# add loops data, it is the sum count of indices
|
||||||
@ -451,21 +455,21 @@ class MeshWriter():
|
|||||||
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
||||||
# add face vertex nml by function
|
# add face vertex nml by function
|
||||||
self.__mAssocMesh.loops.foreach_set('normal',
|
self.__mAssocMesh.loops.foreach_set('normal',
|
||||||
list(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
||||||
)
|
)
|
||||||
# add face vertex uv by function
|
# add face vertex uv by function
|
||||||
self.__mAssocMesh.uv_layers.active.uv.foreach_set('vector',
|
self.__mAssocMesh.uv_layers.active.uv.foreach_set('vector',
|
||||||
list(_flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV))
|
tuple(_flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV))
|
||||||
) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data
|
) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data
|
||||||
|
|
||||||
# iterate face to set face data
|
# iterate face to set face data
|
||||||
fVertexIdx: int = 0
|
f_vertex_idx: int = 0
|
||||||
for fi in range(len(self.__mFaceVertexCount)):
|
for fi in range(len(self.__mFaceVertexCount)):
|
||||||
# set start loop
|
# set start loop
|
||||||
# NOTE: blender 3.6 changed. Loop setting in polygon do not need set loop_total any more.
|
# NOTE: blender 3.6 changed. Loop setting in polygon do not need set loop_total any more.
|
||||||
# the loop_total will be auto calculated by the next loop_start.
|
# the loop_total will be auto calculated by the next loop_start.
|
||||||
# loop_total become read-only
|
# loop_total become read-only
|
||||||
self.__mAssocMesh.polygons[fi].loop_start = fVertexIdx
|
self.__mAssocMesh.polygons[fi].loop_start = f_vertex_idx
|
||||||
|
|
||||||
# set material index
|
# set material index
|
||||||
self.__mAssocMesh.polygons[fi].material_index = self.__mFaceMtlIdx[fi]
|
self.__mAssocMesh.polygons[fi].material_index = self.__mFaceMtlIdx[fi]
|
||||||
@ -475,7 +479,7 @@ class MeshWriter():
|
|||||||
self.__mAssocMesh.polygons[fi].use_smooth = True
|
self.__mAssocMesh.polygons[fi].use_smooth = True
|
||||||
|
|
||||||
# inc vertex idx
|
# inc vertex idx
|
||||||
fVertexIdx += self.__mFaceVertexCount[fi]
|
f_vertex_idx += self.__mFaceVertexCount[fi]
|
||||||
|
|
||||||
# validate mesh.
|
# validate mesh.
|
||||||
# it is IMPORTANT that do NOT delete custom data
|
# it is IMPORTANT that do NOT delete custom data
|
||||||
@ -485,18 +489,20 @@ class MeshWriter():
|
|||||||
self.__mAssocMesh.update(calc_edges = False, calc_edges_loose = False)
|
self.__mAssocMesh.update(calc_edges = False, calc_edges_loose = False)
|
||||||
|
|
||||||
# set custom split normal data
|
# set custom split normal data
|
||||||
# if the validate() change the mesh, skip this and output error.
|
# this operation must copy preserved normal data from loops, not the array data in this class,
|
||||||
# this change is detected by the loops count changes, not the return value of validate(). because only the changes of loops can let following throw errors.
|
# because the validate() may change the mesh and if change happended, an error will occur when applying normals (not matched loops count).
|
||||||
# this should not happend in normal case, just a stupid patch for "线框Level1.Level.NMO" loading.
|
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
|
||||||
if len(self.__mAssocMesh.loops) == len(self.__mFacePosIndices):
|
|
||||||
self.__mAssocMesh.normals_split_custom_set(
|
# copy data from loops preserved in validate().
|
||||||
tuple(_nest_custom_split_normal(self.__mFaceNmlIndices, self.__mVertexNormal))
|
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
||||||
)
|
self.__mAssocMesh.loops.foreach_get('normal', loops_normals)
|
||||||
# enable auto smooth. it is IMPORTANT
|
# apply data
|
||||||
self.__mAssocMesh.use_auto_smooth = True
|
self.__mAssocMesh.normals_split_custom_set(
|
||||||
else:
|
tuple(_nest_custom_split_normal(loops_normals))
|
||||||
print(f'The custom normals of mesh "{self.__mAssocMesh.name}" can not be assigned because validate() changes the mesh. Check your mesh data first!')
|
)
|
||||||
|
# enable auto smooth. it is IMPORTANT
|
||||||
|
self.__mAssocMesh.use_auto_smooth = True
|
||||||
|
|
||||||
def __clear_mesh(self):
|
def __clear_mesh(self):
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
|
||||||
|
Loading…
Reference in New Issue
Block a user