Only include vertex groups used for skinning purposes. More robust requirements for object to be exported as a skin. Misc cleanup and minor refactoring in msh_model_gather.

This commit is contained in:
William Herald Snyder 2022-10-04 13:27:07 -04:00
parent ec54df21d2
commit 0a1866295c
1 changed files with 74 additions and 31 deletions

View File

@ -32,32 +32,54 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
pure_bones_from_armature = {} pure_bones_from_armature = {}
armature_found = None armature_found = None
objects_to_export = select_objects(export_target) # Non-bone objects that will be exported
blender_objects_to_export = []
for uneval_obj in objects_to_export: # This must be seperate from the list above,
if uneval_obj.type == "ARMATURE": # since exported objects will contain Blender objects as well as bones
# Here we just keep track of all names, regardless of origin
exported_object_names: Set[str] = set()
# Armature must be processed before everything else!
# In this loop we also build a set of names of all objects
# that will be exported. This is necessary so we can prune vertex
# groups that do not reference exported objects in the main
# model building loop below this one.
for uneval_obj in select_objects(export_target):
if uneval_obj.type == "ARMATURE" and not armature_found:
# Keep track of the armature, we don't want to process > 1!
armature_found = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj armature_found = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj
# Get all bones in a separate list. While we iterate through
# objects we removed bones with geometry from this dict. After iteration
# is done, we add the remaining bones to the models from exported
# scene objects.
pure_bones_from_armature = expand_armature(armature_found) pure_bones_from_armature = expand_armature(armature_found)
break # All bones to set
exported_object_names.update(pure_bones_from_armature.keys())
elif not (uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents):
exported_object_names.add(uneval_obj.name)
blender_objects_to_export.append(uneval_obj)
else:
pass
for uneval_obj in objects_to_export: for uneval_obj in blender_objects_to_export:
if uneval_obj.type == "ARMATURE" or (uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents):
continue
obj = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj obj = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj
check_for_bad_lod_suffix(obj) check_for_bad_lod_suffix(obj)
# Test for a mesh object that is actually a BONE (shares name with bone_parent) # Test for a mesh object that should be a BONE on export.
# If so, we inject geometry into the BONE while not modifying it's transform/name # If so, we inject geometry into the BONE while not modifying it's transform/name
if obj.parent_bone and obj.parent_bone in pure_bones_from_armature: # and remove it from the set of BONES without geometry (pure).
model = pure_bones_from_armature[obj.parent_bone] if obj.name in pure_bones_from_armature:
# Since we found a composite bone, removed it from the dict of pure bones model = pure_bones_from_armature.pop(obj.name)
pure_bones_from_armature.pop(obj.parent_bone)
else: else:
model = Model() model = Model()
model.name = obj.name model.name = obj.name
model.model_type = get_model_type(obj, skeleton_only) model.model_type = ModelType.NULL if skeleton_only else get_model_type(obj, armature_found)
model.hidden = get_is_model_hidden(obj) model.hidden = get_is_model_hidden(obj)
transform = obj.matrix_local transform = obj.matrix_local
@ -82,10 +104,19 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
model.transform.rotation = convert_rotation_space(local_rotation) model.transform.rotation = convert_rotation_space(local_rotation)
model.transform.translation = convert_vector_space(local_translation) model.transform.translation = convert_vector_space(local_translation)
if obj.type in MESH_OBJECT_TYPES: if obj.type in MESH_OBJECT_TYPES and not skeleton_only:
# Vertex groups are often used for purposes other than skinning.
# Here we gather all vgroups and select the ones that reference
# objects included in the export.
valid_vgroup_indices : Set[int] = set()
if model.model_type == ModelType.SKIN:
valid_vgroups = [group for group in obj.vertex_groups if group.name in exported_object_names]
valid_vgroup_indices = { group.index for group in valid_vgroups }
model.bone_map = [ group.name for group in valid_vgroups ]
mesh = obj.to_mesh() mesh = obj.to_mesh()
model.geometry = create_mesh_geometry(mesh, obj.vertex_groups) model.geometry = create_mesh_geometry(mesh, valid_vgroup_indices)
obj.to_mesh_clear() obj.to_mesh_clear()
@ -98,9 +129,6 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has " raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has "
f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up " f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up "
f"and try again!") f"and try again!")
if obj.vertex_groups:
model.bone_map = [group.name for group in obj.vertex_groups]
if get_is_collision_primitive(obj): if get_is_collision_primitive(obj):
model.collisionprimitive = get_collision_primitive(obj) model.collisionprimitive = get_collision_primitive(obj)
@ -109,9 +137,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
# We removed all composite bones after looking through the objects, # We removed all composite bones after looking through the objects,
# so the bones left are all pure and we add them all here. # so the bones left are all pure and we add them all here.
models_list += pure_bones_from_armature.values() return (models_list + list(pure_bones_from_armature.values()), armature_found)
return (models_list, armature_found)
@ -127,7 +153,7 @@ def create_parents_set() -> Set[str]:
return parents return parents
def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[GeometrySegment]: def create_mesh_geometry(mesh: bpy.types.Mesh, valid_vgroup_indices: Set[int]) -> List[GeometrySegment]:
""" Creates a list of GeometrySegment objects from a Blender mesh. """ Creates a list of GeometrySegment objects from a Blender mesh.
Does NOT create triangle strips in the GeometrySegment however. """ Does NOT create triangle strips in the GeometrySegment however. """
@ -148,7 +174,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
for segment in segments: for segment in segments:
segment.colors = [] segment.colors = []
if has_weights: if valid_vgroup_indices:
for segment in segments: for segment in segments:
segment.weights = [] segment.weights = []
@ -185,8 +211,9 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
if segment.weights is not None: if segment.weights is not None:
for v in mesh.vertices[vertex_index].groups: for v in mesh.vertices[vertex_index].groups:
yield v.group if v.group in valid_vgroup_indices:
yield v.weight yield v.group
yield v.weight
vertex_cache_entry = tuple(get_cache_vertex()) vertex_cache_entry = tuple(get_cache_vertex())
cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index) cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index)
@ -213,8 +240,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
if segment.weights is not None: if segment.weights is not None:
groups = mesh.vertices[vertex_index].groups groups = mesh.vertices[vertex_index].groups
segment.weights.append([VertexWeight(v.weight, v.group) for v in groups if v.group in valid_vgroup_indices])
segment.weights.append([VertexWeight(v.weight, v.group) for v in groups])
return new_index return new_index
@ -233,12 +259,29 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
return segments return segments
def get_model_type(obj: bpy.types.Object, skel_only: bool) -> ModelType: def get_model_type(obj: bpy.types.Object, armature_found: bpy.types.Object) -> ModelType:
""" Get the ModelType for a Blender object. """ """ Get the ModelType for a Blender object. """
if obj.type in MESH_OBJECT_TYPES and not skel_only: if obj.type in MESH_OBJECT_TYPES:
if obj.vertex_groups: # Objects can have vgroups for non-skinning purposes.
return ModelType.SKIN # If we can find one vgroup that shares a name with a bone in the
# armature, we know the vgroup is for weighting purposes and thus
# the object is a skin. Otherwise, interpret it as a static mesh.
# We must also check that an armature included in the export
# and that it is the same one this potential skin is weighting to.
# If we failed to do this, a user could export a selected object
# that is a skin, but the weight data in the export would reference
# nonexistent models!
if (obj.vertex_groups and armature_found and
obj.parent and obj.parent.name == armature_found.name):
for vgroup in obj.vertex_groups:
if vgroup.name in armature_found.data.bones:
return ModelType.SKIN
return ModelType.STATIC
else: else:
return ModelType.STATIC return ModelType.STATIC