finish blender mesh writer

This commit is contained in:
yyc12345 2023-10-18 12:09:40 +08:00
parent ea6cb430fe
commit 484a4101ad
2 changed files with 217 additions and 53 deletions

View File

@ -12,35 +12,55 @@ from . import UTIL_functions, UTIL_virtools_types
#region Assist Functions
## Face used vertex index struct.
# Data member is (PosIdx, NmlIdx, UVIdx)
FaceVertexIndex = tuple[int, int, int]
## Face used vertex indices struct.
# A tuple with FaceVertexIndex member.
# At least 3 member because face need at least 3 vertex to be constructed.
FaceVertexIndices = tuple[FaceVertexIndex, ...]
## Face data struct.
# First is FaceVertexIndices struct to describe how the face constructed.
# Second is used material index.
# If you don't want to use material, you need pass None via material and point this index to it.
FaceData = tuple[FaceVertexIndices, int]
class FaceVertexData():
mPosIdx: int
mNmlIdx: int
mUvIdx: int
def flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.ConstVxVector3]) -> typing.Iterator[float]:
for entry in it:
yield entry[0]
yield entry[1]
yield entry[2]
def __init__(self, pos: int = 0, nml: int = 0, uv: int = 0):
self.mPosIdx = pos
self.mNmlIdx = nml
self.mUvIdx = uv
def flat_vxvector2(it: typing.Iterator[UTIL_virtools_types.ConstVxVector2]) -> typing.Iterator[float]:
for entry in it:
yield entry[0]
yield entry[1]
class FaceData():
mIndices: tuple[FaceVertexData, ...]
mMtlIdx: int
def flat_vertex_indices(it: FaceVertexIndices, prev_pos: int, prev_nml: int, prev_uv: int) -> typing.Iterator[int]:
def __init__(self, indices: tuple[FaceVertexData, ...] = tuple(), mtlidx: int = 0):
self.mIndices = indices
self.mMtlIdx = mtlidx
def is_indices_legal(self) -> bool:
return len(self.mIndices) >= 3
def flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.VxVector3]) -> typing.Iterator[float]:
for entry in it:
yield entry[0] + prev_pos
yield entry[1] + prev_nml
yield entry[2] + prev_uv
yield entry.x
yield entry.y
yield entry.z
def flat_vxvector2(it: typing.Iterator[UTIL_virtools_types.VxVector2]) -> typing.Iterator[float]:
for entry in it:
yield entry.x
yield entry.y
def flat_face_nml_index(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[float]:
for idx in nml_idx:
pos: int = idx * 3
yield nml_array[pos]
yield nml_array[pos + 1]
yield nml_array[pos + 2]
def flat_face_uv_index(uv_idx: array.array, uv_array: array.array) -> typing.Iterator[float]:
for idx in uv_idx:
pos: int = idx * 2
yield uv_array[pos]
yield uv_array[pos + 1]
def nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]:
for idx in nml_idx:
pos: int = idx * 3
yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2])
#endregion
@ -53,13 +73,15 @@ class MeshReader():
class MeshWriter():
"""
If face do not use material, pass 0 as its material index.
If face do not have UV becuase it doesn't have material, you at least create 1 UV vector, eg. (0, 0),
then refer it to all face uv.
"""
class MeshWriterPartData():
mVertexPosition: typing.Iterator[UTIL_virtools_types.ConstVxVector3] | None
mVertexNormal: typing.Iterator[UTIL_virtools_types.ConstVxVector3] | None
mVertexUV: typing.Iterator[UTIL_virtools_types.ConstVxVector2] | None
mVertexPosition: typing.Iterator[UTIL_virtools_types.VxVector3] | None
mVertexNormal: typing.Iterator[UTIL_virtools_types.VxVector3] | None
mVertexUV: typing.Iterator[UTIL_virtools_types.VxVector2] | None
mFace: typing.Iterator[FaceData] | None
mMaterial: typing.Iterator[bpy.types.Material] | None
@ -78,24 +100,34 @@ class MeshWriter():
if self.mMaterial is None: return False
return True
__mAssocMesh: bpy.types.Mesh | None
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this writer. None if this writer is invalid.
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
__mVertexUV: array.array ##< Array item is float(f). Length must be an integer multiple of 2.
## Array item is int32(L).
# Length must be (the sum of each items in __mFaceVertexCount) * 3.
# Item is flatten FaceVertexIndex, it mean every 3 continuous items indicate one vertex property.
__mFaceIndices: array.array
# Length must be the sum of each items in __mFaceVertexCount.
# Item is face vertex position index, based on 0, pointing to __mVertexPos (visiting need multiple it with 3 because __mVertexPos is flat struct).
__mFacePosIndices: array.array
## Same as __mFacePosIndices, but store face vertex normal index.
# Array item is int32(L). Length is equal to __mFacePosIndices
__mFaceNmlIndices: array.array
## 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).
# Length is the face count.
# It indicate how much vertex need to be consumed in __mFaceIndices for one face.
# The real consumed number of items in __mFaceVertexCount for each face need be multipled by 3 because __mFaceVertexCount is flat structure.
# It indicate how much vertex need to be consumed in __mFacePosIndices, __mFaceNmlIndices and __mFaceUvIndices for one face.
__mFaceVertexCount: array.array
__mFaceMtlIdx: array.array ##< Array item is int32(L). Length is equal to __mFaceVertexCount.
## Material Slot.
# 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
# Value is key's index in __mMtlSlot.
__mMtlSlotMap: dict[bpy.types.Material, int]
def __init__(self, assoc_mesh: bpy.types.Mesh):
@ -105,7 +137,9 @@ class MeshWriter():
self.__mVertexNormal = array.array('f')
self.__mVertexUV = array.array('f')
self.__mFaceIndices = array.array('L')
self.__mFacePosIndices = array.array('L')
self.__mFaceNmlIndices = array.array('L')
self.__mFaceUvIndices = array.array('L')
self.__mFaceVertexCount = array.array('L')
self.__mFaceMtlIdx = array.array('L')
@ -147,34 +181,110 @@ class MeshWriter():
# add face data
for face in data.mFace:
# check indices count
indices = face[0]
if len(indices) < 3:
if not face.is_indices_legal():
raise UTIL_functions.BBPException('face must have at least 3 vertex.')
# add indices
self.__mFaceIndices.extend(
flat_vertex_indices(indices,
prev_vertex_pos_count,
prev_vertex_nml_count,
prev_vertex_uv_count
)
)
self.__mFaceVertexCount.append(len(indices))
for vec_index in face.mIndices:
self.__mFacePosIndices.append(vec_index.mPosIdx + prev_vertex_pos_count)
self.__mFaceNmlIndices.append(vec_index.mNmlIdx + prev_vertex_nml_count)
self.__mFaceUvIndices.append(vec_index.mUvIdx + prev_vertex_uv_count)
self.__mFaceVertexCount.append(len(face.mIndices))
# add face mtl with remap
mtl_idx: int = face[1]
self.__mFaceMtlIdx.append(mtl_remap[mtl_idx])
mtl_idx: int = face.mMtlIdx
if mtl_idx < 0 or mtl_idx > len(mtl_remap):
# fall back. add 0
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():
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
# and clear mesh
self.__clear_mesh()
# 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
# we use face pos indices size to get it
self.__mAssocMesh.loops.add(len(self.__mFacePosIndices))
# set face count
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
# create uv layer
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
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
# add face vertex nml by function
self.__mAssocMesh.loops.foreach_set('normal',
list(flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
)
# add face vertex uv by function
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)):
# set start loop
# 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.
# 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.')
# clear geometry
self.__mAssocMesh.clear_geometry()
# clear mtl slot because clear_geometry will not do this.
self.__mAssocMesh.materials.clear()
class MeshUVModifier():

View File

@ -5,8 +5,62 @@ ConstVxVector2 = tuple[float, float]
ConstVxVector3 = tuple[float, float, float]
ConstVxVector4 = tuple[float, float, float, float]
class VxVector2():
x: float
y: float
def __init__(self, _x: float = 0.0, _y: float = 0.0):
self.x = _x
self.y = _y
def from_const(self, cv: ConstVxVector2) -> None:
(self.x, self.y, ) = cv
def to_const(self) -> ConstVxVector2:
return (self.x, self.y, )
# uv just need inverse y (aka. v) factor.
def to_blender_uv(self) -> None:
self.y = -self.y
def to_virtools_uv(self) -> None:
self.y = -self.y
class VxVector3():
x: float
y: float
z: float
def __init__(self, _x: float = 0.0, _y: float = 0.0, _z: float = 0.0):
self.x = _x
self.y = _y
self.z = _z
def from_const(self, cv: ConstVxVector3) -> None:
(self.x, self.y, self.z) = cv
def to_const(self) -> ConstVxVector3:
return (self.x, self.y, self.z)
# both position and normal convertion just swap y and z factor.
def to_blender_pos(self) -> None:
self.y, self.z = self.z, self.y
def to_virtools_pos(self) -> None:
self.y, self.z = self.z, self.y
def to_blender_nml(self) -> None:
self.y, self.z = self.z, self.y
def to_virtools_nml(self) -> None:
self.y, self.z = self.z, self.y
ConstVxColorRGBA = tuple[float, float, float, float]
ConstVxColorRGB = tuple[float, float, float]
class VxColor():
"""
The Color struct support RGBA.