yyc12345
3372c7a4b7
Some checks failed
Publish docs via GitHub Pages / Deploy docs (push) Has been cancelled
- support light type when exporting virtools. - both import and export virtools file now support light type. however there is a slight bug about light direction need to be resolved. - fix bug that exporter throw exception if exported collection contain non-mesh object or exported object is not mesh object. - fix the usage of BMMeshTrans according to upstream changes.
531 lines
22 KiB
Python
531 lines
22 KiB
Python
import bpy, bmesh, mathutils
|
||
import typing, array, collections
|
||
from . import UTIL_functions, UTIL_virtools_types
|
||
|
||
## 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.
|
||
#
|
||
|
||
#region Assist Functions
|
||
|
||
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():
|
||
## @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, ...] | list[FaceVertexData] = tuple(), mtlidx: int = 0):
|
||
self.mIndices = indices
|
||
self.mMtlIdx = mtlidx
|
||
|
||
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.')
|
||
|
||
def is_indices_legal(self) -> bool:
|
||
return len(self.mIndices) >= 3
|
||
|
||
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
|
||
mMaterial: typing.Iterator[bpy.types.Material | None] | None
|
||
|
||
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
|
||
|
||
def _flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.VxVector3]) -> typing.Iterator[float]:
|
||
for entry in it:
|
||
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_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))
|
||
|
||
class TemporaryMesh():
|
||
"""
|
||
Create a temporary mesh for convenient exporting.
|
||
When exporting mesh, we need triangulate it first.
|
||
We create a temporary mesh to hold the triangulated mesh result.
|
||
So that original object will not be affected and keep its original geometry.
|
||
Please note passed bpy.types.Object must be Mesh Object.
|
||
"""
|
||
|
||
__mBindingObject: bpy.types.Object
|
||
__mTempMesh: bpy.types.Mesh
|
||
|
||
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) -> None:
|
||
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.
|
||
"""
|
||
|
||
__mAssocMesh: bpy.types.Mesh ##< 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()
|
||
|
||
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) -> None:
|
||
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.')
|
||
|
||
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.corner_normals:
|
||
cache.x = nml.vector.x
|
||
cache.y = nml.vector.y
|
||
cache.z = nml.vector.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 | None]:
|
||
"""
|
||
@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.types.BMesh = bmesh.new()
|
||
bm.from_mesh(self.__mAssocMesh)
|
||
bmesh.ops.triangulate(bm, faces = bm.faces)
|
||
bm.to_mesh(self.__mAssocMesh)
|
||
bm.free()
|
||
|
||
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.
|
||
"""
|
||
|
||
__mAssocMesh: bpy.types.Mesh ##< 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.
|
||
# 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 __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 | None]
|
||
## 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 | None, int]
|
||
|
||
## The attribute name storing temporary normals data inside mesh.
|
||
__cTempNormalAttrName: typing.ClassVar[str] = 'temp_custom_normals'
|
||
|
||
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')
|
||
|
||
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')
|
||
|
||
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()
|
||
|
||
def dispose(self):
|
||
if self.is_valid():
|
||
# write mesh
|
||
self.__write_mesh()
|
||
# reset mesh
|
||
self.__mAssocMesh = None
|
||
|
||
def add_ingredient(self, data: MeshWriterIngredient):
|
||
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.')
|
||
|
||
# add vertex data
|
||
prev_vertex_pos_count: int = len(self.__mVertexPos) // 3
|
||
self.__mVertexPos.extend(_flat_vxvector3(data.mVertexPosition))
|
||
prev_vertex_nml_count: int = len(self.__mVertexNormal) // 3
|
||
self.__mVertexNormal.extend(_flat_vxvector3(data.mVertexNormal))
|
||
prev_vertex_uv_count: int = len(self.__mVertexUV) // 2
|
||
self.__mVertexUV.extend(_flat_vxvector2(data.mVertexUV))
|
||
|
||
# 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)
|
||
if idx is not None:
|
||
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
|
||
if not face.is_indices_legal():
|
||
raise UTIL_functions.BBPException('face must have at least 3 vertex.')
|
||
|
||
# add 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.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 __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) // 3)
|
||
# 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)
|
||
|
||
# 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 via mesh custom attribute
|
||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||
temp_normal_attribute: bpy.types.FloatVectorAttribute
|
||
temp_normal_attribute = typing.cast(
|
||
bpy.types.FloatVectorAttribute,
|
||
self.__mAssocMesh.attributes.new(MeshWriter.__cTempNormalAttrName, 'FLOAT_VECTOR', 'CORNER')
|
||
)
|
||
temp_normal_attribute.data.foreach_set('vector',
|
||
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
||
)
|
||
# add face vertex uv by function
|
||
self.__mAssocMesh.uv_layers.active.uv.foreach_set('vector',
|
||
tuple(_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
|
||
f_vertex_idx: 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 = f_vertex_idx
|
||
|
||
# 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
|
||
f_vertex_idx += 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
|
||
# 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().
|
||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
||
temp_normal_attribute = typing.cast(
|
||
bpy.types.FloatVectorAttribute,
|
||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||
)
|
||
temp_normal_attribute.data.foreach_get("vector", loops_normals)
|
||
# apply data
|
||
self.__mAssocMesh.normals_split_custom_set(
|
||
tuple(_nest_custom_split_normal(loops_normals))
|
||
)
|
||
self.__mAssocMesh.attributes.remove(
|
||
# MARK: idk why I need fucking get this attribute again.
|
||
# But if I were not, this function must raise bullshit exception!
|
||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||
)
|
||
|
||
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()
|