utils.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Util functions and structs shared by multiple resource converters"""
  2. import logging
  3. import bpy
  4. import bmesh
  5. def get_applicable_modifiers(obj, export_settings):
  6. """Returns a list of all the modifiers that'll be applied to the final
  7. godot mesh"""
  8. ignore_modifiers = []
  9. if not export_settings['use_mesh_modifiers']:
  10. return []
  11. if "ARMATURE" in export_settings['object_types']:
  12. ignore_modifiers.append(bpy.types.ArmatureModifier)
  13. ignore_modifiers = tuple(ignore_modifiers)
  14. return [m for m in obj.modifiers if not isinstance(m, ignore_modifiers)
  15. and m.show_viewport]
  16. def record_modifier_config(obj):
  17. """Returns modifiers viewport visibility config"""
  18. modifier_config_cache = []
  19. for mod in obj.modifiers:
  20. modifier_config_cache.append(mod.show_viewport)
  21. return modifier_config_cache
  22. def restore_modifier_config(obj, modifier_config_cache):
  23. """Applies modifiers viewport visibility config"""
  24. for i, mod in enumerate(obj.modifiers):
  25. mod.show_viewport = modifier_config_cache[i]
  26. def triangulate_ngons(mesh):
  27. """Triangulate n-gons in a mesh"""
  28. tri_mesh = bmesh.new()
  29. tri_mesh.from_mesh(mesh)
  30. ngons = [face for face in tri_mesh.faces if len(face.verts) > 4]
  31. bmesh.ops.triangulate(tri_mesh, faces=ngons, quad_method="ALTERNATE")
  32. tri_mesh.to_mesh(mesh)
  33. tri_mesh.free()
  34. if bpy.app.version[0] > 2 or bpy.app.version[1] > 80:
  35. mesh.update()
  36. else:
  37. mesh.update(calc_loop_triangles=True)
  38. class MeshResourceKey:
  39. """Produces a key based on an mesh object's data, every different
  40. Mesh Resource would have a unique key"""
  41. def __init__(self, rsc_type, obj, export_settings):
  42. mesh_data = obj.data
  43. # Resource type included because same blender mesh may be used as
  44. # MeshResource or CollisionShape, but they are different resources
  45. gd_rsc_type = rsc_type
  46. # Here collect info of all the modifiers applied on the mesh.
  47. # Modifiers along with the original mesh data would determine
  48. # the evaluated mesh.
  49. mod_info_list = list()
  50. for modifier in get_applicable_modifiers(obj, export_settings):
  51. # Modifier name indicates its type, it's an identifier
  52. mod_info_list.append(modifier.name)
  53. # First property is always 'rna_type', skip it
  54. for prop_key in modifier.bl_rna.properties.keys()[1:]:
  55. prop = modifier.bl_rna.properties[prop_key]
  56. prop_val = getattr(modifier, prop_key)
  57. if prop.type == 'COLLECTION':
  58. # For `CollectionProperty`, it would make more sense to
  59. # traversal it, however, we cut down here just for
  60. # simplicity allowing some of mesh resources not being
  61. # shared.
  62. mod_info_list.append(id(prop_val))
  63. elif prop.type == 'POINTER':
  64. # For `PointerProperty`, it points to a bpy.types.ID, its
  65. # hash value is the python object id, which is good as an
  66. # identifier.
  67. mod_info_list.append(prop_val)
  68. else:
  69. # Here Property may be `BoolProperty`, `EnumProperty`,
  70. # `FloatProperty`, `IntProperty`, `StringProperty`
  71. # they are primitive types and all good to be hashed.
  72. assert prop.type in \
  73. ('BOOLEAN', 'ENUM', 'INT', 'STRING', 'FLOAT')
  74. if isinstance(prop_val, (int, float, str, bool)) or \
  75. prop_val is None:
  76. mod_info_list.append(prop_val)
  77. else:
  78. # iterable properties
  79. mod_info_list.append(tuple(prop_val))
  80. # Precalculate the hash now for better efficiency later
  81. self._data = tuple([mesh_data, gd_rsc_type] + mod_info_list)
  82. self._hash = hash(self._data)
  83. def __hash__(self):
  84. return self._hash
  85. def __eq__(self, other):
  86. # pylint: disable=protected-access
  87. return (self.__class__ == other.__class__ and
  88. self._data == other._data)
  89. class MeshConverter:
  90. """MeshConverter evaulates and converts objects to meshes, triangulates
  91. and calculates tangents"""
  92. def __init__(self, obj, export_settings):
  93. self.object = obj
  94. self.eval_object = None
  95. self.use_mesh_modifiers = export_settings["use_mesh_modifiers"]
  96. self.use_export_shape_key = export_settings['use_export_shape_key']
  97. self.has_tangents = False
  98. def to_mesh(self, preserve_vertex_groups=True,
  99. calculate_tangents=True, shape_key_index=0):
  100. """Evaluates object & converts to final mesh, ready for export.
  101. The mesh is only temporary, call to_mesh_clear() afterwards."""
  102. # set shape key to basis key which would have index 0
  103. orig_shape_key_index = self.object.active_shape_key_index
  104. self.object.show_only_shape_key = True
  105. self.object.active_shape_key_index = shape_key_index
  106. self.eval_object = self.object
  107. modifier_config_cache = None
  108. if not self.use_mesh_modifiers:
  109. modifier_config_cache = record_modifier_config(self.object)
  110. for mod in self.object.modifiers:
  111. mod.show_viewport = False
  112. depsgraph = bpy.context.view_layer.depsgraph
  113. depsgraph.update()
  114. self.eval_object = self.object.evaluated_get(depsgraph)
  115. # These parameters are required for preserving vertex groups.
  116. mesh = self.eval_object.to_mesh(
  117. preserve_all_data_layers=preserve_vertex_groups,
  118. depsgraph=depsgraph
  119. )
  120. if not self.use_mesh_modifiers:
  121. restore_modifier_config(self.object, modifier_config_cache)
  122. self.has_tangents = False
  123. # mesh result can be none if the source geometry has no faces, so we
  124. # need to consider this if we want a robust exporter.
  125. if mesh is not None:
  126. self.has_tangents = bool(mesh.uv_layers) and bool(mesh.polygons)
  127. if calculate_tangents:
  128. if self.has_tangents:
  129. try:
  130. mesh.calc_tangents()
  131. except RuntimeError:
  132. # Mesh must have n-gons
  133. logging.warning(
  134. "Mesh '%s' had n-gons and had to be triangulated "
  135. "to calculate tangents; n-gons may look wrong.",
  136. mesh.name
  137. )
  138. triangulate_ngons(mesh)
  139. mesh.calc_tangents()
  140. else:
  141. mesh.calc_normals_split()
  142. self.object.show_only_shape_key = False
  143. self.object.active_shape_key_index = orig_shape_key_index
  144. return mesh
  145. def to_mesh_clear(self):
  146. """Clears the temporary generated mesh from memory"""
  147. if self.object is None:
  148. return
  149. self.eval_object.to_mesh_clear()
  150. self.object = self.eval_object = None