mesh.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. """Exports a normal triangle mesh"""
  2. import logging
  3. import bpy
  4. import bmesh
  5. import mathutils
  6. from .material import export_material
  7. from ..structures import (Array, NodeTemplate, InternalResource, NodePath,
  8. Map, gamma_correct)
  9. from . import physics
  10. from . import armature
  11. from . import animation
  12. MAX_BONE_PER_VERTEX = 4
  13. # ------------------------------- The Mesh -----------------------------------
  14. def export_mesh_node(escn_file, export_settings, obj, parent_gd_node):
  15. """Exports a MeshInstance. If the mesh is not already exported, it will
  16. trigger the export of that mesh"""
  17. # If this mesh object has physics properties, we need to export them first
  18. # because they need to be higher in the scene-tree
  19. if physics.has_physics(obj):
  20. parent_gd_node = physics.export_physics_properties(
  21. escn_file, export_settings, obj, parent_gd_node
  22. )
  23. if physics.has_physics(obj) and obj.display_type == "WIRE":
  24. # skip wire mesh which is used as collision mesh
  25. return parent_gd_node
  26. mesh_node = NodeTemplate(obj.name, "MeshInstance", parent_gd_node)
  27. mesh_exporter = MeshResourceExporter(obj)
  28. armature_data = get_modifier_armature_data(obj)
  29. if ("ARMATURE" in export_settings['object_types'] and
  30. armature_data is not None):
  31. skeleton_node = armature.find_skeletion_node(parent_gd_node)
  32. if skeleton_node is not None:
  33. mesh_exporter.init_mesh_bones_data(skeleton_node)
  34. mesh_node['skeleton'] = NodePath(
  35. mesh_node.get_path(), skeleton_node.get_path())
  36. mesh_id = mesh_exporter.export_mesh(escn_file, export_settings)
  37. mesh_node['mesh'] = "SubResource({})".format(mesh_id)
  38. mesh_node['visible'] = obj.visible_get()
  39. # Transform of rigid mesh is moved up to its collision
  40. # shapes.
  41. if not physics.has_physics(obj):
  42. mesh_node['transform'] = obj.matrix_local
  43. else:
  44. mesh_node['transform'] = mathutils.Matrix.Identity(4)
  45. escn_file.add_node(mesh_node)
  46. export_object_link_material(
  47. escn_file, export_settings, obj, mesh_node
  48. )
  49. # export shape key animation
  50. if (export_settings['use_export_shape_key'] and
  51. obj.data.shape_keys is not None):
  52. animation.export_animation_data(
  53. escn_file, export_settings, mesh_node,
  54. obj.data.shape_keys, 'shapekey')
  55. return mesh_node
  56. def triangulate_mesh(mesh):
  57. """Triangulate a mesh"""
  58. tri_mesh = bmesh.new()
  59. tri_mesh.from_mesh(mesh)
  60. bmesh.ops.triangulate(
  61. tri_mesh, faces=tri_mesh.faces, quad_method="ALTERNATE")
  62. tri_mesh.to_mesh(mesh)
  63. tri_mesh.free()
  64. mesh.update(calc_loop_triangles=True)
  65. def fix_vertex(vtx):
  66. """Changes a single position vector from y-up to z-up"""
  67. return mathutils.Vector((vtx.x, vtx.z, -vtx.y))
  68. def get_modifier_armature_data(mesh_object):
  69. """Get the armature modifier of a blender object
  70. if does not have one, return None"""
  71. for modifier in mesh_object.modifiers:
  72. if (isinstance(modifier, bpy.types.ArmatureModifier) and
  73. modifier.object is not None):
  74. return modifier.object.data
  75. return None
  76. def export_object_link_material(escn_file, export_settings, mesh_object,
  77. gd_node):
  78. """Export object linked material, if multiple object link material,
  79. only export the first one in the material slots"""
  80. mesh_resource_id = escn_file.get_internal_resource(mesh_object.data)
  81. mesh_resource = escn_file.internal_resources[mesh_resource_id - 1]
  82. for index, slot in enumerate(mesh_object.material_slots):
  83. if slot.link == 'OBJECT' and slot.material is not None:
  84. surface_id = mesh_resource.get_surface_id(index)
  85. if surface_id is not None:
  86. gd_node['material/{}'.format(surface_id)] = export_material(
  87. escn_file,
  88. export_settings,
  89. slot.material
  90. )
  91. class ArrayMeshResource(InternalResource):
  92. """Godot ArrayMesh resource, containing surfaces"""
  93. def __init__(self, name):
  94. super().__init__('ArrayMesh', name)
  95. self._mat_to_surf_mapping = dict()
  96. def get_surface_id(self, material_index):
  97. """Given blender material index, return the corresponding
  98. surface id"""
  99. return self._mat_to_surf_mapping.get(material_index, None)
  100. def set_surface_id(self, material_index, surface_id):
  101. """Set a relation between material and surface"""
  102. self._mat_to_surf_mapping[material_index] = surface_id
  103. class MeshResourceExporter:
  104. """Export a mesh resource from a blender mesh object"""
  105. def __init__(self, mesh_object):
  106. # blender mesh object
  107. self.object = mesh_object
  108. self.mesh_resource = None
  109. self.has_tangents = False
  110. self.vgroup_to_bone_mapping = dict()
  111. def init_mesh_bones_data(self, skeleton_node):
  112. """Find the mapping relation between vertex groups
  113. and bone id"""
  114. for bone_name, bone_info in skeleton_node.bones.items():
  115. group = self.object.vertex_groups.get(bone_name)
  116. if group is not None:
  117. self.vgroup_to_bone_mapping[group.index] = bone_info.id
  118. def export_mesh(self, escn_file, export_settings):
  119. """Saves a mesh into the escn file """
  120. # Check if it exists so we don't bother to export it twice
  121. mesh = self.object.data
  122. mesh_id = escn_file.get_internal_resource(mesh)
  123. if mesh_id is not None:
  124. return mesh_id
  125. self.mesh_resource = ArrayMeshResource(mesh.name)
  126. self.make_arrays(
  127. escn_file,
  128. export_settings,
  129. )
  130. mesh_id = escn_file.add_internal_resource(self.mesh_resource, mesh)
  131. assert mesh_id is not None
  132. return mesh_id
  133. def make_arrays(self, escn_file, export_settings):
  134. """Generates arrays of positions, normals etc"""
  135. apply_modifiers = export_settings['use_mesh_modifiers']
  136. # set shape key to basis key which would have index 0
  137. self.object.show_only_shape_key = True
  138. self.object.active_shape_key_index = 0
  139. mesh = self.object.to_mesh(bpy.context.view_layer.depsgraph,
  140. apply_modifiers=apply_modifiers,
  141. calc_undeformed=True)
  142. self.object.show_only_shape_key = False
  143. # if the original mesh has an object link material,
  144. # the new created mesh would use it as data link material,
  145. # seems a bug of Blender,
  146. # here is a simple fix, not sure if it is robust enough..
  147. for idx in range(len(mesh.materials)):
  148. mesh.materials[idx] = self.object.data.materials[idx]
  149. # Prepare the mesh for export
  150. triangulate_mesh(mesh)
  151. # godot engine supports two uv channels
  152. uv_layer_count = min(len(mesh.uv_layers), 2)
  153. if mesh.uv_layers:
  154. self.has_tangents = True
  155. try:
  156. mesh.calc_tangents()
  157. except RuntimeError:
  158. # This fails if the mesh is a single vertex (and presumably an
  159. # edge). Since this won't be rendered by visualserver (the only
  160. # user of the tangents), we'll just disable tangents and hope
  161. # for the best....
  162. self.has_tangents = False
  163. else:
  164. mesh.calc_normals_split()
  165. self.has_tangents = False
  166. # Separate by materials into single-material surfaces
  167. self.generate_surfaces(
  168. escn_file,
  169. export_settings,
  170. mesh
  171. )
  172. bpy.data.meshes.remove(mesh)
  173. @staticmethod
  174. def extract_shape_keys(blender_shape_keys):
  175. """Return a list of (shape_key_index, shape_key_object) each of them
  176. is a shape key needs exported"""
  177. # base shape key needn't be exported
  178. ret = list()
  179. base_key = blender_shape_keys.reference_key
  180. for index, shape_key in enumerate(blender_shape_keys.key_blocks):
  181. if shape_key != base_key:
  182. ret.append((index, shape_key))
  183. return ret
  184. @staticmethod
  185. def validate_morph_mesh_modifiers(mesh_object):
  186. """Check whether a mesh has modifiers not
  187. compatible with shape key"""
  188. # this black list is not complete
  189. modifiers_not_supported = (
  190. bpy.types.SubsurfModifier,
  191. )
  192. for modifier in mesh_object.modifiers:
  193. if isinstance(modifier, modifiers_not_supported):
  194. return False
  195. return True
  196. @staticmethod
  197. def intialize_surfaces_morph_data(surfaces):
  198. """Initialize a list of empty morph for surfaces"""
  199. surfaces_morph_list = [VerticesArrays() for _ in range(len(surfaces))]
  200. for index, morph in enumerate(surfaces_morph_list):
  201. morph.vertices = [None] * len(surfaces[index].vertex_data.vertices)
  202. return surfaces_morph_list
  203. # pylint: disable-msg=R0914
  204. def export_morphs(self, export_settings, surfaces):
  205. """Export shape keys in mesh node and append them to surfaces"""
  206. modifier_config_cache = list()
  207. if export_settings['use_mesh_modifiers']:
  208. if not self.validate_morph_mesh_modifiers(
  209. self.object):
  210. logging.warning(
  211. "Mesh object '%s' has modifiers "
  212. "incompatible with shape key",
  213. self.object.name
  214. )
  215. for modifier in self.object.modifiers:
  216. modifier_config_cache.append(modifier.show_render)
  217. modifier.show_render = False
  218. self.mesh_resource["blend_shape/names"] = Array(
  219. prefix="PoolStringArray(", suffix=')'
  220. )
  221. self.mesh_resource["blend_shape/mode"] = 0
  222. shape_keys_to_export = self.extract_shape_keys(
  223. self.object.data.shape_keys
  224. )
  225. for index, shape_key in shape_keys_to_export:
  226. self.mesh_resource["blend_shape/names"].append(
  227. '"{}"'.format(shape_key.name)
  228. )
  229. self.object.show_only_shape_key = True
  230. self.object.active_shape_key_index = index
  231. shape_key.value = 1.0
  232. shape_key_mesh = self.object.to_mesh(
  233. bpy.context.view_layer.depsgraph,
  234. apply_modifiers=True,
  235. calc_undeformed=True
  236. )
  237. self.object.show_only_shape_key = False
  238. triangulate_mesh(shape_key_mesh)
  239. if self.has_tangents:
  240. shape_key_mesh.calc_tangents()
  241. else:
  242. shape_key_mesh.calc_normals_split()
  243. surfaces_morph_data = self.intialize_surfaces_morph_data(surfaces)
  244. for face in shape_key_mesh.polygons:
  245. surface_index = self.mesh_resource.get_surface_id(
  246. face.material_index
  247. )
  248. surface = surfaces[surface_index]
  249. morph = surfaces_morph_data[surface_index]
  250. for loop_id in range(face.loop_total):
  251. loop_index = face.loop_start + loop_id
  252. new_vert = Vertex.create_from_mesh_loop(
  253. shape_key_mesh,
  254. loop_index,
  255. self.has_tangents,
  256. self.vgroup_to_bone_mapping
  257. )
  258. vertex_index = surface.vertex_index_map[loop_index]
  259. morph.vertices[vertex_index] = new_vert
  260. for surf_index, surf in enumerate(surfaces):
  261. surf.morph_arrays.append(surfaces_morph_data[surf_index])
  262. bpy.data.meshes.remove(shape_key_mesh)
  263. if export_settings['use_mesh_modifiers']:
  264. for index, modifier in enumerate(self.object.modifiers):
  265. modifier.show_render = modifier_config_cache[index]
  266. def generate_surfaces(self, escn_file, export_settings, mesh):
  267. """Splits up the mesh into surfaces with a single material each.
  268. Within this, it creates the Vertex structure to contain all data about
  269. a single vertex
  270. """
  271. surfaces = []
  272. for face_index in range(len(mesh.polygons)):
  273. face = mesh.polygons[face_index]
  274. # Find a surface that matches the material, otherwise create a new
  275. # surface for it
  276. surface_index = self.mesh_resource.get_surface_id(
  277. face.material_index
  278. )
  279. if surface_index is None:
  280. surface_index = len(surfaces)
  281. self.mesh_resource.set_surface_id(
  282. face.material_index, surface_index
  283. )
  284. surface = Surface()
  285. surface.id = surface_index
  286. surfaces.append(surface)
  287. if mesh.materials:
  288. mat = mesh.materials[face.material_index]
  289. if mat is not None:
  290. surface.material = export_material(
  291. escn_file,
  292. export_settings,
  293. mat
  294. )
  295. surface = surfaces[surface_index]
  296. vertex_indices = []
  297. for loop_id in range(face.loop_total):
  298. loop_index = face.loop_start + loop_id
  299. new_vert = Vertex.create_from_mesh_loop(
  300. mesh,
  301. loop_index,
  302. self.has_tangents,
  303. self.vgroup_to_bone_mapping
  304. )
  305. # Merge similar vertices
  306. tup = new_vert.get_tup()
  307. if tup not in surface.vertex_map:
  308. surface.vertex_map[tup] = len(surface.vertex_data.vertices)
  309. surface.vertex_data.vertices.append(new_vert)
  310. vertex_index = surface.vertex_map[tup]
  311. surface.vertex_index_map[loop_index] = vertex_index
  312. vertex_indices.append(vertex_index)
  313. if len(vertex_indices) > 2: # Only triangles and above
  314. surface.vertex_data.indices.append(vertex_indices)
  315. if (export_settings['use_export_shape_key'] and
  316. self.object.data.shape_keys):
  317. self.export_morphs(export_settings, surfaces)
  318. has_bone = bool(self.vgroup_to_bone_mapping)
  319. for surface in surfaces:
  320. surface.vertex_data.has_bone = has_bone
  321. for vert_array in surface.morph_arrays:
  322. vert_array.has_bone = has_bone
  323. self.mesh_resource[surface.name_str] = surface
  324. class VerticesArrays:
  325. """Godot use several arrays to store the data of a surface(e.g. vertices,
  326. indices, bone weights). A surface object has a single VerticesArrays as its
  327. default and also may have a morph array with a list of VerticesArrays"""
  328. def __init__(self):
  329. self.vertices = []
  330. self.indices = []
  331. self.has_bone = False
  332. def calc_tangent_dp(self, vert):
  333. """Calculates the dot product of the tangent. I think this has
  334. something to do with normal mapping"""
  335. cross_product = vert.normal.cross(vert.tangent)
  336. dot_product = cross_product.dot(vert.bitangent)
  337. return 1.0 if dot_product > 0.0 else -1.0
  338. def get_color_array(self):
  339. """Generate a single array that contains the colors of all the vertices
  340. in this surface"""
  341. has_colors = self.vertices[0].color is not None
  342. if has_colors:
  343. color_vals = Array("ColorArray(")
  344. for vert in self.vertices:
  345. col = list(vert.color)
  346. if len(col) == 3:
  347. col += [1.0]
  348. color_vals.extend(col)
  349. else:
  350. color_vals = Array("null, ; no Vertex Colors", "", "")
  351. return color_vals
  352. def get_tangent_array(self):
  353. """Generate a single array that contains the tangents of all the
  354. vertices in this surface"""
  355. has_tangents = self.vertices[0].tangent is not None
  356. if has_tangents:
  357. tangent_vals = Array("FloatArray(")
  358. for vert in self.vertices:
  359. tangent_vals.extend(
  360. list(vert.tangent) + [self.calc_tangent_dp(vert)]
  361. )
  362. else:
  363. tangent_vals = Array("null, ; No Tangents", "", "")
  364. return tangent_vals
  365. def get_uv_array(self, uv_index):
  366. """Returns an array representing the specified UV index"""
  367. uv_layer_count = len(self.vertices[0].uv)
  368. if uv_index >= uv_layer_count:
  369. # If lacking 2 UV layers, mark them as null
  370. return Array("null, ; No UV"+str(uv_index+1), "", "")
  371. uv_vals = Array("Vector2Array(")
  372. for vert in self.vertices:
  373. uv_vals.extend([
  374. vert.uv[uv_index].x,
  375. 1.0-vert.uv[uv_index].y
  376. ])
  377. return uv_vals
  378. def generate_lines(self):
  379. """Generates the various arrays that are part of the surface (eg
  380. normals, position etc.)"""
  381. surface_lines = Array(
  382. prefix='[\n\t\t', seperator=',\n\t\t', suffix='\n\t]'
  383. )
  384. position_vals = Array("Vector3Array(",
  385. values=[v.vertex for v in self.vertices])
  386. normal_vals = Array("Vector3Array(",
  387. values=[v.normal for v in self.vertices])
  388. surface_lines.append(position_vals.to_string())
  389. surface_lines.append(normal_vals.to_string())
  390. surface_lines.append(self.get_tangent_array().to_string())
  391. surface_lines.append(self.get_color_array().to_string())
  392. surface_lines.append(self.get_uv_array(0).to_string())
  393. surface_lines.append(self.get_uv_array(1).to_string())
  394. # Bones and Weights
  395. # Export armature data (if armature exists)
  396. bones, bone_weights = self._get_bone_arrays()
  397. surface_lines.append(bones.to_string())
  398. surface_lines.append(bone_weights.to_string())
  399. # Indices- each face is made of 3 verts, and these are the indices
  400. # in the vertex arrays. The backface is computed from the winding
  401. # order, hence v[2] before v[1]
  402. if self.indices:
  403. face_indices = Array(
  404. "IntArray(",
  405. values=[[v[0], v[2], v[1]] for v in self.indices]
  406. )
  407. else:
  408. # in morph, it has no indices
  409. face_indices = Array(
  410. "null, ; Morph Object", "", ""
  411. )
  412. surface_lines.append(face_indices.to_string())
  413. return surface_lines
  414. def _get_bone_arrays(self):
  415. """Returns the most influential bones and their weights"""
  416. if not self.has_bone:
  417. return [
  418. Array("null, ; No Bones", "", ""),
  419. Array("null, ; No Weights", "", "")
  420. ]
  421. # Skin Weights
  422. bone_idx_array = Array("IntArray(")
  423. bone_ws_array = Array("FloatArray(")
  424. for vert in self.vertices:
  425. weights = []
  426. for i in range(len(vert.bones)):
  427. weights.append((vert.bones[i], vert.weights[i]))
  428. weights = sorted(weights, key=lambda x: x[1], reverse=True)
  429. # totalw guaranteed to be not zero
  430. totalw = 0.0
  431. for index, weight in enumerate(weights):
  432. if index >= MAX_BONE_PER_VERTEX:
  433. break
  434. totalw += weight[1]
  435. for i in range(MAX_BONE_PER_VERTEX):
  436. if i < len(weights):
  437. bone_idx_array.append(weights[i][0])
  438. bone_ws_array.append(weights[i][1]/totalw)
  439. else:
  440. bone_idx_array.append(0)
  441. bone_ws_array.append(0.0)
  442. return bone_idx_array, bone_ws_array
  443. def to_string(self):
  444. """Serialize"""
  445. return self.generate_lines().to_string()
  446. class Surface:
  447. """A surface is a single part of a mesh (eg in blender, one mesh can have
  448. multiple materials. Godot calls these separate parts separate surfaces"""
  449. def __init__(self):
  450. # map from a Vertex.tup() to surface.vertex_data.indices
  451. self.vertex_map = dict()
  452. self.vertex_data = VerticesArrays()
  453. self.morph_arrays = Array(prefix="[", seperator=",\n", suffix="]")
  454. # map from mesh.loop_index to surface.vertex_data.indices
  455. self.vertex_index_map = dict()
  456. self.id = None
  457. self.material = None
  458. @property
  459. def name_str(self):
  460. """Used to separate surfaces that are part of the same mesh by their
  461. id"""
  462. return "surfaces/" + str(self.id)
  463. def generate_object(self):
  464. """Generate object with mapping structure which fully
  465. describe the surface"""
  466. surface_object = Map()
  467. if self.material is not None:
  468. surface_object['material'] = self.material
  469. surface_object['primitive'] = 4
  470. surface_object['arrays'] = self.vertex_data
  471. surface_object['morph_arrays'] = self.morph_arrays
  472. return surface_object
  473. def to_string(self):
  474. """Serialize"""
  475. return self.generate_object().to_string()
  476. class Vertex:
  477. """Stores all the attributes for a single vertex"""
  478. def get_tup(self):
  479. """Returns a tuple form of this vertex so that it can be hashed"""
  480. tup = (self.vertex.x, self.vertex.y, self.vertex.z, self.normal.x,
  481. self.normal.y, self.normal.z)
  482. for uv_data in self.uv:
  483. tup = tup + (uv_data.x, uv_data.y)
  484. if self.color is not None:
  485. tup = tup + (self.color.x, self.color.y, self.color.z)
  486. if self.tangent is not None:
  487. tup = tup + (self.tangent.x, self.tangent.y, self.tangent.z)
  488. if self.bitangent is not None:
  489. tup = tup + (self.bitangent.x, self.bitangent.y,
  490. self.bitangent.z)
  491. for bone in self.bones:
  492. tup = tup + (float(bone), )
  493. for weight in self.weights:
  494. tup = tup + (float(weight), )
  495. return tup
  496. @classmethod
  497. def create_from_mesh_loop(cls, mesh, loop_index, has_tangents,
  498. gid_to_bid_map):
  499. """Create a vertex from a blender mesh loop"""
  500. new_vert = cls()
  501. loop = mesh.loops[loop_index]
  502. new_vert.vertex = fix_vertex(mesh.vertices[loop.vertex_index].co)
  503. for uv_layer in mesh.uv_layers:
  504. new_vert.uv.append(mathutils.Vector(
  505. uv_layer.data[loop_index].uv
  506. ))
  507. if mesh.vertex_colors:
  508. new_vert.color = mathutils.Vector(
  509. gamma_correct(mesh.vertex_colors[0].data[loop_index].color))
  510. new_vert.normal = fix_vertex(loop.normal)
  511. if has_tangents:
  512. new_vert.tangent = fix_vertex(loop.tangent)
  513. new_vert.bitangent = fix_vertex(loop.bitangent)
  514. for vertex_group in mesh.vertices[loop.vertex_index].groups:
  515. if (vertex_group.group in gid_to_bid_map and
  516. vertex_group.weight != 0.0):
  517. new_vert.bones.append(gid_to_bid_map[vertex_group.group])
  518. new_vert.weights.append(vertex_group.weight)
  519. if gid_to_bid_map and not new_vert.weights:
  520. # vertex not assign to any bones
  521. logging.warning(
  522. "No bone assigned vertex detected in mesh '%s' "
  523. "at local position %s.",
  524. mesh.name,
  525. str(mesh.vertices[loop.vertex_index].co)
  526. )
  527. return new_vert
  528. __slots__ = ("vertex", "normal", "tangent", "bitangent", "color", "uv",
  529. "bones", "weights")
  530. def __init__(self):
  531. self.vertex = mathutils.Vector((0.0, 0.0, 0.0))
  532. self.normal = mathutils.Vector((0.0, 0.0, 0.0))
  533. self.tangent = None
  534. self.bitangent = None
  535. self.color = None
  536. self.uv = []
  537. self.bones = []
  538. self.weights = []