Browse Source

add documentation + some refactoring

Bert Temme 1 year ago
parent
commit
fe0253bde5

+ 171 - 0
src/SharpGLTF.Ext.3DTiles/README.md

@@ -0,0 +1,171 @@
+# SharpGLTF.Ext.3DTiles
+
+This project contains the implementation of 3D Tiles support for SharpGLTF.
+
+The following extensions are supported:
+
+
+- EXT_Mesh_Features
+
+Specs: https://github.com/CesiumGS/glTF/tree/proposal-EXT_mesh_features/extensions/2.0/Vendor/EXT_mesh_features
+
+Samples: https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_mesh_features
+
+- EXT_Instance_Features
+
+Specs: https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_instance_features
+
+Samples: https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/GpuInstancesMetadata
+
+- Ext_Structural_Metadata
+
+Specs: https://github.com/CesiumGS/glTF/tree/proposal-EXT_structural_metadata/extensions/2.0/Vendor/EXT_structural_metadata
+
+Samples: https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata
+
+Not supported: 
+
+- External schema 
+
+- min, max, scale and offset properties for StructuralMetadataClassProperty and PropertyAttributeProperty
+
+## Unit testing 
+
+## Reading 3D Tiles glTF files
+
+The unit test project contains a set of glTF files that are used to test the implementation of the extensions. The glTF files 
+are obtained from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions.
+
+## Writing 3D Tiles glTF files
+
+See the unit test project for examples of how to write glTF files with the extensions.
+
+The unit tests writes glTF files like the samples from https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF
+
+
+## Sample code 
+
+### Reading a 3D Tiles glTF file with metadata
+
+```csharp
+    var model = ModelRoot.Load("sample.gltf");
+    var structuralMetadataExtension = model.GetExtension<EXTStructuralMetadataRoot>();
+    var meshFeaturesExtension = model.LogicalMeshes[0].Primitives[0].GetExtension<MeshExtMeshFeatures>();
+```
+
+## Writing a 3D Tiles glTF file with metadata
+
+In the following sample a glTF with 1 triangle is created. The triangle contains metadata with
+a name column. The name column is set to "this is featureId0". The triangle is assigned featureId 0.
+
+```csharp
+    int featureId = 0;
+    var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+    var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+    var prim = mesh.UsePrimitive(material);
+
+    var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
+    var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
+    var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
+
+    prim.AddTriangle(vt0, vt1, vt2);
+    var scene = new SceneBuilder();
+    scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+    var model = scene.ToGltf2();
+
+    var rootMetadata = model.UseStructuralMetadata();
+    var schema = rootMetadata.UseEmbeddedSchema("schema_001");
+
+    var schemaClass = schema.UseClassMetadata("triangles");
+
+    var nameProperty = schemaClass
+        .UseProperty("name")
+        .WithStringType();
+
+    var propertyTable = schemaClass
+        .AddPropertyTable(1);
+
+    propertyTable
+        .UseProperty(nameProperty)
+        .SetValues("this is featureId0");
+
+    foreach (var primitive in model.LogicalMeshes[0].Primitives)
+    {
+        var featureIdAttribute = new FeatureIDBuilder(1, 0, propertyTable);
+        primitive.AddMeshFeatureIds(featureIdAttribute);
+    }
+
+    model.SaveGLTF(@"sample.gltf");
+```
+
+3D Tiles specific parts in the resulting glTF:
+
+```
+ "extensions": {
+    "EXT_structural_metadata": {
+      "propertyTables": [
+        {
+          "class": "triangles",
+          "count": 1,
+          "properties": {
+            "name": {
+              "stringOffsets": 3,
+              "values": 2
+            }
+          }
+        }
+      ],
+      "schema": {
+        "classes": {
+          "triangles": {
+            "properties": {
+              "name": {
+                "type": "STRING"
+              }
+            }
+          }
+        },
+        "id": "schema_001"
+      }
+    }
+  },
+  "extensionsUsed": [
+    "EXT_structural_metadata",
+    "EXT_mesh_features"
+  ],
+  "meshes": [
+    {
+      "name": "mesh",
+      "primitives": [
+        {
+          "extensions": {
+            "EXT_mesh_features": {
+              "featureIds": [
+                {
+                  "attribute": 0,
+                  "featureCount": 1,
+                  "propertyTable": 0
+                }
+              ]
+            }
+          },
+          "attributes": {
+            "POSITION": 0,
+            "NORMAL": 1,
+            "_FEATURE_ID_0": 2
+          },
+          "indices": 3,
+          "material": 0
+        }
+      ]
+    }
+  ],
+
+  ```
+
+  Sample loaded in Cesium:
+
+
+  ![alt text](cesium_Sample.png)
+

+ 67 - 73
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -6,7 +6,6 @@ using SharpGLTF.Validation;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
-    using System.Text.Json.Nodes;
     using Tiles3D;
     using Tiles3D;
 
 
     partial class Tiles3DExtensions
     partial class Tiles3DExtensions
@@ -108,28 +107,28 @@ namespace SharpGLTF.Schema2
             {
             {
                 var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
                 var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
                 Guard.NotNull(rootMetadata, nameof(rootMetadata), "EXT_Structural_Metadata extension missing in root");
                 Guard.NotNull(rootMetadata, nameof(rootMetadata), "EXT_Structural_Metadata extension missing in root");
+
+                ValidatePropertyTexturesReferences(validate, rootMetadata);
+
+                ValidatePropertyAttributesReferences(validate, rootMetadata);
+
+                base.OnValidateReferences(validate);
+            }
+
+            protected override void OnValidateContent(ValidationContext validate)
+            {
+                var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
                 var propertyTextures = rootMetadata.PropertyTextures;
                 var propertyTextures = rootMetadata.PropertyTextures;
 
 
-                // Scan textures
-                foreach (var propertyTexture in _propertyTextures)
-                {
-                    validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
+                ValidatePropertyTexturesContent(rootMetadata, propertyTextures);
 
 
-                    var schemaTexture = propertyTextures[propertyTexture];
-                    var className = schemaTexture.ClassName;
-                    foreach (var property in schemaTexture.Properties)
-                    {
-                        var textureCoordinate = property.Value.TextureCoordinate;
-                        // Guard the meshprimitive has the texture coordinate attribute
-                        var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
-                        Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), nameof(textureCoordinate), $"The primitive should have texture coordinate attribute {textureCoordinate}.");
+                ValidatePropertyAttributesContent(rootMetadata);
 
 
-                        var texture = property.Value.Texture;
-                        Guard.NotNull(texture, nameof(texture), $"The primitive should have texture {texture}.");
-                    }
-                }
+                base.OnValidateContent(validate);
+            }
 
 
-                // scan attributes
+            private void ValidatePropertyAttributesReferences(ValidationContext validate, EXTStructuralMetadataRoot rootMetadata)
+            {
                 foreach (var propertyAttribute in _propertyAttributes)
                 foreach (var propertyAttribute in _propertyAttributes)
                 {
                 {
                     var propertyAttributes = rootMetadata.PropertyAttributes;
                     var propertyAttributes = rootMetadata.PropertyAttributes;
@@ -164,60 +163,34 @@ namespace SharpGLTF.Schema2
                         }
                         }
                     }
                     }
                 }
                 }
-
-                base.OnValidateReferences(validate);
             }
             }
 
 
-            protected override void OnValidateContent(ValidationContext validate)
+            private void ValidatePropertyTexturesReferences(ValidationContext validate, EXTStructuralMetadataRoot rootMetadata)
             {
             {
-                var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
                 var propertyTextures = rootMetadata.PropertyTextures;
                 var propertyTextures = rootMetadata.PropertyTextures;
 
 
                 // Scan textures
                 // Scan textures
                 foreach (var propertyTexture in _propertyTextures)
                 foreach (var propertyTexture in _propertyTextures)
                 {
                 {
+                    validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
+
                     var schemaTexture = propertyTextures[propertyTexture];
                     var schemaTexture = propertyTextures[propertyTexture];
                     var className = schemaTexture.ClassName;
                     var className = schemaTexture.ClassName;
                     foreach (var property in schemaTexture.Properties)
                     foreach (var property in schemaTexture.Properties)
                     {
                     {
                         var textureCoordinate = property.Value.TextureCoordinate;
                         var textureCoordinate = property.Value.TextureCoordinate;
+                        // Guard the meshprimitive has the texture coordinate attribute
                         var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
                         var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
-                        var vertex = meshPrimitive.GetVertexAccessor(expectedVertexAttribute);
+                        Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), nameof(textureCoordinate), $"The primitive should have texture coordinate attribute {textureCoordinate}.");
 
 
                         var texture = property.Value.Texture;
                         var texture = property.Value.Texture;
-
-                        var schemaProperty = rootMetadata.Schema.Classes[className].Properties[property.Key];
-
-                        Guard.IsTrue(schemaProperty.Type != ElementType.STRING, nameof(schemaProperty.Type),
-                            $"The property '{property.Key}' has the type 'STRING', which is not supported for property textures");
-
-
-                        if (schemaProperty.Array)
-                        {
-                            Guard.IsTrue(schemaProperty.Count != null, nameof(schemaProperty.Array),
-                                 $"The property '{property.Key}'  is a variable-length array, which is not supported for property textures");
-                        }
-
-                        // todo: check used values in texture against min, max (using scale and offset)
-                        // var min = schemaProperty.Min;
-                        // var max = schemaProperty.Max;
-                        // var scale = schemaProperty.Scale;
-                        // var offset = schemaProperty.Offset;
-
-                        var channels = property.Value.Channels;
-                        var elementCount = ComponentCount.ElementCountForType(schemaProperty.Type);
-                        if (schemaProperty.ComponentType != null)
-                        {
-                            var componentByteSize = ComponentCount.ByteSizeForComponentType(schemaProperty.ComponentType);
-                            var elementByteSize = elementCount * componentByteSize;
-                            var totalByteSize = channels.Count * elementByteSize;
-                            Guard.IsTrue(totalByteSize == channels.Count, nameof(totalByteSize),
-                                $"The property '{property.Key}' has the component type {schemaProperty.ComponentType}, with a size of {componentByteSize} bytes, and the type {schemaProperty.Type} with {channels.Count} components, resulting in {totalByteSize} bytes per element, but the number of channels in the property texture property was {channels.Count}");
-                        }
+                        Guard.NotNull(texture, nameof(texture), $"The primitive should have texture {texture}.");
                     }
                     }
                 }
                 }
+            }
 
 
-                // scan attributes
+            private void ValidatePropertyAttributesContent(EXTStructuralMetadataRoot rootMetadata)
+            {
                 foreach (var propertyAttribute in _propertyAttributes)
                 foreach (var propertyAttribute in _propertyAttributes)
                 {
                 {
                     var propertyAttributes = rootMetadata.PropertyAttributes;
                     var propertyAttributes = rootMetadata.PropertyAttributes;
@@ -229,13 +202,10 @@ namespace SharpGLTF.Schema2
 
 
                     foreach (var property in schemaAttribute.Properties)
                     foreach (var property in schemaAttribute.Properties)
                     {
                     {
-                        var expectedVertexAttribute = property.Value.Attribute;
-
                         var key = property.Key;
                         var key = property.Key;
 
 
                         var acc = property.Value.Attribute;
                         var acc = property.Value.Attribute;
                         var vertexAccessor = meshPrimitive.GetVertexAccessor(acc);
                         var vertexAccessor = meshPrimitive.GetVertexAccessor(acc);
-                        var propertyValues = vertexAccessor.AsScalarArray();
 
 
                         // Todo: check min, max, scale, offset of propertyAttributeProperty
                         // Todo: check min, max, scale, offset of propertyAttributeProperty
 
 
@@ -260,8 +230,7 @@ namespace SharpGLTF.Schema2
                         else if (propertyDefinition.Type == ElementType.ENUM)
                         else if (propertyDefinition.Type == ElementType.ENUM)
                         {
                         {
                             var enumType = propertyDefinition.EnumType;
                             var enumType = propertyDefinition.EnumType;
-                            // Get the enum from the schema
-                            var enumDefinition = schema.Enums[enumType];
+                            schema.Enums.TryGetValue(enumType, out var enumDefinition);
                             var valueType = enumDefinition.ValueType;
                             var valueType = enumDefinition.ValueType;
                             var allowedIntegerTypes = new List<IntegerType?>()
                             var allowedIntegerTypes = new List<IntegerType?>()
                             {
                             {
@@ -272,28 +241,53 @@ namespace SharpGLTF.Schema2
                         }
                         }
                     }
                     }
                 }
                 }
-
-                base.OnValidateContent(validate);
             }
             }
 
 
-            private static float ToFLoat(JsonNode node)
+            private void ValidatePropertyTexturesContent(EXTStructuralMetadataRoot rootMetadata, IReadOnlyList<PropertyTexture> propertyTextures)
             {
             {
-                return Convert.ToSingle(node.ToJsonString());
-            }
+                foreach (var propertyTexture in _propertyTextures)
+                {
+                    var schemaTexture = propertyTextures[propertyTexture];
 
 
-            private static bool AreLargerThan(IList<float> items, float min)
-            {
-                return items.All(item => item > min);
-            }
+                    var className = schemaTexture.ClassName;
+                    foreach (var property in schemaTexture.Properties)
+                    {
+                        // use for validation of texture
+                        // var textureCoordinate = property.Value.TextureCoordinate;
+                        // var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
 
 
-            private static bool AreSmallerThan(IList<float> items, float max)
-            {
-                return items.All(item => item < max);
-            }
+                        var schemaProperty = rootMetadata.Schema.Classes[className].Properties[property.Key];
 
 
-            #endregion
-        }
+                        Guard.IsTrue(schemaProperty.Type != ElementType.STRING, nameof(schemaProperty.Type),
+                            $"The property '{property.Key}' has the type 'STRING', which is not supported for property textures");
 
 
+                        if (schemaProperty.Array)
+                        {
+                            Guard.IsTrue(schemaProperty.Count != null, nameof(schemaProperty.Array),
+                                 $"The property '{property.Key}'  is a variable-length array, which is not supported for property textures");
+                        }
 
 
+                        // todo: check used values in texture against min, max (using scale and offset)
+                        // var min = schemaProperty.Min;
+                        // var max = schemaProperty.Max;
+                        // var scale = schemaProperty.Scale;
+                        // var offset = schemaProperty.Offset;
+
+                        var channels = property.Value.Channels;
+                        var elementCount = ComponentCount.ElementCountForType(schemaProperty.Type);
+                        if (schemaProperty.ComponentType != null)
+                        {
+                            var componentByteSize = ComponentCount.ByteSizeForComponentType(schemaProperty.ComponentType);
+                            var elementByteSize = elementCount * componentByteSize;
+                            var totalByteSize = channels.Count * elementByteSize;
+                            Guard.IsTrue(totalByteSize == channels.Count, nameof(totalByteSize),
+                                $"The property '{property.Key}' has the component type {schemaProperty.ComponentType}, with a size of {componentByteSize} bytes, and the type {schemaProperty.Type} with {channels.Count} components, resulting in {totalByteSize} bytes per element, but the number of channels in the property texture property was {channels.Count}");
+                        }
+                    }
+
+                }
+            }
+            #endregion
+        }
     }
     }
 }
 }

+ 15 - 12
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -131,16 +131,16 @@ namespace SharpGLTF.Schema2
                 return prop;
                 return prop;
             }
             }
 
 
-            public PropertyTable AddPropertyTable(StructuralMetadataClass schemaClass, int? featureCount = null, string name = null)
+            public PropertyTable AddPropertyTable(StructuralMetadataClass schemaClass, int featureCount, string name = null)
             {
             {
                 var table = AddPropertyTable();
                 var table = AddPropertyTable();
                 table.ClassInstance = schemaClass;
                 table.ClassInstance = schemaClass;
-                if (featureCount != null) table.Count = featureCount.Value;
+                table.Count = featureCount;
                 table.Name = name;
                 table.Name = name;
                 return table;
                 return table;
             }
             }
 
 
-            public PropertyTable AddPropertyTable()
+            private PropertyTable AddPropertyTable()
             {
             {
                 var prop = new PropertyTable();
                 var prop = new PropertyTable();
                 _propertyTables.Add(prop);
                 _propertyTables.Add(prop);
@@ -719,6 +719,8 @@ namespace SharpGLTF.Schema2
 
 
             public void SetArrayValues<T>(List<List<T>> values)
             public void SetArrayValues<T>(List<List<T>> values)
             {
             {
+                Guard.IsTrue(values.Count == LogicalParent.Count, nameof(values), $"Values must have length {LogicalParent.Count}");
+
                 var className = LogicalParent.ClassName;
                 var className = LogicalParent.ClassName;
                 var metadataProperty = GetProperty<T>(className, LogicalKey);
                 var metadataProperty = GetProperty<T>(className, LogicalKey);
 
 
@@ -754,6 +756,7 @@ namespace SharpGLTF.Schema2
 
 
             public void SetValues<T>(params T[] values)
             public void SetValues<T>(params T[] values)
             {
             {
+                Guard.IsTrue(values.Length == LogicalParent.Count, nameof(values), $"Values must have length {LogicalParent.Count}");
                 var className = LogicalParent.ClassName;
                 var className = LogicalParent.ClassName;
                 GetProperty<T>(className, LogicalKey);
                 GetProperty<T>(className, LogicalKey);
 
 
@@ -1196,7 +1199,7 @@ namespace SharpGLTF.Schema2
                 return LogicalParent.LogicalParent.AddPropertyAttribute(this);
                 return LogicalParent.LogicalParent.AddPropertyAttribute(this);
             }
             }
 
 
-            public PropertyTable AddPropertyTable(int? featureCount = null, string name = null)
+            public PropertyTable AddPropertyTable(int featureCount, string name = null)
             {
             {
                 return LogicalParent.LogicalParent.AddPropertyTable(this, featureCount, name);
                 return LogicalParent.LogicalParent.AddPropertyTable(this, featureCount, name);
             }
             }
@@ -1236,7 +1239,7 @@ namespace SharpGLTF.Schema2
                 set => _description = value;
                 set => _description = value;
             }
             }
 
 
-            public ELEMENTTYPE Type
+            internal ELEMENTTYPE Type
             {
             {
                 get => _type;
                 get => _type;
                 set => _type = value;
                 set => _type = value;
@@ -1427,13 +1430,13 @@ namespace SharpGLTF.Schema2
             }
             }
 
 
 
 
-            public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null)
-            {
-                Type = etype;
-                ComponentType = ctype;
-                Array = false;
-                return this;
-            }
+            //public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null)
+            //{
+            //    Type = etype;
+            //    ComponentType = ctype;
+            //    Array = false;
+            //    return this;
+            //}
 
 
             public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, int? count = null)
             public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, int? count = null)
             {
             {

BIN
src/SharpGLTF.Ext.3DTiles/cesium_sample.png


+ 50 - 1
tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs

@@ -23,7 +23,6 @@ namespace SharpGLTF.Schema2.Tiles3D
         }
         }
 
 
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/structuralMetadata
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/structuralMetadata
-
         [Test(Description = "Reads glTF's with EXT_Structural_Metadata")]
         [Test(Description = "Reads glTF's with EXT_Structural_Metadata")]
         [TestCase("ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf", typeof(ModelException))]
         [TestCase("ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf", typeof(ModelException))]
         [TestCase("PropertyAttributesClassPropertyArray.gltf", typeof(ModelException))]
         [TestCase("PropertyAttributesClassPropertyArray.gltf", typeof(ModelException))]
@@ -83,6 +82,56 @@ namespace SharpGLTF.Schema2.Tiles3D
             }
             }
         }
         }
 
 
+
+        [Test(Description = "MinimalMetadataSample")]
+        public void MinimalMetadataSample()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            int featureId = 0;
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim.AddTriangle(vt0, vt1, vt2);
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("schema_001");
+
+            var schemaClass = schema.UseClassMetadata("triangles");
+
+            var nameProperty = schemaClass
+                .UseProperty("name")
+                .WithStringType();
+
+            var propertyTable = schemaClass.AddPropertyTable(1);
+
+            propertyTable
+                .UseProperty(nameProperty)
+                .SetValues("this is featureId0");
+
+            foreach (var primitive in model.LogicalMeshes[0].Primitives)
+            {
+                var featureIdAttribute = new FeatureIDBuilder(1, 0, propertyTable);
+                primitive.AddMeshFeatureIds(featureIdAttribute);
+            }
+
+            // create files
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.plotly");
+        }
+
+
         [Test(Description = "TestWith2PrimitivesAndMetadata")]
         [Test(Description = "TestWith2PrimitivesAndMetadata")]
         public void MultiplePrimitivesAndMetadata()
         public void MultiplePrimitivesAndMetadata()
         {
         {