KhronosGroup / glTF-Blender-IO

Blender glTF 2.0 importer and exporter
https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html
Apache License 2.0
1.49k stars 318 forks source link

Crashes when importing with custom attributes #2000

Closed pjoe closed 1 year ago

pjoe commented 1 year ago

Describe the bug Trying to import .gltf exported from Source 2 Viewer, this contains primitives with custom attributes, e.g. _TEXCOORD_4. The file passes gltf validation, but causes crash in the blender importer.

To Reproduce Steps to reproduce the behavior:

  1. Export CS2 de_anubis usign Source 2 Viewer
  2. Try importing in Blender

Expected behavior The file is imported without crashes

Screenshots See https://github.com/ValveResourceFormat/ValveResourceFormat/issues/624

.blend file/ .gltf For IP reasons I cannot share here

Version

Additional context I have a fix for this against the 3.6 branch, but guessing you need PRs against main?

It seems pretty obvious that e.g. here accessing prim doesn't make sense: https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py#L149

FWIW: here is my diff:

diff --git a/addons/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/addons/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
index 4950d69d..33e9fa30 100644
--- a/addons/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
+++ b/addons/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
@@ -86,6 +86,10 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
     num_cols = 0
     num_joint_sets = 0
     attributes = set({})
+    attribute_data = []
+    attribute_type = {}
+    attribute_component_type = {}
+
     for prim in pymesh.primitives:
         if 'POSITION' not in prim.attributes:
             continue
@@ -109,7 +113,18 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
         while i < COLOR_MAX and ('COLOR_%d' % i) in prim.attributes: i += 1
         num_cols = max(i, num_cols)

-        attributes.update(set([k for k in prim.attributes if k.startswith('_')]))
+        custom_attrs = [k for k in prim.attributes if k.startswith('_')]
+        for attr in custom_attrs:
+            if not attr in attributes:
+                attribute_type[attr] = gltf.data.accessors[prim.attributes[attr]].type
+                attribute_component_type[attr] = gltf.data.accessors[prim.attributes[attr]].component_type
+                attribute_data.append(
+                    np.empty(
+                        dtype=ComponentType.to_numpy_dtype(attribute_component_type[attr]),
+                        shape=(0, DataType.num_elements(attribute_type[attr])))
+                        )
+        attributes.update(set(custom_attrs))
+

     num_shapekeys = sum(sk_name is not None for sk_name in pymesh.shapekey_names)

@@ -142,13 +157,6 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
         np.empty(dtype=np.float32, shape=(0,3))  # coordinate for each vert for each shapekey
         for _ in range(num_shapekeys)
     ]
-    attribute_data = []
-    for attr in attributes:
-        attribute_data.append(
-            np.empty(
-                dtype=ComponentType.to_numpy_dtype(gltf.data.accessors[prim.attributes[attr]].component_type),
-                shape=(0, DataType.num_elements(gltf.data.accessors[prim.attributes[attr]].type)))
-                )

     for prim in pymesh.primitives:
         prim.num_faces = 0
@@ -253,12 +261,13 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
         for idx, attr in enumerate(attributes):
             if attr in prim.attributes:
                 attr_data = BinaryData.decode_accessor(gltf, prim.attributes[attr], cache=True)
+                attribute_data[idx] = np.concatenate((attribute_data[idx], attr_data[unique_indices]))
             else:
                 attr_data = np.zeros(
-                    (len(indices), DataType.num_elements(gltf.data.accessors[prim.attributes[attr]].type)),
-                     dtype=ComponentType.to_numpy_dtype(gltf.data.accessors[prim.attributes[attr]].component_type)
+                    (len(unique_indices), DataType.num_elements(attribute_type[attr])),
+                     dtype=ComponentType.to_numpy_dtype(attribute_component_type[attr])
                 )
-            attribute_data[idx] = np.concatenate((attribute_data[idx], attr_data[unique_indices]))
+                attribute_data[idx] = np.concatenate((attribute_data[idx], attr_data))

     # Accessors are cached in case they are shared between primitives; clear
     # the cache now that all prims are done.
@@ -456,14 +465,14 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
     for idx, attr in enumerate(attributes):

         blender_attribute_data_type = get_attribute_type(
-            gltf.data.accessors[prim.attributes[attr]].component_type,
-            gltf.data.accessors[prim.attributes[attr]].type
+            attribute_component_type[attr],
+            attribute_type[attr]
         )

         blender_attribute = mesh.attributes.new(attr, blender_attribute_data_type, 'POINT')
-        if DataType.num_elements(gltf.data.accessors[prim.attributes[attr]].type) == 1:
+        if DataType.num_elements(attribute_type[attr]) == 1:
             blender_attribute.data.foreach_set('value', attribute_data[idx].flatten())
-        elif DataType.num_elements(gltf.data.accessors[prim.attributes[attr]].type) > 1:
+        elif DataType.num_elements(attribute_type[attr]) > 1:
             if blender_attribute_data_type in ["BYTE_COLOR", "FLOAT_COLOR"]:
                 blender_attribute.data.foreach_set('color', attribute_data[idx].flatten())
             else:
julienduroure commented 1 year ago

@pjoe Hello, Thanks for the reporting and the diff. Yes, I need it again main. Can you open a PR on your own?

pjoe commented 1 year ago

Will, do ... just having some issue getting gltF-Blender-IO working against the 4.0 alpha I downloaded, is there any specific blender 4.0 I need or just latest ... should main also work against 3.6? Was having issues until I switched to the 3.6 branch :S

julienduroure commented 1 year ago

glTF I/O (this repo) will not work on Blender 3.6, as there are some API change between 3.6 and 4.0 Last changes was yesterday, so you need a real fresh 4.0 alpha version of Blender

pjoe commented 1 year ago

Yeah looks like latest daily windows build available from builder.blender.org is from sep-11, probably the cause of my issues

julienduroure commented 1 year ago

No change on this file since 3.6 => I am able to apply your patch. Don't worry, I will create the PR

xPaw commented 1 year ago

Can this be backported to 3.6 too since it's LTS?

julienduroure commented 1 year ago

Requested to be backported here: https://projects.blender.org/blender/blender/issues/109399#issuecomment-1029998 (not done yet)