2023-10-20 10:40:20 +08:00
import bpy , bmesh , mathutils
2023-10-17 11:50:31 +08:00
import typing , array , collections
from . import UTIL_functions , UTIL_virtools_types
2023-10-16 10:12:05 +08:00
## Blender Mesh Usage
# This module create a universal mesh visitor, including MeshReader, MeshWriter and MeshUVModifier
# for every other possible module using.
# Obviously, MeshReader is served for 2 exporter, MeshWriter is served for 2 importer.
# MeshWriter also served for BMERevenge module and Ballance element loading.
# MeshUVModifier is used by Flatten UV and Rail UV.
2023-10-17 11:50:31 +08:00
#
#region Assist Functions
2023-10-18 12:09:40 +08:00
class FaceVertexData ( ) :
mPosIdx : int
mNmlIdx : int
mUvIdx : int
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
def __init__ ( self , pos : int = 0 , nml : int = 0 , uv : int = 0 ) :
self . mPosIdx = pos
self . mNmlIdx = nml
self . mUvIdx = uv
class FaceData ( ) :
2023-10-19 10:56:33 +08:00
## @remark List or tuple. List is convenient for adding and removing
2023-12-01 23:27:53 +08:00
mIndices : tuple [ FaceVertexData , . . . ] | list [ FaceVertexData ]
2023-10-19 10:56:33 +08:00
## Face used material slot index
# @remark If material slot is empty, or this face do not use material, set this value to 0.
2023-10-18 12:09:40 +08:00
mMtlIdx : int
2023-10-19 10:56:33 +08:00
2023-12-01 23:27:53 +08:00
def __init__ ( self , indices : tuple [ FaceVertexData , . . . ] | list [ FaceVertexData ] = tuple ( ) , mtlidx : int = 0 ) :
2023-10-18 12:09:40 +08:00
self . mIndices = indices
self . mMtlIdx = mtlidx
2023-11-14 22:16:12 +08:00
2023-11-10 12:26:04 +08:00
def conv_co ( self ) - > None :
"""
Change indice order between Virtools and Blender
"""
if isinstance ( self . mIndices , list ) :
self . mIndices . reverse ( )
elif isinstance ( self . mIndices , tuple ) :
self . mIndices = self . mIndices [ : : - 1 ]
else :
raise UTIL_functions . BBPException ( ' invalid indices container. ' )
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
def is_indices_legal ( self ) - > bool :
return len ( self . mIndices ) > = 3
2023-11-10 12:26:04 +08:00
class MeshWriterIngredient ( ) :
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
2023-11-11 13:32:58 +08:00
mMaterial : typing . Iterator [ bpy . types . Material | None ] | None
2023-11-10 12:26:04 +08:00
def __init__ ( self ) :
self . mVertexPosition = None
self . mVertexNormal = None
self . mVertexUV = None
self . mFace = None
self . mMaterial = None
def is_valid ( self ) - > bool :
if self . mVertexPosition is None : return False
if self . mVertexNormal is None : return False
if self . mVertexUV is None : return False
if self . mFace is None : return False
if self . mMaterial is None : return False
return True
2023-10-18 21:23:04 +08:00
def _flat_vxvector3 ( it : typing . Iterator [ UTIL_virtools_types . VxVector3 ] ) - > typing . Iterator [ float ] :
2023-10-17 11:50:31 +08:00
for entry in it :
2023-10-18 12:09:40 +08:00
yield entry . x
yield entry . y
yield entry . z
2023-10-17 11:50:31 +08:00
2023-10-18 21:23:04 +08:00
def _flat_vxvector2 ( it : typing . Iterator [ UTIL_virtools_types . VxVector2 ] ) - > typing . Iterator [ float ] :
2023-10-17 11:50:31 +08:00
for entry in it :
2023-10-18 12:09:40 +08:00
yield entry . x
yield entry . y
2023-10-18 21:23:04 +08:00
def _flat_face_nml_index ( nml_idx : array . array , nml_array : array . array ) - > typing . Iterator [ float ] :
2023-10-18 12:09:40 +08:00
for idx in nml_idx :
pos : int = idx * 3
yield nml_array [ pos ]
yield nml_array [ pos + 1 ]
yield nml_array [ pos + 2 ]
2023-10-18 21:23:04 +08:00
def _flat_face_uv_index ( uv_idx : array . array , uv_array : array . array ) - > typing . Iterator [ float ] :
2023-10-18 12:09:40 +08:00
for idx in uv_idx :
pos : int = idx * 2
yield uv_array [ pos ]
yield uv_array [ pos + 1 ]
2023-11-14 22:16:12 +08:00
def _nest_custom_split_normal ( nml_array : array . array ) - > typing . Iterator [ UTIL_virtools_types . ConstVxVector3 ] :
# following statement create a iterator for normals array by `iter(nml_array)`
# 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.
# 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 ) )
2023-10-17 11:50:31 +08:00
2023-10-19 10:56:33 +08:00
class TemporaryMesh ( ) :
"""
"""
2024-05-25 20:32:30 +08:00
__mBindingObject : bpy . types . Object
__mTempMesh : bpy . types . Mesh
2023-10-19 10:56:33 +08:00
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
2023-10-20 10:40:20 +08:00
def dispose ( self ) - > None :
2023-10-19 10:56:33 +08:00
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
2023-10-17 11:50:31 +08:00
#endregion
2023-10-16 10:12:05 +08:00
class MeshReader ( ) :
2023-10-17 11:50:31 +08:00
"""
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 .
2023-10-19 10:56:33 +08:00
A helper class TemporaryMesh can help you do this .
2023-10-17 11:50:31 +08:00
"""
2023-10-19 10:56:33 +08:00
2024-05-25 20:32:30 +08:00
__mAssocMesh : bpy . types . Mesh ##< The binding mesh for this reader. None if this reader is invalid.
2023-10-19 10:56:33 +08:00
def __init__ ( self , assoc_mesh : bpy . types . Mesh ) :
self . __mAssocMesh = assoc_mesh
# triangulate temp mesh
if self . is_valid ( ) :
self . __triangulate_mesh ( )
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 ( )
2023-10-20 10:40:20 +08:00
def dispose ( self ) - > None :
2023-10-19 10:56:33 +08:00
if self . is_valid ( ) :
# reset mesh
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. ' )
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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. ' )
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
cache : UTIL_virtools_types . VxVector3 = UTIL_virtools_types . VxVector3 ( )
2024-07-17 19:45:35 +08:00
for nml in self . __mAssocMesh . corner_normals :
cache . x = nml . vector . x
cache . y = nml . vector . y
cache . z = nml . vector . z
2023-10-19 10:56:33 +08:00
yield cache
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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 )
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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. ' )
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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 )
2023-11-14 22:16:12 +08:00
2023-11-11 13:32:58 +08:00
def get_material_slot ( self ) - > typing . Iterator [ bpy . types . Material | None ] :
2023-10-19 10:56:33 +08:00
"""
@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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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 )
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
# 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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
# 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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
# return value
yield cache
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
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
2023-11-14 22:16:12 +08:00
2023-10-19 10:56:33 +08:00
def __triangulate_mesh ( self ) - > None :
2023-11-10 12:26:04 +08:00
bm : bmesh . types . BMesh = bmesh . new ( )
2023-10-19 10:56:33 +08:00
bm . from_mesh ( self . __mAssocMesh )
bmesh . ops . triangulate ( bm , faces = bm . faces )
bm . to_mesh ( self . __mAssocMesh )
bm . free ( )
2023-10-16 10:12:05 +08:00
class MeshWriter ( ) :
2023-10-17 11:50:31 +08:00
"""
2023-10-18 12:09:40 +08:00
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 .
2023-10-17 11:50:31 +08:00
"""
2024-05-25 20:32:30 +08:00
__mAssocMesh : bpy . types . Mesh ##< The binding mesh for this writer. None if this writer is invalid.
2023-10-17 11:50:31 +08:00
__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).
2023-10-18 12:09:40 +08:00
# 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
2023-10-19 10:56:33 +08:00
## Array item is int32(L).
2023-10-17 11:50:31 +08:00
# Length is the face count.
2023-10-18 12:09:40 +08:00
# It indicate how much vertex need to be consumed in __mFacePosIndices, __mFaceNmlIndices and __mFaceUvIndices for one face.
2023-10-17 11:50:31 +08:00
__mFaceVertexCount : array . array
__mFaceMtlIdx : array . array ##< Array item is int32(L). Length is equal to __mFaceVertexCount.
2023-10-18 12:09:40 +08:00
## Material Slot.
# Each item is unique make sure by __mMtlSlotMap
2023-11-11 13:32:58 +08:00
__mMtlSlot : list [ bpy . types . Material | None ]
2023-10-18 12:09:40 +08:00
## The map to make sure every item in __mMtlSlot is unique.
2023-10-19 10:56:33 +08:00
# Key is bpy.types.Material
2023-10-18 12:09:40 +08:00
# Value is key's index in __mMtlSlot.
2023-11-11 13:32:58 +08:00
__mMtlSlotMap : dict [ bpy . types . Material | None , int ]
2023-10-17 11:50:31 +08:00
def __init__ ( self , assoc_mesh : bpy . types . Mesh ) :
self . __mAssocMesh = assoc_mesh
self . __mVertexPos = array . array ( ' f ' )
self . __mVertexNormal = array . array ( ' f ' )
self . __mVertexUV = array . array ( ' f ' )
2023-10-18 12:09:40 +08:00
self . __mFacePosIndices = array . array ( ' L ' )
self . __mFaceNmlIndices = array . array ( ' L ' )
self . __mFaceUvIndices = array . array ( ' L ' )
2023-10-17 11:50:31 +08:00
self . __mFaceVertexCount = array . array ( ' L ' )
self . __mFaceMtlIdx = array . array ( ' L ' )
self . __mMtlSlot = [ ]
self . __mMtlSlotMap = { }
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 ( )
2023-10-19 10:56:33 +08:00
def dispose ( self ) :
if self . is_valid ( ) :
# write mesh
self . __write_mesh ( )
# reset mesh
self . __mAssocMesh = None
2023-11-10 12:26:04 +08:00
def add_ingredient ( self , data : MeshWriterIngredient ) :
2023-10-19 10:56:33 +08:00
if not self . is_valid ( ) :
raise UTIL_functions . BBPException ( ' try to call an invalid MeshWriter. ' )
2023-10-17 11:50:31 +08:00
if not data . is_valid ( ) :
raise UTIL_functions . BBPException ( ' invalid mesh part data. ' )
# add vertex data
2023-11-10 12:26:04 +08:00
prev_vertex_pos_count : int = len ( self . __mVertexPos ) / / 3
2023-10-18 21:23:04 +08:00
self . __mVertexPos . extend ( _flat_vxvector3 ( data . mVertexPosition ) )
2023-11-10 12:26:04 +08:00
prev_vertex_nml_count : int = len ( self . __mVertexNormal ) / / 3
2023-10-18 21:23:04 +08:00
self . __mVertexNormal . extend ( _flat_vxvector3 ( data . mVertexNormal ) )
2023-11-10 12:26:04 +08:00
prev_vertex_uv_count : int = len ( self . __mVertexUV ) / / 2
2023-10-18 21:23:04 +08:00
self . __mVertexUV . extend ( _flat_vxvector2 ( data . mVertexUV ) )
2023-10-17 11:50:31 +08:00
# add material slot data and create mtl remap
mtl_remap : list [ int ] = [ ]
for mtl in data . mMaterial :
idx : int | None = self . __mMtlSlotMap . get ( mtl , None )
2023-11-10 12:26:04 +08:00
if idx is not None :
2023-10-17 11:50:31 +08:00
mtl_remap . append ( idx )
else :
self . __mMtlSlotMap [ mtl ] = len ( self . __mMtlSlot )
mtl_remap . append ( len ( self . __mMtlSlot ) )
self . __mMtlSlot . append ( mtl )
# add face data
for face in data . mFace :
# check indices count
2023-10-18 12:09:40 +08:00
if not face . is_indices_legal ( ) :
2023-10-17 11:50:31 +08:00
raise UTIL_functions . BBPException ( ' face must have at least 3 vertex. ' )
# add indices
2023-10-18 12:09:40 +08:00
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 ) )
2023-10-17 11:50:31 +08:00
# add face mtl with remap
2023-10-18 12:09:40 +08:00
mtl_idx : int = face . mMtlIdx
2023-11-10 12:26:04 +08:00
if mtl_idx < 0 or mtl_idx > = len ( mtl_remap ) :
2023-10-18 12:09:40 +08:00
# fall back. add 0
self . __mFaceMtlIdx . append ( 0 )
else :
self . __mFaceMtlIdx . append ( mtl_remap [ mtl_idx ] )
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
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 )
2023-11-14 22:16:12 +08:00
2023-10-18 12:09:40 +08:00
# add corresponding count for vertex position
2023-11-10 12:26:04 +08:00
self . __mAssocMesh . vertices . add ( len ( self . __mVertexPos ) / / 3 )
2023-10-18 12:09:40 +08:00
# 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 )
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# 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 ' ,
2023-11-14 22:16:12 +08:00
tuple ( _flat_face_nml_index ( self . __mFaceNmlIndices , self . __mVertexNormal ) )
2023-10-18 12:09:40 +08:00
)
# add face vertex uv by function
2023-10-23 10:57:29 +08:00
self . __mAssocMesh . uv_layers . active . uv . foreach_set ( ' vector ' ,
2023-11-14 22:16:12 +08:00
tuple ( _flat_face_uv_index ( self . __mFaceUvIndices , self . __mVertexUV ) )
2023-10-18 12:09:40 +08:00
) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# iterate face to set face data
2023-11-14 22:16:12 +08:00
f_vertex_idx : int = 0
2023-10-18 12:09:40 +08:00
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
2023-11-14 22:16:12 +08:00
self . __mAssocMesh . polygons [ fi ] . loop_start = f_vertex_idx
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# set material index
self . __mAssocMesh . polygons [ fi ] . material_index = self . __mFaceMtlIdx [ fi ]
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# set auto smooth. it is IMPORTANT
# because it related to whether custom split normal can work
self . __mAssocMesh . polygons [ fi ] . use_smooth = True
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# inc vertex idx
2023-11-14 22:16:12 +08:00
f_vertex_idx + = self . __mFaceVertexCount [ fi ]
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# validate mesh.
# it is IMPORTANT that do NOT delete custom data
# because we need use these data to set custom split normal later
2023-11-14 21:37:29 +08:00
self . __mAssocMesh . validate ( clean_customdata = False )
2023-10-18 12:09:40 +08:00
# update mesh without mesh calc
self . __mAssocMesh . update ( calc_edges = False , calc_edges_loose = False )
2023-10-19 10:56:33 +08:00
2023-10-18 12:09:40 +08:00
# set custom split normal data
2023-11-14 22:16:12 +08:00
# this operation must copy preserved normal data from loops, not the array data in this class,
# 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, for testing, please load "Level_1.NMO" (Ballance Level 1).
# copy data from loops preserved in validate().
loops_normals = array . array ( ' f ' , [ 0.0 ] * ( len ( self . __mAssocMesh . loops ) * 3 ) )
self . __mAssocMesh . loops . foreach_get ( ' normal ' , loops_normals )
# apply data
self . __mAssocMesh . normals_split_custom_set (
tuple ( _nest_custom_split_normal ( loops_normals ) )
)
2023-10-18 12:09:40 +08:00
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 ( )