[Bf-extensions-cvs] [564fdcdf] master: glTF exporter: Manage skinning when some vertices are not weights at all

Julien Duroure noreply at git.blender.org
Tue Mar 29 17:44:51 CEST 2022


Commit: 564fdcdf7159013bc7087c9d12f2a034cfa3a945
Author: Julien Duroure
Date:   Tue Mar 29 17:44:36 2022 +0200
Branches: master
https://developer.blender.org/rBA564fdcdf7159013bc7087c9d12f2a034cfa3a945

glTF exporter: Manage skinning when some vertices are not weights at all

===================================================================

M	io_scene_gltf2/__init__.py
M	io_scene_gltf2/blender/exp/gltf2_blender_extract.py
M	io_scene_gltf2/blender/exp/gltf2_blender_gather.py
M	io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
M	io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
M	io_scene_gltf2/io/imp/gltf2_io_binary.py

===================================================================

diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 11dbdce6..4ff7ff69 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -4,7 +4,7 @@
 bl_info = {
     'name': 'glTF 2.0 format',
     'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
-    "version": (3, 2, 16),
+    "version": (3, 2, 17),
     'blender': (3, 1, 0),
     'location': 'File > Import-Export',
     'description': 'Import-Export as glTF 2.0',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index d81bd706..98e2ac19 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -82,7 +82,14 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
 
     locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings)
     if skin:
-        vert_bones, num_joint_sets = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
+        vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
+        if need_neutral_bone is True:
+            # Need to create a fake joint at root of armature
+            # In order to assign not assigned vertices to it
+            # But for now, this is not yet possible, we need to wait the armature node is created
+            # Just store this, to be used later
+            armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature
+            export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True
 
     # In Blender there is both per-vert data, like position, and also per-loop
     # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert
@@ -535,6 +542,9 @@ def __get_colors(blender_mesh, color_i):
 
 
 def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
+
+    need_neutral_bone = False
+
     joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)}
     group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups]
 
@@ -557,7 +567,10 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
                     continue
                 bones.append((joint, weight))
         bones.sort(key=lambda x: x[1], reverse=True)
-        if not bones: bones = ((0, 1.0),)  # HACK for verts with zero weight (#308)
+        if not bones:
+            # Is not assign to any bone
+            bones = ((len(skin.joints), 1.0),)  # Assign to a joint that will be created later
+            need_neutral_bone = True
         vert_bones.append(bones)
         if len(bones) > max_num_influences:
             max_num_influences = len(bones)
@@ -565,7 +578,7 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
     # How many joint sets do we need? 1 set = 4 influences
     num_joint_sets = (max_num_influences + 3) // 4
 
-    return vert_bones, num_joint_sets
+    return vert_bones, num_joint_sets, need_neutral_bone
 
 
 def __zup2yup(array):
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index f515da8c..b3f4fd2a 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -68,6 +68,8 @@ def __gather_scene(blender_scene, export_settings):
         if node is not None:
             scene.nodes.append(node)
 
+    vtree.add_neutral_bones()
+
     export_user_extensions('gather_scene_hook', export_settings, scene, blender_scene)
 
     return scene
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
index 136d654d..3e4673e1 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
@@ -73,6 +73,9 @@ def __gather_inverse_bind_matrices(armature_uuid, export_settings):
         axis_basis_change = mathutils.Matrix(
             ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
 
+    # store matrix_world of armature in case we need to add a neutral bone
+    export_settings['vtree'].nodes[armature_uuid].matrix_world_armature = blender_armature_object.matrix_world.copy()
+
     bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
     def __collect_matrices(bone):
         inverse_bind_matrix = (
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
index 643cbea0..3a7b0fa5 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -3,10 +3,17 @@
 
 import bpy
 import uuid
+import numpy as np
 
 from . import gltf2_blender_export_keys
 from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
 from mathutils import Quaternion, Matrix
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.imp.gltf2_io_binary import BinaryData
+from io_scene_gltf2.io.com import gltf2_io_constants
+from .gltf2_blender_gather_primitive_attributes import array_to_accessor
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
 
 class VExportNode:
 
@@ -375,6 +382,68 @@ class VExportTree:
                 n.armature = candidates[0].uuid
             del n.armature_needed
 
+    def add_neutral_bones(self):
+        for n in [n for n in self.nodes.values() if n.armature is not None and n.blender_type == VExportNode.OBJECT and hasattr(self.nodes[n.armature], "need_neutral_bone")]: #all skin meshes objects where neutral bone is needed
+            # First add a new node
+
+            axis_basis_change = Matrix.Identity(4)
+            if self.export_settings[gltf2_blender_export_keys.YUP]:
+                axis_basis_change = Matrix(((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+            trans, rot, sca = axis_basis_change.decompose()
+            translation, rotation, scale = (None, None, None)
+            if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
+                translation = [trans[0], trans[1], trans[2]]
+            if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
+                rotation = [rot[1], rot[2], rot[3], rot[0]]
+            if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
+                scale = [sca[0], sca[1], sca[2]]
+            neutral_bone = gltf2_io.Node(
+                            camera=None,
+                            children=None,
+                            extensions=None,
+                            extras=None,
+                            matrix=None,
+                            mesh=None,
+                            name='neutral_bone',
+                            rotation=rotation,
+                            scale=scale,
+                            skin=None,
+                            translation=translation,
+                            weights=None
+                        )
+            # Add it to child list of armature
+            self.nodes[n.armature].node.children.append(neutral_bone)
+            # Add it to joint list
+            n.node.skin.joints.append(neutral_bone)
+
+            # Need to add an InverseBindMatrix
+            array = BinaryData.decode_accessor_internal(n.node.skin.inverse_bind_matrices)
+
+            axis_basis_change = Matrix.Identity(4)
+            if self.export_settings[gltf2_blender_export_keys.YUP]:
+                axis_basis_change = Matrix(
+                    ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+            inverse_bind_matrix = (
+                axis_basis_change @ self.nodes[n.armature].matrix_world_armature).inverted_safe()
+
+            matrix = []
+            for column in range(0, 4):
+                for row in range(0, 4):
+                        matrix.append(inverse_bind_matrix[row][column])
+
+            array = np.append(array, np.array([matrix]), axis=0)
+            binary_data = gltf2_io_binary_data.BinaryData.from_list(array.flatten(), gltf2_io_constants.ComponentType.Float)
+            n.node.skin.inverse_bind_matrices = gltf2_blender_gather_accessors.gather_accessor(
+                binary_data,
+                gltf2_io_constants.ComponentType.Float,
+                len(array.flatten()) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4),
+                None,
+                None,
+                gltf2_io_constants.DataType.Mat4,
+                self.export_settings
+            )
     def get_unused_skins(self):
         from .gltf2_blender_gather_skins import gather_skin
         skins = []
diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py
index 21bbb41b..995fd3c9 100755
--- a/io_scene_gltf2/io/imp/gltf2_io_binary.py
+++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py
@@ -77,6 +77,37 @@ class BinaryData():
 
         return array
 
+
+    @staticmethod
+    def decode_accessor_internal(accessor):
+        # Is use internally when accessor binary data is not yet in a glTF buffer_view
+        # MAT2/3 have special alignment requirements that aren't handled. But it
+        # doesn't matter because nothing uses them.
+        assert accessor.type not in ['MAT2', 'MAT3']
+
+        dtype = ComponentType.to_numpy_dtype(accessor.component_type)
+        component_nb = DataType.num_elements(accessor.type)
+
+        buffer_data = accessor.buffer_view.data
+
+        accessor_offset = accessor.byte_offset or 0
+        buffer_data = buffer_data[accessor_offset:]
+
+        bytes_per_elem = dtype(1).nbytes
+        default_stride = bytes_per_elem * component_nb
+        stride = default_stride
+
+        array = np.frombuffer(
+                    buffer_data,
+                    dtype=np.dtype(dtype).newbyteorder('<'),
+                    count=acces

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list