123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- """Util functions and structs shared by multiple resource converters"""
- import logging
- import bpy
- import bmesh
- def get_applicable_modifiers(obj, export_settings):
- """Returns a list of all the modifiers that'll be applied to the final
- godot mesh"""
- ignore_modifiers = []
- if not export_settings['use_mesh_modifiers']:
- return []
- if "ARMATURE" in export_settings['object_types']:
- ignore_modifiers.append(bpy.types.ArmatureModifier)
- ignore_modifiers = tuple(ignore_modifiers)
- return [m for m in obj.modifiers if not isinstance(m, ignore_modifiers)
- and m.show_viewport]
- def record_modifier_config(obj):
- """Returns modifiers viewport visibility config"""
- modifier_config_cache = []
- for mod in obj.modifiers:
- modifier_config_cache.append(mod.show_viewport)
- return modifier_config_cache
- def restore_modifier_config(obj, modifier_config_cache):
- """Applies modifiers viewport visibility config"""
- for i, mod in enumerate(obj.modifiers):
- mod.show_viewport = modifier_config_cache[i]
- def triangulate_ngons(mesh):
- """Triangulate n-gons in a mesh"""
- tri_mesh = bmesh.new()
- tri_mesh.from_mesh(mesh)
- ngons = [face for face in tri_mesh.faces if len(face.verts) > 4]
- bmesh.ops.triangulate(tri_mesh, faces=ngons, quad_method="ALTERNATE")
- tri_mesh.to_mesh(mesh)
- tri_mesh.free()
- if bpy.app.version[0] > 2 or bpy.app.version[1] > 80:
- mesh.update()
- else:
- mesh.update(calc_loop_triangles=True)
- class MeshResourceKey:
- """Produces a key based on an mesh object's data, every different
- Mesh Resource would have a unique key"""
- def __init__(self, rsc_type, obj, export_settings):
- mesh_data = obj.data
- # Resource type included because same blender mesh may be used as
- # MeshResource or CollisionShape, but they are different resources
- gd_rsc_type = rsc_type
- # Here collect info of all the modifiers applied on the mesh.
- # Modifiers along with the original mesh data would determine
- # the evaluated mesh.
- mod_info_list = list()
- for modifier in get_applicable_modifiers(obj, export_settings):
- # Modifier name indicates its type, it's an identifier
- mod_info_list.append(modifier.name)
- # First property is always 'rna_type', skip it
- for prop_key in modifier.bl_rna.properties.keys()[1:]:
- prop = modifier.bl_rna.properties[prop_key]
- prop_val = getattr(modifier, prop_key)
- if prop.type == 'COLLECTION':
- # For `CollectionProperty`, it would make more sense to
- # traversal it, however, we cut down here just for
- # simplicity allowing some of mesh resources not being
- # shared.
- mod_info_list.append(id(prop_val))
- elif prop.type == 'POINTER':
- # For `PointerProperty`, it points to a bpy.types.ID, its
- # hash value is the python object id, which is good as an
- # identifier.
- mod_info_list.append(prop_val)
- else:
- # Here Property may be `BoolProperty`, `EnumProperty`,
- # `FloatProperty`, `IntProperty`, `StringProperty`
- # they are primitive types and all good to be hashed.
- assert prop.type in \
- ('BOOLEAN', 'ENUM', 'INT', 'STRING', 'FLOAT')
- if isinstance(prop_val, (int, float, str, bool)) or \
- prop_val is None:
- mod_info_list.append(prop_val)
- else:
- # iterable properties
- mod_info_list.append(tuple(prop_val))
- # Precalculate the hash now for better efficiency later
- self._data = tuple([mesh_data, gd_rsc_type] + mod_info_list)
- self._hash = hash(self._data)
- def __hash__(self):
- return self._hash
- def __eq__(self, other):
- # pylint: disable=protected-access
- return (self.__class__ == other.__class__ and
- self._data == other._data)
- class MeshConverter:
- """MeshConverter evaulates and converts objects to meshes, triangulates
- and calculates tangents"""
- def __init__(self, obj, export_settings):
- self.object = obj
- self.eval_object = None
- self.use_mesh_modifiers = export_settings["use_mesh_modifiers"]
- self.use_export_shape_key = export_settings['use_export_shape_key']
- self.has_tangents = False
- def to_mesh(self, preserve_vertex_groups=True,
- calculate_tangents=True, shape_key_index=0):
- """Evaluates object & converts to final mesh, ready for export.
- The mesh is only temporary, call to_mesh_clear() afterwards."""
- # set shape key to basis key which would have index 0
- orig_shape_key_index = self.object.active_shape_key_index
- self.object.show_only_shape_key = True
- self.object.active_shape_key_index = shape_key_index
- self.eval_object = self.object
- modifier_config_cache = None
- if not self.use_mesh_modifiers:
- modifier_config_cache = record_modifier_config(self.object)
- for mod in self.object.modifiers:
- mod.show_viewport = False
- depsgraph = bpy.context.view_layer.depsgraph
- depsgraph.update()
- self.eval_object = self.object.evaluated_get(depsgraph)
- # These parameters are required for preserving vertex groups.
- mesh = self.eval_object.to_mesh(
- preserve_all_data_layers=preserve_vertex_groups,
- depsgraph=depsgraph
- )
- if not self.use_mesh_modifiers:
- restore_modifier_config(self.object, modifier_config_cache)
- self.has_tangents = False
- # mesh result can be none if the source geometry has no faces, so we
- # need to consider this if we want a robust exporter.
- if mesh is not None:
- self.has_tangents = bool(mesh.uv_layers) and bool(mesh.polygons)
- if calculate_tangents:
- if self.has_tangents:
- try:
- mesh.calc_tangents()
- except RuntimeError:
- # Mesh must have n-gons
- logging.warning(
- "Mesh '%s' had n-gons and had to be triangulated "
- "to calculate tangents; n-gons may look wrong.",
- mesh.name
- )
- triangulate_ngons(mesh)
- mesh.calc_tangents()
- else:
- mesh.calc_normals_split()
- self.object.show_only_shape_key = False
- self.object.active_shape_key_index = orig_shape_key_index
- return mesh
- def to_mesh_clear(self):
- """Clears the temporary generated mesh from memory"""
- if self.object is None:
- return
- self.eval_object.to_mesh_clear()
- self.object = self.eval_object = None
|