Browse Source

progress on cesium API redesign++

vpenades 1 year ago
parent
commit
de297f0c61

+ 6 - 6
build/SharpGLTF.CodeGen/Ext.EXT_Structural_Metadata.cs

@@ -23,9 +23,9 @@ namespace SharpGLTF
             newEmitter.SetRuntimeName("Property Texture in EXT_structural_metadata", "PropertyTexture", Constants.CesiumNameSpace);
             newEmitter.SetRuntimeName("Property Texture Property in EXT_structural_metadata", "PropertyTextureProperty", Constants.CesiumNameSpace);
             newEmitter.SetRuntimeName("Property Attribute Property in EXT_structural_metadata", "PropertyAttributeProperty", Constants.CesiumNameSpace);
-            newEmitter.SetRuntimeName("Class Property in EXT_structural_metadata", "ClassProperty", Constants.CesiumNameSpace);
+            newEmitter.SetRuntimeName("Class Property in EXT_structural_metadata", "StructuralMetadataClassProperty", Constants.CesiumNameSpace);
             newEmitter.SetRuntimeName("Class in EXT_structural_metadata", "StructuralMetadataClass", Constants.CesiumNameSpace);
-            newEmitter.SetRuntimeName("Enum Value in EXT_structural_metadata", "EnumValue", Constants.CesiumNameSpace);
+            newEmitter.SetRuntimeName("Enum Value in EXT_structural_metadata", "StructuralMetadataEnumValue", Constants.CesiumNameSpace);
             newEmitter.SetRuntimeName("Enum in EXT_structural_metadata", "StructuralMetadataEnum", Constants.CesiumNameSpace);
             newEmitter.SetRuntimeName("Property Attribute in EXT_structural_metadata", "PropertyAttribute", Constants.CesiumNameSpace);
 
@@ -38,15 +38,15 @@ namespace SharpGLTF
             newEmitter.SetFieldToChildrenList(ctx, "EXT_structural_metadata glTF extension", "propertyAttributes");
             newEmitter.SetFieldToChildrenList(ctx, "EXT_structural_metadata glTF extension", "propertyTextures");
 
+            newEmitter.SetFieldToChildrenDictionary(ctx, "Property Attribute in EXT_structural_metadata", "properties");
             newEmitter.SetFieldToChildrenDictionary(ctx, "Property Texture in EXT_structural_metadata", "properties");
-
-            newEmitter.SetFieldToChildrenDictionary(ctx, "Class in EXT_structural_metadata", "properties");
-
             newEmitter.SetFieldToChildrenDictionary(ctx, "Property Table in EXT_structural_metadata", "properties");
 
-            newEmitter.SetFieldToChildrenDictionary(ctx, "Property Attribute in EXT_structural_metadata", "properties");
 
+            newEmitter.SetFieldToChildrenDictionary(ctx, "Class in EXT_structural_metadata", "properties");
             newEmitter.SetFieldToChildrenDictionary(ctx, "Schema in EXT_structural_metadata", "classes");
+
+            newEmitter.SetFieldToChildrenList(ctx, "Enum in EXT_structural_metadata", "values");
             newEmitter.SetFieldToChildrenDictionary(ctx, "Schema in EXT_structural_metadata", "enums");
         }
 

+ 1 - 1
src/SharpGLTF.Ext.3DTiles/Memory/BinaryTable.cs

@@ -151,7 +151,7 @@ namespace SharpGLTF.Memory
             return result;
         }
 
-        public static List<int> GetStringOffsets(List<string> values)
+        public static List<int> GetStringOffsets(IReadOnlyList<string> values)
         {
             var offsets = new List<int>() { 0 };
             foreach (var value in values)

+ 65 - 7
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.FeatureID.cs

@@ -7,6 +7,12 @@ namespace SharpGLTF.Schema2.Tiles3D
 {
     using Collections;
 
+    /// <summary>
+    /// Mesh Feature Ids
+    /// </summary>
+    /// <remarks>
+    /// Implemented by <see cref="MeshExtInstanceFeatureID"/> and <see cref="MeshExtMeshFeatureID"/>
+    /// </remarks>
     public interface IMeshFeatureIDInfo
     {
         /// <summary>
@@ -34,7 +40,54 @@ namespace SharpGLTF.Schema2.Tiles3D
         /// <summary>
         /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
         /// </summary>
-        public int? PropertyTable { get; set; }
+        public int? PropertyTableIndex { get; set; }        
+    }
+
+    public sealed class FeatureIDBuilder : IMeshFeatureIDInfo , IEquatable<IMeshFeatureIDInfo>
+    {
+        public FeatureIDBuilder(int featureCount, string label = null)
+        {
+            FeatureCount = featureCount;
+            Label = label;
+        }
+
+        public FeatureIDBuilder(int featureCount, int attribute, string label = null)
+        {
+            FeatureCount = featureCount;
+            Attribute = attribute;            
+
+            Label = label;
+        }
+
+        public FeatureIDBuilder(PropertyTable table, int? attribute = null, string label = null)
+        {
+            FeatureCount = table.Count;
+            Attribute = attribute;
+            _root = table.LogicalParent;
+            PropertyTableIndex = table.LogicalIndex;
+
+            Label = label;
+        }
+
+        private readonly EXTStructuralMetadataRoot _root;
+
+        public int FeatureCount { get; set; }
+        public int? NullFeatureId { get; set; }
+        public int? Attribute { get; set; }
+        public string Label { get; set; }
+        public int? PropertyTableIndex { get; set; }
+
+        public bool Equals(IMeshFeatureIDInfo other)
+        {
+            if (other == null) return false;
+            if (this.FeatureCount != other.FeatureCount) return false;
+            if (this.NullFeatureId != other.NullFeatureId) return false;
+            if (this.Attribute != other.Attribute) return false;
+            if (this.Label != other.Label) return false;
+            if (this.PropertyTableIndex != other.PropertyTableIndex) return false;
+
+            return true;
+        }
     }
 
     /// <remarks>
@@ -44,14 +97,15 @@ namespace SharpGLTF.Schema2.Tiles3D
     {
         #region lifecycle
 
+        /*
         public MeshExtInstanceFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null)
         {
             FeatureCount = featureCount;
             Attribute = attribute;
             Label = label;
-            PropertyTable = propertyTable;
+            PropertyTableIndex = propertyTable;
             NullFeatureId = nullFeatureId;
-        }
+        }*/
 
 
         internal MeshExtInstanceFeatureID() { }
@@ -78,7 +132,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 
         #endregion
 
-        #region properties        
+        #region IMeshFeatureIDInfo properties        
         public int FeatureCount
         {
             get => _featureCount;
@@ -111,7 +165,7 @@ namespace SharpGLTF.Schema2.Tiles3D
                 _label = value;
             }
         }        
-        public int? PropertyTable
+        public int? PropertyTableIndex
         {
             get => _propertyTable;
             set
@@ -158,7 +212,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 
         #endregion
 
-        #region properties        
+        #region IMeshFeatureIDInfo properties        
         public int FeatureCount
         {
             get => _featureCount;
@@ -191,7 +245,7 @@ namespace SharpGLTF.Schema2.Tiles3D
                 _label = value;
             }
         }                
-        public int? PropertyTable
+        public int? PropertyTableIndex
         {
             get => _propertyTable;
             set
@@ -205,6 +259,9 @@ namespace SharpGLTF.Schema2.Tiles3D
 
         #region API
 
+        // question: is _texture required to always exist? if that would be the case, then it should be created
+        // in the constructor an exposed as a read only property.
+
         /// <summary>
         /// Gets a texture containing feature IDs.
         /// </summary>
@@ -276,6 +333,7 @@ namespace SharpGLTF.Schema2.Tiles3D
         public void SetChannels(IReadOnlyList<int> channels)
         {
             Guard.NotNullOrEmpty(channels, nameof(channels));
+            Guard.MustBeGreaterThanOrEqualTo(channels.Count, _channelsMinItems, nameof(channels));
 
             _channels.Clear();
             _channels.AddRange(channels);

+ 6 - 6
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.Features.cs

@@ -14,11 +14,11 @@ namespace SharpGLTF.Schema2
     {
         internal static void ValidateFeatureIdReferences(this IMeshFeatureIDInfo featureId, ModelRoot root)
         {
-            if (featureId.PropertyTable.HasValue)
+            if (featureId.PropertyTableIndex.HasValue)
             {
                 var metadataExtension = root.GetExtension<EXTStructuralMetadataRoot>();
                 Guard.NotNull(metadataExtension, nameof(metadataExtension), "EXT_Structural_Metadata extension is not found.");
-                Guard.NotNull(metadataExtension.PropertyTables[featureId.PropertyTable.Value], nameof(featureId.PropertyTable), $"Property table index {featureId.PropertyTable.Value} does not exist");
+                Guard.NotNull(metadataExtension.PropertyTables[featureId.PropertyTableIndex.Value], nameof(featureId.PropertyTableIndex), $"Property table index {featureId.PropertyTableIndex.Value} does not exist");
             }
         }
 
@@ -40,9 +40,9 @@ namespace SharpGLTF.Schema2
             {
                 Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
             }
-            if (featureId.PropertyTable.HasValue)
+            if (featureId.PropertyTableIndex.HasValue)
             {
-                Guard.MustBeGreaterThanOrEqualTo((int)featureId.PropertyTable, 0, nameof(featureId.PropertyTable));
+                Guard.MustBeGreaterThanOrEqualTo((int)featureId.PropertyTableIndex, 0, nameof(featureId.PropertyTableIndex));
             }
         }
 
@@ -134,7 +134,7 @@ namespace SharpGLTF.Schema2
                 instance.NullFeatureId = properties.NullFeatureId;
                 instance.Label = properties.Label;
                 instance.Attribute = properties.Attribute;
-                instance.PropertyTable = properties.PropertyTable;
+                instance.PropertyTableIndex = properties.PropertyTableIndex;
 
                 return instance;
             }
@@ -234,7 +234,7 @@ namespace SharpGLTF.Schema2
                 instance.NullFeatureId = properties.NullFeatureId;
                 instance.Label = properties.Label;
                 instance.Attribute = properties.Attribute;
-                instance.PropertyTable = properties.PropertyTable;
+                instance.PropertyTableIndex = properties.PropertyTableIndex;
 
                 if (texture != null)
                 {

+ 82 - 24
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Linq;
+using System.Collections.Generic;
 
 using SharpGLTF.Validation;
 
@@ -8,20 +10,22 @@ namespace SharpGLTF.Schema2
 
     partial class Tiles3DExtensions
     {
-        public static void SetPropertyTextures(this MeshPrimitive primitive, List<int> propertyTextures)
-        {
-            if (propertyTextures == null) { primitive.RemoveExtensions<ExtStructuralMetadataMeshPrimitive>(); return; }
+        // TODO: PropertyTexture is taken from a Schema, but it is possible the schema is an external file,
+        // in which case we could not have a PopertyTexture, just a blind ID
+
+        // Solution1: enforce loading the schema as part of the memory document
+        // Solution2: allow the API to be OneOf<int,PropertyTexture>
 
+        public static void AddPropertyTexture(this MeshPrimitive primitive, PropertyTexture texture)
+        {
             var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
-            ext.PropertyTextures = propertyTextures;
+            ext.AddTexture(texture);
         }
 
-        public static void SetPropertyAttributes(this MeshPrimitive primitive, List<int> propertyAttributes)
+        public static void AddPropertyAttribute(this MeshPrimitive primitive, PropertyAttribute attribute)
         {
-            if (propertyAttributes == null) { primitive.RemoveExtensions<ExtStructuralMetadataMeshPrimitive>(); return; }
-
             var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
-            ext.PropertyAttributes = propertyAttributes;
+            ext.AddAttribute(attribute);
         }
     }
 
@@ -29,6 +33,7 @@ namespace SharpGLTF.Schema2
     {
         partial class ExtStructuralMetadataMeshPrimitive
         {
+            #region lifecycle
             internal ExtStructuralMetadataMeshPrimitive(MeshPrimitive meshPrimitive)
             {
                 this.meshPrimitive = meshPrimitive;
@@ -36,41 +41,94 @@ namespace SharpGLTF.Schema2
                 _propertyAttributes = new List<int>();
             }
 
+            #endregion
+
+            #region data
+
             private MeshPrimitive meshPrimitive;
 
-            public List<int> PropertyTextures
+            #endregion
+
+            #region properties
+
+            private ModelRoot _GetModelRoot() => meshPrimitive.LogicalParent.LogicalParent;
+
+            public int PropertyCount => _propertyTextures.Count;
+
+            public int AttributeCount => _propertyAttributes.Count;
+
+            #endregion
+
+            #region API
+
+            public PropertyTexture GetTexture(int index)
             {
-                get => _propertyTextures;
-                set => _propertyTextures = value;
+                Guard.MustBeBetweenOrEqualTo(index, 0, _propertyTextures.Count - 1, nameof(index));
+
+                var root = _GetModelRoot();
+                var metadata = root.GetExtension<EXTStructuralMetadataRoot>();
+                Guard.NotNull(metadata, nameof(index));
+
+                return metadata.PropertyTextures[index];
             }
 
-            public List<int> PropertyAttributes
+            public void AddTexture(PropertyTexture texture)
             {
-                get => _propertyAttributes;
-                set => _propertyAttributes = value;
+                Guard.NotNull(texture, nameof(texture));
+                
+                var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
+                var properties = metadata.PropertyTextures;
+                Guard.IsTrue(properties.Contains(texture), nameof(texture));
+                
+                _propertyTextures.Add(texture.LogicalIndex);
             }
 
+            public PropertyAttribute GetAttribute(int index)
+            {
+                Guard.MustBeBetweenOrEqualTo(index, 0, _propertyTextures.Count - 1, nameof(index));
+
+                var root = _GetModelRoot();
+                var metadata = root.GetExtension<EXTStructuralMetadataRoot>();
+                Guard.NotNull(metadata, nameof(index));
+
+                return metadata.PropertyAttributes[index];
+            }
+
+            public void AddAttribute(PropertyAttribute attribute)
+            {
+                Guard.NotNull(attribute, nameof(attribute));
+                
+                var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
+                var properties = metadata.PropertyAttributes;
+                Guard.IsTrue(properties.Contains(attribute), nameof(attribute));
+                
+                _propertyAttributes.Add(attribute.LogicalIndex);
+            }
+
+            #endregion
+
+            #region validation
+
             protected override void OnValidateReferences(ValidationContext validate)
             {
-                foreach (var propertyTexture in PropertyTextures)
+                var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
+
+                foreach (var propertyTexture in _propertyTextures)
                 {
-                    var propertyTextures = meshPrimitive.LogicalParent.LogicalParent.GetExtension<EXTStructuralMetadataRoot>().PropertyTextures;
+                    var propertyTextures = rootMetadata.PropertyTextures;
                     validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
                 }
 
-                foreach (var propertyAttribute in PropertyAttributes)
+                foreach (var propertyAttribute in _propertyAttributes)
                 {
-                    var propertyAttributes = meshPrimitive.LogicalParent.LogicalParent.GetExtension<EXTStructuralMetadataRoot>().PropertyAttributes;
+                    var propertyAttributes = rootMetadata.PropertyAttributes;
                     validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, propertyAttributes);
                 }
 
                 base.OnValidateReferences(validate);
-            }
+            }            
 
-            protected override void OnValidateContent(ValidationContext result)
-            {
-                base.OnValidateContent(result);
-            }
+            #endregion
         }
     }
 }

+ 582 - 257
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -2,152 +2,37 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Reflection;
 
-using OneOf;
+using DATATYPE = SharpGLTF.Schema2.Tiles3D.DataType;
+using ELEMENTTYPE = SharpGLTF.Schema2.Tiles3D.ElementType;
 
 namespace SharpGLTF.Schema2
 {
     using Collections;
     using Memory;
     using Validation;
-    using Tiles3D;
-
-    using METADATAORURI = OneOf<Tiles3D.StructuralMetadataSchema, Uri>;
+    using Tiles3D;    
 
     partial class Tiles3DExtensions
     {
-        public static void SetPropertyAttribute(
-            this ModelRoot modelRoot,
-            PropertyAttribute propertyAttribute,
-            METADATAORURI schema)
-        {
-            SetPropertyAttributes(modelRoot, new List<PropertyAttribute>() { propertyAttribute }, schema);
-        }
-
-        public static void SetPropertyAttributes(
-this ModelRoot modelRoot,
-List<PropertyAttribute> propertyAttributes,
-METADATAORURI schema)
-        {
-            if (propertyAttributes == null || propertyAttributes.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
-
-            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            // ext.PropertyAttributes = propertyAttributes;
-            throw new NotImplementedException();
-            ext.AddSchema(schema);
-        }
-
-
-        public static void SetPropertyTexture(
-    this ModelRoot modelRoot,
-    PropertyTexture propertyTexture,
-    METADATAORURI schema)
-        {
-            SetPropertyTextures(modelRoot, new List<PropertyTexture>() { propertyTexture }, schema);
-        }
-
-
-        public static void SetPropertyTextures(
-    this ModelRoot modelRoot,
-    List<PropertyTexture> propertyTextures,
-    METADATAORURI schema)
-        {
-            if (propertyTextures == null || propertyTextures.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
-
-            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            // ext.PropertyTextures = propertyTextures;
-            throw new NotImplementedException();
-            ext.AddSchema(schema);
-        }
-
-        public static void SetPropertyTable(
-            this ModelRoot modelRoot,
-            PropertyTable propertyTable,
-            METADATAORURI schema)
-        {
-            SetPropertyTables(modelRoot, new List<PropertyTable>() { propertyTable }, schema);
-        }
-
-        public static void SetPropertyTables(
-            this ModelRoot modelRoot,
-            List<PropertyTable> propertyTables,
-            METADATAORURI schema)
-        {
-            if (propertyTables == null || propertyTables.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
-
-            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            // ext.PropertyTables = propertyTables;
-            throw new NotImplementedException();
-            ext.AddSchema(schema);
-        }
-
-        public static PropertyTableProperty GetArrayPropertyTableProperty<T>(this ModelRoot model, List<List<T>> values, bool CreateArrayOffsets = true)
+        public static EXTStructuralMetadataRoot UseStructuralMetadata(this ModelRoot modelRoot)
         {
-            var propertyTableProperty = new PropertyTableProperty();
-            int logicalIndex = GetBufferView(model, values);
-            propertyTableProperty.Values = logicalIndex;
-
-            if (CreateArrayOffsets)
-            {
-                var arrayOffsets = BinaryTable.GetArrayOffsets(values);
-                int logicalIndexOffsets = GetBufferView(model, arrayOffsets);
-                propertyTableProperty.ArrayOffsets = logicalIndexOffsets;
-
-                if (typeof(T) == typeof(string))
-                {
-                    var stringValues = values.ConvertAll(x => x.ConvertAll(y => (string)Convert.ChangeType(y, typeof(string), CultureInfo.InvariantCulture)));
-                    var stringOffsets = BinaryTable.GetStringOffsets(stringValues);
-                    int offsets = GetBufferView(model, stringOffsets);
-                    propertyTableProperty.StringOffsets = offsets;
-                }
-            }
-            return propertyTableProperty;
-        }
-
-        public static PropertyTableProperty GetPropertyTableProperty<T>(this ModelRoot model, List<T> values)
-        {
-            var propertyTableProperty = new PropertyTableProperty();
-            int logicalIndex = GetBufferView(model, values);
-            propertyTableProperty.Values = logicalIndex;
-
-            if (typeof(T) == typeof(string))
-            {
-                var stringvalues = values.ConvertAll(x => (string)Convert.ChangeType(x, typeof(string), CultureInfo.InvariantCulture));
-                var stringOffsets = BinaryTable.GetStringOffsets(stringvalues);
-                int offsets = GetBufferView(model, stringOffsets);
-                propertyTableProperty.StringOffsets = offsets;
-            }
-
-            return propertyTableProperty;
-        }
-
-        private static int GetBufferView<T>(this ModelRoot model, List<T> values)
-        {
-            var bytes = BinaryTable.GetBytes(values);
-            var bufferView = model.UseBufferView(bytes);
-            int logicalIndex = bufferView.LogicalIndex;
-            return logicalIndex;
-        }
-
-        private static int GetBufferView<T>(this ModelRoot model, List<List<T>> values)
-        {
-            List<byte> bytes = BinaryTable.GetBytesForArray(values);
-            var bufferView = model.UseBufferView(bytes.ToArray());
-            int logicalIndex = bufferView.LogicalIndex;
-            return logicalIndex;
-        }
+            return modelRoot.UseExtension<EXTStructuralMetadataRoot>();
+        }        
     }
 
     namespace Tiles3D
     {
+        /// <remarks>
+        /// Use <see cref="Tiles3DExtensions.UseStructuralMetadata(ModelRoot)"/> to create an instance of this class.
+        /// </remarks>        
         public partial class EXTStructuralMetadataRoot
         {
             #region lifecycle
 
             internal EXTStructuralMetadataRoot(ModelRoot modelRoot)
             {
-                this.modelRoot = modelRoot;
+                this.LogicalParent = modelRoot;
                 _propertyTables = new ChildrenList<PropertyTable, EXTStructuralMetadataRoot>(this);
                 _propertyAttributes = new ChildrenList<PropertyAttribute, EXTStructuralMetadataRoot>(this);
                 _propertyTextures = new ChildrenList<PropertyTexture, EXTStructuralMetadataRoot>(this);
@@ -169,7 +54,7 @@ METADATAORURI schema)
 
             #region data
 
-            private ModelRoot modelRoot;
+            public ModelRoot LogicalParent { get; }
 
             #endregion
 
@@ -184,7 +69,7 @@ METADATAORURI schema)
             internal StructuralMetadataSchema Schema
             {
                 get => _schema;
-                set { GetChildSetter(this).SetListProperty(ref _schema, value); }
+                set { GetChildSetter(this).SetProperty(ref _schema, value); }
             }
 
             internal IReadOnlyList<PropertyTable> PropertyTables => _propertyTables;
@@ -195,14 +80,74 @@ METADATAORURI schema)
 
             #region API
 
-            internal void AddSchema(METADATAORURI schema)
+            public bool TryGetEmbeddedSchema(out StructuralMetadataSchema schema)
             {
-                schema.Switch(
-                    StructuralMetadataSchema => _schema = StructuralMetadataSchema,
-                    Uri => this.SchemaUri = Uri.ToString()
-                    );
+                if (_schema != null) { schema = _schema; return true; }
+
+                schema = null;
+                return false;
             }
 
+            public StructuralMetadataSchema UseEmbeddedSchema(string id)
+            {
+                var schema = UseEmbeddedSchema();
+                schema.Id = id;
+                return schema;
+            }
+
+            public StructuralMetadataSchema UseEmbeddedSchema()
+            {
+                this.SchemaUri = null;
+
+                if (_schema == null) GetChildSetter(this).SetProperty(ref _schema, new StructuralMetadataSchema());
+
+                return _schema;
+            }            
+
+            public PropertyAttribute AddPropertyAttribute(StructuralMetadataClass schemaClass)
+            {
+                var prop = AddPropertyAttribute();
+                prop.ClassInstance = schemaClass;
+                return prop;
+            }
+
+            public PropertyAttribute AddPropertyAttribute()
+            {
+                var prop = new PropertyAttribute();
+                _propertyAttributes.Add(prop);
+                return prop;
+            }            
+
+            public PropertyTable AddPropertyTable(StructuralMetadataClass schemaClass, int? featureCount = null, string name = null)
+            {
+                var table = AddPropertyTable();
+                table.ClassInstance = schemaClass;
+                if (featureCount != null) table.Count = featureCount.Value;
+                table.Name = name;
+                return table;
+            }
+
+            public PropertyTable AddPropertyTable()
+            {
+                var prop = new PropertyTable();
+                _propertyTables.Add(prop);
+                return prop;
+            }
+
+            public PropertyTexture AddPropertyTexture(StructuralMetadataClass schemaClass)
+            {
+                var prop = AddPropertyTexture();
+                prop.ClassInstance = schemaClass;                
+                return prop;
+            }
+
+            public PropertyTexture AddPropertyTexture()
+            {
+                var prop = new PropertyTexture();
+                _propertyTextures.Add(prop);
+                return prop;
+            }            
+
             #endregion
 
             #region validation
@@ -214,31 +159,31 @@ METADATAORURI schema)
                     foreach (var propertyTextureProperty in propertyTexture.Properties)
                     {
                         var textureId = propertyTextureProperty.Value.LogicalTextureIndex;
-                        validate.IsNullOrIndex(nameof(propertyTexture), textureId, modelRoot.LogicalTextures);
+                        validate.IsNullOrIndex(nameof(propertyTexture), textureId, LogicalParent.LogicalTextures);
                     }
                 }
 
                 foreach (var propertyTable in PropertyTables)
                 {
-                    Guard.NotNull(Schema.Classes[propertyTable.Class], nameof(propertyTable.Class), $"Schema must have class {propertyTable.Class}");
+                    Guard.NotNull(Schema.Classes[propertyTable.ClassName], nameof(propertyTable.ClassName), $"Schema must have class {propertyTable.ClassName}");
 
                     foreach (var property in propertyTable.Properties)
                     {
-                        Guard.NotNull(Schema.Classes[propertyTable.Class].Properties[property.Key], nameof(property.Key), $"Schema must have property {property.Key}");
+                        Guard.NotNull(Schema.Classes[propertyTable.ClassName].Properties[property.Key], nameof(property.Key), $"Schema must have property {property.Key}");
 
                         var values = property.Value.Values;
-                        validate.IsNullOrIndex(nameof(propertyTable), values, modelRoot.LogicalBufferViews);
+                        validate.IsNullOrIndex(nameof(propertyTable), values, LogicalParent.LogicalBufferViews);
 
                         if (property.Value.ArrayOffsets.HasValue)
                         {
                             var arrayOffsets = property.Value.ArrayOffsets.Value;
-                            validate.IsNullOrIndex(nameof(propertyTable), arrayOffsets, modelRoot.LogicalBufferViews);
+                            validate.IsNullOrIndex(nameof(propertyTable), arrayOffsets, LogicalParent.LogicalBufferViews);
                         }
 
                         if (property.Value.StringOffsets.HasValue)
                         {
                             var stringOffsets = property.Value.StringOffsets.Value;
-                            validate.IsNullOrIndex(nameof(propertyTable), stringOffsets, modelRoot.LogicalBufferViews);
+                            validate.IsNullOrIndex(nameof(propertyTable), stringOffsets, LogicalParent.LogicalBufferViews);
                         }
                     }
                 }
@@ -249,7 +194,7 @@ METADATAORURI schema)
                     {
                         foreach (var property in @class.Value.Properties)
                         {
-                            if (property.Value.Type == ElementType.ENUM)
+                            if (property.Value.Type == ELEMENTTYPE.ENUM)
                             {
                                 Guard.IsTrue(Schema.Enums.ContainsKey(property.Value.EnumType), nameof(property.Value.EnumType), $"Enum {property.Value.EnumType} must be defined in schema");
                             }
@@ -298,7 +243,7 @@ METADATAORURI schema)
 
                 foreach (var propertyTable in PropertyTables)
                 {
-                    Guard.IsTrue(propertyTable.Class != null, nameof(propertyTable.Class), "Class must be defined");
+                    Guard.IsTrue(propertyTable.ClassName != null, nameof(propertyTable.ClassName), "Class must be defined");
                     Guard.IsTrue(propertyTable.Count > 0, nameof(propertyTable.Count), "Count must be greater than 0");
                     Guard.IsTrue(propertyTable.Properties.Count > 0, nameof(propertyTable.Properties), "Properties must be defined");
                 }
@@ -313,6 +258,11 @@ METADATAORURI schema)
             #endregion
         }
 
+        #region structural properties
+
+        /// <remarks>
+        /// Use <see cref="EXTStructuralMetadataRoot.AddPropertyTexture"/> to create an instance of this class.
+        /// </remarks> 
         public partial class PropertyTexture : IChildOfList<EXTStructuralMetadataRoot>
         {
             #region lifecycle
@@ -351,11 +301,56 @@ METADATAORURI schema)
                 set => _class = value;
             }
 
+            public StructuralMetadataClass ClassInstance
+            {
+                get
+                {
+                    if (string.IsNullOrEmpty(ClassName)) return null;
+                    var root = _GetModelRoot()?.GetExtension<EXTStructuralMetadataRoot>();
+                    if (root == null) return null;
+
+                    if (root.TryGetEmbeddedSchema(out var schema))
+                    {
+                        return schema.Classes[ClassName];
+                    }
+                    else return null;                    
+                }
+                set
+                {
+                    // Todo: check value is part of this DOM
+                    ClassName = value?.LogicalKey;
+                }
+            }
+
             public IReadOnlyDictionary<string, PropertyTextureProperty> Properties => _properties;
 
             #endregion
+
+            #region API
+
+            private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent;
+
+            public PropertyTextureProperty CreateProperty(string key, Texture texture, IReadOnlyList<int> channels = null)
+            {
+                var property = CreateProperty(key);
+                property.Texture = texture;
+                if (channels != null) property.Channels = channels;
+                return property;
+            }
+
+            public PropertyTextureProperty CreateProperty(string key)
+            {
+                var property = new PropertyTextureProperty();
+                _properties[key] = property;
+                return property;
+            }
+
+            #endregion
         }
 
+        /// <remarks>
+        /// Use <see cref="PropertyTexture.CreateProperty(string)"/> to create an instance of this class.
+        /// </remarks> 
         public partial class PropertyTextureProperty : IChildOfDictionary<PropertyTexture>
         {
             #region lifecycle
@@ -382,11 +377,36 @@ METADATAORURI schema)
 
             #region data
 
-            public List<int> Channels => _channels;
+            private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent.LogicalParent;
 
-            #endregion
+            public IReadOnlyList<int> Channels
+            {
+                get => _channels;
+                set
+                {
+                    _channels.Clear();
+                    _channels.AddRange(value);
+                }
+            }            
+
+            public Schema2.Texture Texture
+            {
+                get => _GetModelRoot().LogicalTextures[LogicalTextureIndex];
+                set
+                {
+                    Guard.NotNull(value, nameof(value));
+                    Guard.MustShareLogicalParent(_GetModelRoot(), nameof(MeshExtMeshFeatureIDTexture), value, nameof(value));
+
+                    LogicalTextureIndex = value.LogicalIndex;
+                }
+            }
+
+            #endregion            
         }
 
+        /// <remarks>
+        /// Use <see cref="EXTStructuralMetadataRoot.AddPropertyAttribute"/> to create an instance of this class.
+        /// </remarks> 
         public partial class PropertyAttribute : IChildOfList<EXTStructuralMetadataRoot>
         {
             #region lifecycle
@@ -424,18 +444,55 @@ METADATAORURI schema)
             #endregion
 
             #region properties
-            public string Class
+            public string ClassName
             {
                 get => _class;
                 set => _class = value;
             }
 
+            public StructuralMetadataClass ClassInstance
+            {
+                get
+                {
+                    if (string.IsNullOrEmpty(ClassName)) return null;
+                    var root = _GetModelRoot()?.GetExtension<EXTStructuralMetadataRoot>();
+                    if (root == null) return null;
+
+                    if (root.TryGetEmbeddedSchema(out var schema))
+                    {
+                        return schema.Classes[ClassName];
+                    }
+                    else return null;
+                }
+                set
+                {
+                    // Todo: check value is part of this DOM
+                    ClassName = value?.LogicalKey;
+                }
+            }
+
             public IReadOnlyDictionary<string, PropertyAttributeProperty> Properties => _properties;
 
             #endregion
 
+            #region API
+
+            private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent;
+
+            public PropertyAttributeProperty CreateProperty(string key)
+            {
+                var property = new PropertyAttributeProperty();
+                _properties[key] = property;
+                return property;
+            }
+
+            #endregion
+
         }
 
+        /// <remarks>
+        /// Use <see cref="PropertyAttribute.CreateProperty(string)"/> to create an instance of this class.
+        /// </remarks> 
         public partial class PropertyAttributeProperty : IChildOfDictionary<PropertyAttribute>
         {
             #region child properties
@@ -463,7 +520,227 @@ METADATAORURI schema)
             #endregion
         }
 
-        public partial class StructuralMetadataSchema : IChildOfList<EXTStructuralMetadataRoot>
+        /// <remarks>
+        /// Use <see cref="EXTStructuralMetadataRoot.AddPropertyTable"/> to create an instance of this class.
+        /// </remarks> 
+        public partial class PropertyTable : IChildOfList<EXTStructuralMetadataRoot>
+        {
+            #region lifecycle
+            internal PropertyTable()
+            {
+                _properties = new ChildrenDictionary<PropertyTableProperty, PropertyTable>(this);
+
+                _count = _countMinimum;
+            }           
+
+            protected override IEnumerable<ExtraProperties> GetLogicalChildren()
+            {
+                return base.GetLogicalChildren()
+                    .Concat(_properties.Values);
+            }
+
+            #endregion
+
+            #region child properties
+
+            public int LogicalIndex { get; private set; } = -1;
+            public EXTStructuralMetadataRoot LogicalParent { get; private set; }
+
+            void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
+            {
+                LogicalParent = parent;
+                LogicalIndex = index;
+            }
+
+            #endregion
+
+            #region properties
+
+            public IReadOnlyDictionary<string, PropertyTableProperty> Properties => _properties;
+
+            public string ClassName
+            {
+                get => _class;
+                set => _class = value;
+            }
+
+            public StructuralMetadataClass ClassInstance
+            {
+                get
+                {
+                    if (string.IsNullOrEmpty(ClassName)) return null;
+                    var root = _GetModelRoot()?.GetExtension<EXTStructuralMetadataRoot>();
+                    if (root == null) return null;
+
+                    if (root.TryGetEmbeddedSchema(out var schema))
+                    {
+                        return schema.Classes[ClassName];
+                    }
+                    else return null;
+                }
+                set
+                {
+                    // Todo: check value is part of this DOM
+                    ClassName = value?.LogicalKey;
+                }
+            }
+
+            public string Name
+            {
+                get => _name;
+                set => _name = value;
+            }            
+
+            public int Count
+            {
+                get => _count;
+                set
+                {
+                    Guard.MustBeGreaterThanOrEqualTo(value, _countMinimum, nameof(value));
+                    _count = value;
+                }
+            }
+
+            #endregion
+
+            #region API
+
+            private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent;
+
+            public PropertyTableProperty UseProperty(StructuralMetadataClassProperty key)
+            {
+                return UseProperty(key.LogicalKey);
+            }
+
+            public PropertyTableProperty UseProperty(string key)
+            {
+                if (_properties.TryGetValue(key, out var value)) return value;
+
+                value = new PropertyTableProperty();
+                _properties[key] = value;
+                return value;
+            }            
+
+            #endregion
+        }
+
+        /// <remarks>
+        /// Use <see cref="PropertyTable.UseProperty(string)"/> to create an instance of this class.
+        /// </remarks>  
+        public partial class PropertyTableProperty : IChildOfDictionary<PropertyTable>
+        {
+            #region child properties
+
+            public string LogicalKey { get; private set; }
+
+            public PropertyTable LogicalParent { get; private set; }
+
+            void IChildOfDictionary<PropertyTable>.SetLogicalParent(PropertyTable parent, string key)
+            {
+                LogicalParent = parent;
+                LogicalKey = key;
+            }
+
+            #endregion
+
+            #region properties
+
+            /// <summary>
+            /// this is an index to a BufferView
+            /// </summary>
+            public int Values
+            {
+                get => _values;
+                set => _values = value;
+            }
+
+            public int? ArrayOffsets
+            {
+                get => _arrayOffsets;
+                set => _arrayOffsets = value;
+            }
+
+            public int? StringOffsets
+            {
+                get => _stringOffsets;
+                set => _stringOffsets = value;
+            }
+
+            #endregion
+
+            #region API
+
+            private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent.LogicalParent;
+
+            public void SetValues2D<T>(List<List<T>> values, bool CreateArrayOffsets = true)
+            {
+                var root = _GetModelRoot();
+                
+                int logicalIndex = GetBufferView(root, values);
+                this.Values = logicalIndex;
+
+                if (CreateArrayOffsets)
+                {
+                    var arrayOffsets = BinaryTable.GetArrayOffsets(values);
+                    int logicalIndexOffsets = GetBufferView(root, arrayOffsets);
+                    this.ArrayOffsets = logicalIndexOffsets;
+
+                    if (typeof(T) == typeof(string))
+                    {
+                        var stringValues = values.ConvertAll(x => x.ConvertAll(y => (string)Convert.ChangeType(y, typeof(string), CultureInfo.InvariantCulture)));
+                        var stringOffsets = BinaryTable.GetStringOffsets(stringValues);
+                        int offsets = GetBufferView(root, stringOffsets);
+                        this.StringOffsets = offsets;
+                    }
+                }                
+            }
+
+            public void SetValues1D<T>(params T[] values)
+            {
+                var root = _GetModelRoot();
+                
+                int logicalIndex = GetBufferView(root, values);
+                this.Values = logicalIndex;
+
+                if (typeof(T) == typeof(string))
+                {
+                    var stringvalues = values
+                        .Select(x => (string)Convert.ChangeType(x, typeof(string), CultureInfo.InvariantCulture))
+                        .ToList();
+
+                    var stringOffsets = BinaryTable.GetStringOffsets(stringvalues);
+                    int offsets = GetBufferView(root, stringOffsets);
+                    this.StringOffsets = offsets;
+                }                
+            }
+
+            private static int GetBufferView<T>(ModelRoot model, IReadOnlyList<T> values)
+            {
+                var bytes = BinaryTable.GetBytes(values);
+                var bufferView = model.UseBufferView(bytes);
+                int logicalIndex = bufferView.LogicalIndex;
+                return logicalIndex;
+            }
+
+            private static int GetBufferView<T>(ModelRoot model, List<List<T>> values)
+            {
+                List<byte> bytes = BinaryTable.GetBytesForArray(values);
+                var bufferView = model.UseBufferView(bytes.ToArray());
+                int logicalIndex = bufferView.LogicalIndex;
+                return logicalIndex;
+            }
+
+            #endregion
+        }
+
+        #endregion
+
+        #region structural schema
+
+        /// <remarks>
+        /// Use <see cref="EXTStructuralMetadataRoot.UseEmbeddedSchema()"/> to create an instance of this class.
+        /// </remarks>
+        public partial class StructuralMetadataSchema : IChildOf<EXTStructuralMetadataRoot>
         {
             #region lifecycle
             public StructuralMetadataSchema()
@@ -481,16 +758,13 @@ METADATAORURI schema)
 
             #endregion
 
-            #region child properties
-
-            public int LogicalIndex { get; private set; } = -1;
+            #region child properties           
 
             public EXTStructuralMetadataRoot LogicalParent { get; private set; }
 
-            void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
+            void IChildOf<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent)
             {
-                LogicalParent = parent;
-                LogicalIndex = index;
+                LogicalParent = parent;                
             }
 
             #endregion
@@ -525,14 +799,49 @@ METADATAORURI schema)
             }
 
             #endregion
+
+            #region API
+
+            public StructuralMetadataClass UseClassMetadata(string key)
+            {
+                if (_classes.TryGetValue(key, out var value)) return value;
+
+                value = new StructuralMetadataClass();
+                _classes[key] = value;
+                return value;
+            }
+
+            public StructuralMetadataEnum UseEnumMetadata(string key, params (string name, int value)[] enumValues)
+            {
+                var enumType = UseEnumMetadata(key);
+                foreach(var (name, value) in enumValues)
+                {
+                    enumType.AddEnum(name, value);
+                }
+                return enumType;
+            }
+
+            public StructuralMetadataEnum UseEnumMetadata(string key)
+            {
+                if (_enums.TryGetValue(key, out var value)) return value;
+
+                value = new StructuralMetadataEnum();
+                _enums[key] = value;
+                return value;
+            }
+
+            #endregion
         }
 
+        /// <remarks>
+        /// Use <see cref="StructuralMetadataSchema.UseEnumMetadata(string)"/> to create an instance of this class.
+        /// </remarks> 
         public partial class StructuralMetadataEnum : IChildOfDictionary<StructuralMetadataSchema>
         {
             #region lifecycle
             public StructuralMetadataEnum()
             {
-                _values = new List<EnumValue>();
+                _values = new ChildrenList<StructuralMetadataEnumValue, StructuralMetadataEnum>(this);
             }
 
             #endregion
@@ -553,6 +862,8 @@ METADATAORURI schema)
 
             #region properties
 
+            public IReadOnlyList<StructuralMetadataEnumValue> Values => _values;
+
             public string Name
             {
                 get => _name;
@@ -563,17 +874,50 @@ METADATAORURI schema)
                 get => _description;
                 set => _description = value;
             }
-            public List<EnumValue> Values
+
+            #endregion
+
+            #region API
+
+            public StructuralMetadataEnumValue AddEnum(string name, int value, string desc = null)
             {
-                get => _values;
-                set => _values = value;
+                var prop = AddEnum();
+                prop.Name = name;
+                prop.Value = value;
+                prop.Description = desc;
+                return prop;
+            }
+
+            public StructuralMetadataEnumValue AddEnum()
+            {
+                var prop = new StructuralMetadataEnumValue();
+                _values.Add(prop);
+                return prop;
             }
 
             #endregion
         }
 
-        public partial class EnumValue
+        /// <remarks>
+        /// Use <see cref="StructuralMetadataEnum.AddEnum()"/> to create an instance of this class.
+        /// </remarks> 
+        public partial class StructuralMetadataEnumValue : IChildOfList<StructuralMetadataEnum>
         {
+            #region child properties
+
+            public int LogicalIndex { get; private set; } = -1;
+
+            public StructuralMetadataEnum LogicalParent { get; private set; }
+
+            void IChildOfList<StructuralMetadataEnum>.SetLogicalParent(StructuralMetadataEnum parent, int index)
+            {
+                LogicalParent = parent;
+                LogicalIndex = index;
+            }
+
+            #endregion
+
+            #region properties
             public string Description
             {
                 get => _description;
@@ -589,15 +933,20 @@ METADATAORURI schema)
                 get => _value;
                 set => _value = value;
             }
+
+            #endregion
         }
 
+        /// <remarks>
+        /// Use <see cref="StructuralMetadataSchema.UseClassMetadata(string)"/> to create an instance of this class.
+        /// </remarks> 
         public partial class StructuralMetadataClass : IChildOfDictionary<StructuralMetadataSchema>
         {
             #region lifecycle
 
             public StructuralMetadataClass()
             {
-                _properties = new ChildrenDictionary<ClassProperty, StructuralMetadataClass>(this);
+                _properties = new ChildrenDictionary<StructuralMetadataClassProperty, StructuralMetadataClass>(this);
             }
 
             protected override IEnumerable<ExtraProperties> GetLogicalChildren()
@@ -624,7 +973,7 @@ METADATAORURI schema)
 
             #region properties
 
-            public IReadOnlyDictionary<string, ClassProperty> Properties => _properties;
+            public IReadOnlyDictionary<string, StructuralMetadataClassProperty> Properties => _properties;
 
             public string Name
             {
@@ -640,9 +989,46 @@ METADATAORURI schema)
 
             #endregion
 
+            #region API
+
+            public StructuralMetadataClass WithNameAndDesc(string name, string desc = null)
+            {
+                this.Name = name;
+                this.Description = desc;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty UseProperty(string key)
+            {
+                if (_properties.TryGetValue(key, out var value)) return value;
+
+                value = new StructuralMetadataClassProperty();
+                _properties[key] = value;
+                return value;
+            }
+
+            public PropertyTexture AddPropertyTexture()
+            {
+                return LogicalParent.LogicalParent.AddPropertyTexture(this);
+            }
+
+            public PropertyAttribute AddPropertyAttribute()
+            {
+                return LogicalParent.LogicalParent.AddPropertyAttribute(this);
+            }
+
+            public PropertyTable AddPropertyTable(int? featureCount = null, string name = null)
+            {
+                return LogicalParent.LogicalParent.AddPropertyTable(this, featureCount, name);
+            }
+
+            #endregion
         }
 
-        public partial class ClassProperty : IChildOfDictionary<StructuralMetadataClass>
+        /// <remarks>
+        /// Use <see cref="StructuralMetadataClass.UseProperty(string)"/> to create an instance of this class.
+        /// </remarks> 
+        public partial class StructuralMetadataClassProperty : IChildOfDictionary<StructuralMetadataClass>
         {
             #region child properties
 
@@ -671,7 +1057,7 @@ METADATAORURI schema)
                 set => _description = value;
             }
 
-            public ElementType Type
+            public ELEMENTTYPE Type
             {
                 get => _type;
                 set => _type = value;
@@ -683,28 +1069,28 @@ METADATAORURI schema)
                 set => _enumType = value;
             }
 
-            public DataType? ComponentType
+            public DATATYPE? ComponentType
             {
                 get => _componentType;
                 set => _componentType = value;
             }
 
-            public bool? Required
+            public bool Required
             {
-                get => _required;
-                set => _required = value;
+                get => _required ?? _requiredDefault;
+                set => _required = value.AsNullable(_requiredDefault);
             }
 
-            public bool? Normalized
+            public bool Normalized
             {
-                get => _normalized;
-                set => _normalized = value;
+                get => _normalized ?? _normalizedDefault;
+                set => _normalized = value.AsNullable(_normalizedDefault);
             }
 
-            public bool? Array
+            public bool Array
             {
-                get => _array;
-                set => _array = value;
+                get => _array ?? _arrayDefault;
+                set => _array = value.AsNullable(_arrayDefault);
             }
 
             public int? Count
@@ -714,114 +1100,53 @@ METADATAORURI schema)
             }
 
             #endregion
-        }
 
-        /// <remarks>
-        /// Represents a Propery table of <see cref="EXTStructuralMetadataRoot"/>
-        /// </remarks> 
-        public partial class PropertyTable : IChildOfList<EXTStructuralMetadataRoot>
-        {
-            #region lifecycle
-            public PropertyTable()
-            {
-                _properties = new ChildrenDictionary<PropertyTableProperty, PropertyTable>(this);
-            }
-            public PropertyTable(string Class, int Count, string Name = "") : this()
-            {
-                _class = Class;
-                _count = Count;
-                _name = Name;
-            }
+            #region API
 
-            protected override IEnumerable<ExtraProperties> GetLogicalChildren()
+            public StructuralMetadataClassProperty WithNameAndDesc(string name, string desc = null)
             {
-                return base.GetLogicalChildren()
-                    .Concat(_properties.Values);
+                this.Name = name;
+                this.Description = desc;
+                return this;
             }
 
-            #endregion
-
-            #region child properties
-
-            public int LogicalIndex { get; private set; } = -1;
-            public EXTStructuralMetadataRoot LogicalParent { get; private set; }
-
-            void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
+            public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null, bool normalized = false)
             {
-                LogicalParent = parent;
-                LogicalIndex = index;
+                this.Type = etype;
+                this.ComponentType = ctype;
+                this.Normalized = normalized;
+                this.Array = false;
+                return this;
             }
 
-            #endregion
-
-            #region properties
-
-            public IReadOnlyDictionary<string, PropertyTableProperty> Properties => _properties;
-
-            public string Name
+            public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, bool normalized = false, int? count = null)
             {
-                get => _name;
-                set => _name = value;
+                this.Type = etype;
+                this.ComponentType = ctype;
+                this.Normalized = normalized;
+                this.Array = true;
+                this.Count = count;
+                return this;
             }
 
-            public string Class
+            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumType, int? count = null)
             {
-                get => _class;
-                set => _class = value;
+                return WithEnumArrayType(enumType.LogicalKey, count);
             }
 
-            public int Count
+            public StructuralMetadataClassProperty WithEnumArrayType(string enumType, int? count = null)
             {
-                get => _count;
-                set => _count = value;
+                this.Type = ELEMENTTYPE.ENUM;
+                this.EnumType = enumType;                
+                this.Array = true;
+                this.Count = count;
+                return this;
             }
 
             #endregion
         }
 
-        /// <remarks>
-        /// Represents a Property of <see cref="PropertyTable"/>
-        /// </remarks>    
-        public partial class PropertyTableProperty : IChildOfDictionary<PropertyTable>
-        {
-            #region child properties
-
-            public string LogicalKey { get; private set; }
-
-            public PropertyTable LogicalParent { get; private set; }
-
-            void IChildOfDictionary<PropertyTable>.SetLogicalParent(PropertyTable parent, string key)
-            {
-                LogicalParent = parent;
-                LogicalKey = key;
-            }
-
-            #endregion
-
-            #region properties
-            /// <summary>
-            /// this is an index to a BufferView
-            /// </summary>
-            public int Values
-            {
-                get => _values;
-                set => _values = value;
-            }
-
-            public int? ArrayOffsets
-            {
-                get => _arrayOffsets;
-                set => _arrayOffsets = value;
-            }
-
-            public int? StringOffsets
-            {
-                get => _stringOffsets;
-                set => _stringOffsets = value;
-            }
-
-            #endregion
-        }
+        #endregion
     }
 }
 

+ 6 - 6
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_root.g.cs

@@ -98,7 +98,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
 	#endif
 	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class ClassProperty : ExtraProperties
+	partial class StructuralMetadataClassProperty : ExtraProperties
 	{
 	
 		private static readonly Boolean _arrayDefault = false;
@@ -199,7 +199,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 		
 		private String _name;
 		
-		private ChildrenDictionary<ClassProperty,StructuralMetadataClass> _properties;
+		private ChildrenDictionary<StructuralMetadataClassProperty,StructuralMetadataClass> _properties;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -216,7 +216,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 			{
 				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
 				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
-				case "properties": DeserializePropertyDictionary<ClassProperty>(ref reader, _properties); break;
+				case "properties": DeserializePropertyDictionary<StructuralMetadataClassProperty>(ref reader, _properties); break;
 				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
 			}
 		}
@@ -230,7 +230,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
 	#endif
 	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class EnumValue : ExtraProperties
+	partial class StructuralMetadataEnumValue : ExtraProperties
 	{
 	
 		private String _description;
@@ -279,7 +279,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 		private IntegerType? _valueType = _valueTypeDefault;
 		
 		private const int _valuesMinItems = 1;
-		private List<EnumValue> _values;
+		private ChildrenList<StructuralMetadataEnumValue,StructuralMetadataEnum> _values;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -298,7 +298,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
 				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
 				case "valueType": _valueType = DeserializePropertyValue<IntegerType>(ref reader); break;
-				case "values": DeserializePropertyList<EnumValue>(ref reader, _values); break;
+				case "values": DeserializePropertyList<StructuralMetadataEnumValue>(ref reader, _values); break;
 				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
 			}
 		}

+ 6 - 7
tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs

@@ -38,19 +38,18 @@ namespace SharpGLTF.Schema2.Tiles3D
                     WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":1}"));
 
 
-            var featureId0 = new MeshExtInstanceFeatureID(2, 0, label: "Forests");
-            var featureId1 = new MeshExtInstanceFeatureID(9, label: "Trees");            
+            var featureId0 = new FeatureIDBuilder(2, 0, label: "Forests");
+            var featureId1 = new FeatureIDBuilder(9, label: "Trees");            
 
             var model = sceneBuilder.ToGltf2(settings);
-            model.LogicalNodes[0].SetInstanceFeatureIds(featureId0, featureId1);
+            model.LogicalNodes[0].AddInstanceFeatureIds(featureId0, featureId1);
 
             var cesiumExtInstanceFeaturesExtension = model.LogicalNodes[0].GetExtension<MeshExtInstanceFeatures>();
 
             Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds, Is.Not.Null);
-
-            // these are failing now because the properties of featureId0 are being copied, so a different kind of test might be needed
-            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[0], Is.EqualTo(featureId0)); 
-            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[1], Is.EqualTo(featureId1));
+            
+            Assert.That(featureId0.Equals(cesiumExtInstanceFeaturesExtension.FeatureIds[0])); 
+            Assert.That(featureId1.Equals(cesiumExtInstanceFeaturesExtension.FeatureIds[1]));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.ValidateContent(ctx.GetContext());

+ 4 - 4
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -46,8 +46,8 @@ namespace SharpGLTF.Schema2.Tiles3D
             var model = scene.ToGltf2();
 
             // Set the FeatureIds
-            var featureIdAttribute = new MeshExtInstanceFeatureID(1, 0);            
-            model.LogicalMeshes[0].Primitives[0].SetMeshFeatureIds((featureIdAttribute,null,null));
+            var featureIdAttribute = new FeatureIDBuilder(1, 0);            
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureIdAttribute,null,null));
 
             // Validate the FeatureIds
             var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)model.LogicalMeshes[0].Primitives[0].Extensions.FirstOrDefault();
@@ -107,10 +107,10 @@ namespace SharpGLTF.Schema2.Tiles3D
             var model = scene.ToGltf2();
 
             // Set the FeatureIds, pointing to the red channel of the texture            
-            var featureId = new MeshExtInstanceFeatureID(4);            
+            var featureId = new FeatureIDBuilder(4);            
 
             var primitive = model.LogicalMeshes[0].Primitives[0];
-            primitive.SetMeshFeatureIds((featureId, model.LogicalTextures[0], new int[] { 0 }));
+            primitive.AddMeshFeatureIds((featureId, model.LogicalTextures[0], new int[] { 0 }));
 
             var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)primitive.Extensions.FirstOrDefault();
             Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.Not.Null);

+ 268 - 360
tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs

@@ -22,6 +22,54 @@ namespace SharpGLTF.Schema2.Tiles3D
             Tiles3DExtensions.RegisterExtensions();
         }
 
+        /*
+        [Test(Description = "First test with ext_structural_metadata")]
+        public void TriangleWithMetadataTest()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+            var mesh = new MeshBuilder<VertexPosition>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            prim.AddTriangle(new VertexPosition(-10, 0, 0), new VertexPosition(10, 0, 0), new VertexPosition(0, 10, 0));
+
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            var schema = new StructuralMetadataSchema();
+            schema.Id = "schema_001";
+            schema.Name = "schema 001";
+            schema.Description = "an example schema";
+            schema.Version = "3.5.1";
+            var classes = new Dictionary<string, StructuralMetadataClass>();
+            var treeClass = new StructuralMetadataClass();
+            treeClass.Name = "Tree";
+            treeClass.Description = "Woody, perennial plant.";
+            classes["tree"] = treeClass;
+            var ageProperty = new ClassProperty();
+            ageProperty.Description = "The age of the tree, in years";
+            ageProperty.Type = ElementType.SCALAR;
+            ageProperty.ComponentType = DataType.UINT32;
+            ageProperty.Required = true;
+
+            treeClass.Properties.Add("age", ageProperty);
+
+            schema.Classes = classes;
+
+            var propertyTable = new PropertyTable("tree", 1, "PropertyTable");
+            var agePropertyTableProperty = model.GetPropertyTableProperty(new List<int>() { 100 });
+            propertyTable.Properties.Add("age", agePropertyTableProperty);
+
+            model.SetPropertyTable(propertyTable, schema);
+
+            // create files
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.plotly");
+        }*/
+
         [Test(Description = "ext_structural_metadata with FeatureId Texture and Property Table")]
         // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdTextureAndPropertyTable
         public void FeatureIdTextureAndPropertytableTest()
@@ -43,8 +91,7 @@ namespace SharpGLTF.Schema2.Tiles3D
                 .WithDoubleSide(true)
                 .WithAlpha(Materials.AlphaMode.OPAQUE)
                 .WithMetallicRoughness(0, 1)
-                .WithMetallicRoughness(imageBuilder1)
-            ;
+                .WithMetallicRoughness(imageBuilder1);
 
             var mesh = VBTexture1.CreateCompatibleMesh("mesh");
             var prim = mesh.UsePrimitive(material);
@@ -62,34 +109,44 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
 
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "FeatureIdTextureAndPropertyTableSchema";
+            // --------------------------------------------------------------
 
-            var buildingComponentsClass = new StructuralMetadataClass();
-            buildingComponentsClass.Name = "Building components";
-            buildingComponentsClass.Properties.Add("component", new ClassProperty() { Name = "Component", Type = ElementType.STRING });
-            buildingComponentsClass.Properties.Add("yearBuilt", new ClassProperty() { Name = "Year built", Type = ElementType.SCALAR, ComponentType = DataType.INT16 });
-            schema.Classes.Add("buildingComponents", buildingComponentsClass);
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("FeatureIdTextureAndPropertyTableSchema");
 
-            var propertyTable = new PropertyTable();
-            propertyTable.Name = "Example property table";
-            propertyTable.Class = "buildingComponents";
-            propertyTable.Count = 4;
+            // define schema
 
-            var componentProperty = model.GetPropertyTableProperty(new List<string>() { "Wall", "Door", "Roof", "Window" });
-            var yearBuiltProperty = model.GetPropertyTableProperty(new List<Int16>() { 1960, 1996, 1985, 2002 });
-            propertyTable.Properties.Add("component", componentProperty);
-            propertyTable.Properties.Add("yearBuilt", yearBuiltProperty);
+            var buildingComponentsClass = schema
+                .UseClassMetadata("buildingComponents")
+                .WithNameAndDesc("Building components");
 
-            model.SetPropertyTable(propertyTable, schema);
+            var componentProp = buildingComponentsClass
+                .UseProperty("component")
+                .WithNameAndDesc("Component")
+                .WithValueType(ElementType.STRING);
+
+            var yearProp = buildingComponentsClass
+                .UseProperty("yearBuilt")
+                .WithNameAndDesc("Year built")
+                .WithValueType(ElementType.SCALAR, DataType.INT16);            
+
+            var propertyTable = buildingComponentsClass
+                .AddPropertyTable(4, "Example property table");
+
+            propertyTable
+                .UseProperty(componentProp)
+                .SetValues1D("Wall", "Door", "Roof", "Window");
+
+            propertyTable
+                .UseProperty(yearProp)
+                .SetValues1D(1960, 1996, 1985, 2002);            
 
             // Set the FeatureIds, pointing to the red channel of the texture
-            var texture = new MeshExtMeshFeatureIDTexture(new List<int>() { 0 }, 1, 0);
-            var featureIdTexture = new MeshExtMeshFeatureID(4, texture: texture, propertyTable: 0);
-            var featureIds = new List<MeshExtMeshFeatureID>() { featureIdTexture };
+
+            var featureId = new FeatureIDBuilder(propertyTable);            
 
             var primitive = model.LogicalMeshes[0].Primitives[0];
-            primitive.SetFeatureIds(featureIds);
+            primitive.AddMeshFeatureIds((featureId, model.LogicalTextures[0], new[] {0}));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
 
@@ -138,64 +195,44 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
 
-            var schema = new StructuralMetadataSchema();
-
-            schema.Id = "SimplePropertyTextureSchema";
-
-            var exampleMetadataClass = new StructuralMetadataClass();
-            exampleMetadataClass.Name = "Building properties";
-
-            var insideTemperatureProperty = new ClassProperty();
-            insideTemperatureProperty.Name = "Inside temperature";
-            insideTemperatureProperty.Type = ElementType.SCALAR;
-            insideTemperatureProperty.ComponentType = DataType.UINT8;
-
-            var outsideTemperatureProperty = new ClassProperty();
-            outsideTemperatureProperty.Name = "Outside temperature";
-            outsideTemperatureProperty.Type = ElementType.SCALAR;
-            outsideTemperatureProperty.ComponentType = DataType.UINT8;
-
-            var insulationProperty = new ClassProperty();
-            insulationProperty.Name = "Insulation Thickness";
-            insulationProperty.Type = ElementType.SCALAR;
-            insulationProperty.ComponentType = DataType.UINT8;
-            insulationProperty.Normalized = true;
-
-            exampleMetadataClass.Properties.Add("insideTemperature", insideTemperatureProperty);
-            exampleMetadataClass.Properties.Add("outsideTemperature", outsideTemperatureProperty);
-            exampleMetadataClass.Properties.Add("insulation", insulationProperty);
+            // --------------------------------------------------------------
 
-            schema.Classes.Add("buildingComponents", exampleMetadataClass);
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("SimplePropertyTextureSchema");
 
-            var buildingPropertyTexture = new PropertyTexture();
-            buildingPropertyTexture.ClassName = "buildingComponents";
+            // define schema 
 
-            var insideTemperatureTextureProperty = new PropertyTextureProperty();
-            insideTemperatureTextureProperty._LogicalTextureIndex = 1;
-            insideTemperatureTextureProperty.TextureCoordinate = 0;
-            insideTemperatureTextureProperty.Channels = new List<int>() { 0 };
+            var exampleMetadataClass = schema
+                .UseClassMetadata("buildingComponents")
+                .WithNameAndDesc("Building properties");
 
-            buildingPropertyTexture.Properties.Add("insideTemperature", insideTemperatureTextureProperty);
+            exampleMetadataClass
+                .UseProperty("insideTemperature")
+                .WithNameAndDesc("Inside temperature")
+                .WithValueType(ElementType.SCALAR, DataType.UINT8);
 
-            var outsideTemperatureTextureProperty = new PropertyTextureProperty();
-            outsideTemperatureTextureProperty._LogicalTextureIndex = 1;
-            outsideTemperatureTextureProperty.TextureCoordinate = 0;
-            outsideTemperatureTextureProperty.Channels = new List<int>() { 1 };
+            exampleMetadataClass
+                .UseProperty("outsideTemperature")
+                .WithNameAndDesc("Outside temperature")
+                .WithValueType(ElementType.SCALAR, DataType.UINT8);
 
-            buildingPropertyTexture.Properties.Add("outsideTemperature", outsideTemperatureTextureProperty);
+            exampleMetadataClass
+                .UseProperty("insulation")
+                .WithNameAndDesc("Insulation Thickness")
+                .WithValueType(ElementType.SCALAR, DataType.UINT8, true);
 
-            var insulationTextureProperty = new PropertyTextureProperty();
-            insulationTextureProperty._LogicalTextureIndex = 1;
-            insulationTextureProperty.TextureCoordinate = 0;
-            insulationTextureProperty.Channels = new List<int>() { 2 };
+            // define texture property
 
-            buildingPropertyTexture.Properties.Add("insulation", insulationTextureProperty);
+            var buildingPropertyTexture = exampleMetadataClass.AddPropertyTexture();    
+            
+            buildingPropertyTexture.CreateProperty("insideTemperature", model.LogicalTextures[1], new int[] {0});
+            buildingPropertyTexture.CreateProperty("outsideTemperature", model.LogicalTextures[1], new int[] {1});
+            buildingPropertyTexture.CreateProperty("insulation", model.LogicalTextures[1], new int[] {2});
 
-            model.SetPropertyTexture(buildingPropertyTexture, schema);
+            // assign to primitive
 
-            var primitive = model.LogicalMeshes[0].Primitives[0];
-            var propertyTextures = new List<int> { 0 };
-            primitive.SetPropertyTextures(propertyTextures);
+            var primitive = model.LogicalMeshes[0].Primitives[0];            
+            primitive.AddPropertyTexture(buildingPropertyTexture);            
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.glb");
@@ -205,7 +242,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 
         [Test(Description = "ext_structural_metadata with Multiple Feature IDs and Properties")]
         // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/MultipleFeatureIdsAndProperties
-        public void MultipleFeatureIdsandPropertiesTest()
+        public void MultipleFeatureIdsAndPropertiesTest()
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
@@ -230,55 +267,45 @@ namespace SharpGLTF.Schema2.Tiles3D
 
             var model = scene.ToGltf2();
 
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "MultipleFeatureIdsAndPropertiesSchema";
-
-            var exampleMetadataClass = new StructuralMetadataClass();
-            exampleMetadataClass.Name = "Example metadata class";
-            exampleMetadataClass.Description = "An example metadata class";
-
-            var vector3Property = new ClassProperty();
-            vector3Property.Name = "Example VEC3 FLOAT32 property";
-            vector3Property.Description = "An example property, with type VEC3, with component type FLOAT32";
-            vector3Property.Type = ElementType.VEC3;
-            vector3Property.ComponentType = DataType.FLOAT32;
-
-            exampleMetadataClass.Properties.Add("example_VEC3_FLOAT32", vector3Property);
+            // --------------------------------------------------------------
 
-            var stringProperty = new ClassProperty();
-            stringProperty.Name = "Example STRING property";
-            stringProperty.Description = "An example property, with type STRING";
-            stringProperty.Type = ElementType.STRING;
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("MultipleFeatureIdsAndPropertiesSchema");
 
-            exampleMetadataClass.Properties.Add("example_STRING", stringProperty);
+            // define schema
 
-            schema.Classes.Add("exampleMetadataClass", exampleMetadataClass);
+            var exampleMetadataClass = schema
+                .UseClassMetadata("exampleMetadataClass")
+                .WithNameAndDesc("Example metadata class", "An example metadata class");
 
-            var vector3List = new List<Vector3>() {
-                new Vector3(3, 3.0999999046325684f, 3.200000047683716f),
-                new Vector3(103, 103.0999999046325684f, 103.200000047683716f)
+            var vec3Property = exampleMetadataClass
+                .UseProperty("example_VEC3_FLOAT32")
+                .WithNameAndDesc("Example VEC3 FLOAT32 property", "An example property, with type VEC3, with component type FLOAT32")
+                .WithValueType(ElementType.VEC3, DataType.FLOAT32);
 
-            };
+            var stringProperty = exampleMetadataClass
+                .UseProperty("example_STRING")
+                .WithNameAndDesc("Example STRING property", "An example property, with type STRING")
+                .WithValueType(ElementType.STRING);
 
-            var vector3PropertyTableProperty = model.GetPropertyTableProperty(vector3List);
+            // define table
 
-            var examplePropertyTable = new PropertyTable("exampleMetadataClass", 2, "Example property table");
+            var examplePropertyTable = exampleMetadataClass.AddPropertyTable(2, "Example property table");
 
-            examplePropertyTable.Properties.Add("example_VEC3_FLOAT32", vector3PropertyTableProperty);
+            examplePropertyTable
+                .UseProperty(vec3Property)
+                .SetValues1D(new Vector3(3, 3.0999999046325684f, 3.200000047683716f), new Vector3(103, 103.0999999046325684f, 103.200000047683716f));
 
-            var stringList = new List<string>() { "Rain 🌧", "Thunder ⛈" };
+            examplePropertyTable
+                .UseProperty(stringProperty)
+                .SetValues1D("Rain 🌧", "Thunder ⛈");
 
-            var stringPropertyTableProperty = model.GetPropertyTableProperty(stringList);
+            // assign to primitive
 
-            examplePropertyTable.Properties.Add("example_STRING", stringPropertyTableProperty);
+            var featureId0 = new FeatureIDBuilder(examplePropertyTable, 0);
+            var featureId1 = new FeatureIDBuilder(examplePropertyTable, 1);
 
-            model.SetPropertyTable(examplePropertyTable, schema);
-
-            var featureId0 = new MeshExtMeshFeatureID(2, 0, 0);
-            var featureId1 = new MeshExtMeshFeatureID(2, 1, 0);
-            var featureIds = new List<MeshExtMeshFeatureID>() { featureId0, featureId1 };
-
-            model.LogicalMeshes[0].Primitives[0].SetFeatureIds(featureIds);
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds( (featureId0, null, null), (featureId1, null, null) );
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.glb");
@@ -286,8 +313,8 @@ namespace SharpGLTF.Schema2.Tiles3D
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.plotly");
         }
 
-        [Test(Description = "ext_structural_metadata with FeatureIdAttributeAndPropertyTable")]
         // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdAttributeAndPropertyTable
+        [Test(Description = "ext_structural_metadata with FeatureIdAttributeAndPropertyTable")]        
         public void FeatureIdAndPropertyTableTest()
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
@@ -307,49 +334,44 @@ namespace SharpGLTF.Schema2.Tiles3D
 
             var model = scene.ToGltf2();
 
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "FeatureIdAttributeAndPropertyTableSchema";
-
-            var exampleMetadataClass = new StructuralMetadataClass();
-            exampleMetadataClass.Name = "Example metadata class";
-            exampleMetadataClass.Description = "An example metadata class";
-
-            var vector3Property = new ClassProperty();
-            vector3Property.Name = "Example VEC3 FLOAT32 property";
-            vector3Property.Description = "An example property, with type VEC3, with component type FLOAT32";
-            vector3Property.Type = ElementType.VEC3;
-            vector3Property.ComponentType = DataType.FLOAT32;
+            // --------------------------------------------------------------
 
-            exampleMetadataClass.Properties.Add("example_VEC3_FLOAT32", vector3Property);
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("FeatureIdAttributeAndPropertyTableSchema");
 
-            var matrix4x4Property = new ClassProperty();
-            matrix4x4Property.Name = "Example MAT4 FLOAT32 property";
-            matrix4x4Property.Description = "An example property, with type MAT4, with component type FLOAT32";
-            matrix4x4Property.Type = ElementType.MAT4;
-            matrix4x4Property.ComponentType = DataType.FLOAT32;
+            // define schema
 
-            exampleMetadataClass.Properties.Add("example_MAT4_FLOAT32", matrix4x4Property);
+            var exampleMetadataClass = schema
+                .UseClassMetadata("exampleMetadataClass")
+                .WithNameAndDesc("Example metadata class", "An example metadata class");
 
-            schema.Classes.Add("exampleMetadataClass", exampleMetadataClass);
+            var vector3Property = exampleMetadataClass
+                .UseProperty("example_VEC3_FLOAT32")
+                .WithNameAndDesc("Example VEC3 FLOAT32 property", "An example property, with type VEC3, with component type FLOAT32")
+                .WithValueType(ElementType.VEC3, DataType.FLOAT32);
 
-            var vector3List = new List<Vector3>() { new Vector3(3, 3.0999999046325684f, 3.200000047683716f) };
+            var matrix4x4Property = exampleMetadataClass
+                .UseProperty("example_MAT4_FLOAT32")
+                .WithNameAndDesc("Example MAT4 FLOAT32 property", "An example property, with type MAT4, with component type FLOAT32")
+                .WithValueType(ElementType.MAT4, DataType.FLOAT32);
 
-            var vector3PropertyTableProperty = model.GetPropertyTableProperty(vector3List);
+            // define table
 
-            var examplePropertyTable = new PropertyTable("exampleMetadataClass", 1, "Example property table");
+            var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
 
-            examplePropertyTable.Properties.Add("example_VEC3_FLOAT32", vector3PropertyTableProperty);
+            examplePropertyTable
+                .UseProperty(vector3Property)
+                .SetValues1D(new Vector3(3, 3.0999999046325684f, 3.200000047683716f));
 
-            var matrix4x4List = new List<Matrix4x4>() { Matrix4x4.Identity };
+            examplePropertyTable
+                .UseProperty(matrix4x4Property)
+                .SetValues1D(Matrix4x4.Identity);
 
-            var matrix4x4PropertyTableProperty = model.GetPropertyTableProperty(matrix4x4List);
+            // assign to primitive
 
-            examplePropertyTable.Properties.Add("example_MAT4_FLOAT32", matrix4x4PropertyTableProperty);
+            var featureId = new FeatureIDBuilder(examplePropertyTable, 0);
 
-            model.SetPropertyTable(examplePropertyTable, schema);
-
-            var featureId = new MeshExtMeshFeatureID(1, 0, 0);
-            model.LogicalMeshes[0].Primitives[0].SetFeatureId(featureId);
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId, null, null));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.glb");
@@ -357,8 +379,8 @@ namespace SharpGLTF.Schema2.Tiles3D
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.plotly");
         }
 
-        [Test(Description = "ext_structural_metadata with complex types")]
         // sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/ComplexTypes/
+        [Test(Description = "ext_structural_metadata with complex types")]        
         public void ComplexTypesTest()
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
@@ -378,98 +400,70 @@ namespace SharpGLTF.Schema2.Tiles3D
 
             var model = scene.ToGltf2();
 
-            var schema = new StructuralMetadataSchema();
-
-            var exampleMetadataClass = new StructuralMetadataClass();
-            exampleMetadataClass.Name = "Example metadata class A";
-            exampleMetadataClass.Description = "First example metadata class";
-
-            // class properties
-
-            var uint8ArrayProperty = new ClassProperty();
-            uint8ArrayProperty.Name = "Example variable-length ARRAY normalized INT8 property";
-            uint8ArrayProperty.Description = "An example property, with type ARRAY, with component type UINT8, normalized, and variable length";
-            uint8ArrayProperty.Type = ElementType.SCALAR;
-            uint8ArrayProperty.ComponentType = DataType.UINT8;
-            uint8ArrayProperty.Normalized = false;
-            uint8ArrayProperty.Array = true;
+            // --------------------------------------------------------------
 
-            exampleMetadataClass.Properties.Add("example_variable_length_ARRAY_normalized_UINT8", uint8ArrayProperty);
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("FeatureIdAttributeAndPropertyTableSchema");
 
-            var fixedLengthBooleanProperty = new ClassProperty();
-            fixedLengthBooleanProperty.Name = "Example fixed-length ARRAY BOOLEAN property";
-            fixedLengthBooleanProperty.Description = "An example property, with type ARRAY, with component type BOOLEAN, and fixed length ";
-            fixedLengthBooleanProperty.Type = ElementType.BOOLEAN;
-            fixedLengthBooleanProperty.Array = true;
-            fixedLengthBooleanProperty.Count = 4;
+            // define schema            
 
-            exampleMetadataClass.Properties.Add("example_fixed_length_ARRAY_BOOLEAN", fixedLengthBooleanProperty);
+            var exampleMetadataClass = schema
+                .UseClassMetadata("exampleMetadataClass")
+                .WithNameAndDesc("Example metadata class A", "First example metadata class");
 
-            var variableLengthStringArrayProperty = new ClassProperty();
-            variableLengthStringArrayProperty.Name = "Example variable-length ARRAY STRING property";
-            variableLengthStringArrayProperty.Description = "An example property, with type ARRAY, with component type STRING, and variable length";
-            variableLengthStringArrayProperty.Type = ElementType.STRING;
-            variableLengthStringArrayProperty.Array = true;
-            exampleMetadataClass.Properties.Add("example_variable_length_ARRAY_STRING", variableLengthStringArrayProperty);
+            // enums
 
-            var fixed_length_ARRAY_ENUM = new ClassProperty();
-            fixed_length_ARRAY_ENUM.Name = "Example fixed-length ARRAY ENUM property";
-            fixed_length_ARRAY_ENUM.Description = "An example property, with type ARRAY, with component type ENUM, and fixed length";
-            fixed_length_ARRAY_ENUM.Type = ElementType.ENUM;
-            fixed_length_ARRAY_ENUM.Array = true;
-            fixed_length_ARRAY_ENUM.Count = 2;
-            fixed_length_ARRAY_ENUM.EnumType = "exampleEnumType";
+            var exampleEnum = schema.UseEnumMetadata("exampleEnumType", ("ExampleEnumValueA", 0), ("ExampleEnumValueB", 1), ("ExampleEnumValueC", 2));
 
-            exampleMetadataClass.Properties.Add("example_fixed_length_ARRAY_ENUM", fixed_length_ARRAY_ENUM);
+            // class properties
 
-            schema.Classes.Add("exampleMetadataClass", exampleMetadataClass);
+            var uint8ArrayProperty = exampleMetadataClass
+                .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
+                .WithNameAndDesc("Example variable-length ARRAY normalized INT8 property","An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
+                .WithArrayType(ElementType.SCALAR,DataType.UINT8,false);
 
-            // enums
+            var fixedLengthBooleanProperty = exampleMetadataClass
+                .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
+                .WithNameAndDesc("Example fixed-length ARRAY BOOLEAN property", "An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
+                .WithArrayType(ElementType.BOOLEAN, null, false, 4);
 
-            var exampleEnum = new StructuralMetadataEnum();
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueA", Value = 0 });
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueB", Value = 1 });
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueC", Value = 2 });
+            var variableLengthStringArrayProperty = exampleMetadataClass
+                .UseProperty("example_variable_length_ARRAY_STRING")
+                .WithNameAndDesc("Example variable-length ARRAY STRING property", "An example property, with type ARRAY, with component type STRING, and variable length")
+                .WithArrayType(ElementType.STRING);
 
-            schema.Enums.Add("exampleEnumType", exampleEnum);
+            var fixed_length_ARRAY_ENUM = exampleMetadataClass
+                .UseProperty("example_fixed_length_ARRAY_ENUM")
+                .WithNameAndDesc("Example fixed-length ARRAY ENUM property", "An example property, with type ARRAY, with component type ENUM, and fixed length")
+                .WithEnumArrayType(exampleEnum, 2);
 
             // property tables
 
-            var examplePropertyTable = new PropertyTable("exampleMetadataClass", 1, "Example property table");
-            var list2 = new List<List<byte>>() {
-                new() { 0, 1, 2, 3, 4, 5, 6, 7 }
-            };
-
-            var property = model.GetArrayPropertyTableProperty(list2);
-            examplePropertyTable.Properties.Add("example_variable_length_ARRAY_normalized_UINT8", property);
+            var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
 
-            var booleansList = new List<List<bool>>()
-            {
-                new() { true, false, true, false }
-            };
-            var propertyBooleansList = model.GetArrayPropertyTableProperty(booleansList, false);
-            examplePropertyTable.Properties.Add("example_fixed_length_ARRAY_BOOLEAN", propertyBooleansList);
+            // Question: The table declares a feature count of 1, but then, these properties define different number of items
+            
+            examplePropertyTable
+                .UseProperty(uint8ArrayProperty)
+                .SetValues1D<byte>(0, 1, 2, 3, 4, 5, 6, 7);
 
-            var stringsList = new List<List<string>>()
-            {
-                new() { "Example string 1", "Example string 2", "Example string 3" }
-            };
+            examplePropertyTable
+                .UseProperty(fixedLengthBooleanProperty)
+                .SetValues1D<Boolean>(true, false, true, false);
 
-            var propertyStringsList = model.GetArrayPropertyTableProperty(stringsList);
-            examplePropertyTable.Properties.Add("example_variable_length_ARRAY_STRING", propertyStringsList);
+            examplePropertyTable
+                .UseProperty(variableLengthStringArrayProperty)
+                .SetValues1D("Example string 1", "Example string 2", "Example string 3");
 
-            var enumsList = new List<List<int>>()
-            {
-                new() { 0, 1 }
-            };
+            examplePropertyTable
+                .UseProperty(fixed_length_ARRAY_ENUM)
+                .SetValues1D<int>(0, 1);
 
-            var enumsProperty = model.GetArrayPropertyTableProperty(enumsList, false);
-            examplePropertyTable.Properties.Add("example_fixed_length_ARRAY_ENUM", enumsProperty);
+            // add to primitive            
 
-            model.SetPropertyTable(examplePropertyTable, schema);
+            var featureId = new FeatureIDBuilder(examplePropertyTable, 0);
 
-            var featureId = new MeshExtMeshFeatureID(1, 0, 0);
-            model.LogicalMeshes[0].Primitives[0].SetFeatureId(featureId);
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId, null, null));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.glb");
@@ -477,8 +471,8 @@ namespace SharpGLTF.Schema2.Tiles3D
             model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.plotly");
         }
 
-        [Test(Description = "ext_structural_metadata with multiple classes")]
         // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/MultipleClasses/
+        [Test(Description = "ext_structural_metadata with multiple classes")]        
         public void MultipleClassesTest()
         {
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
@@ -496,36 +490,54 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
 
-            // FeatureID 0: featureCount=1, attribute=0, porpertyTable=0 
-            var featureId0Attribute = new MeshExtMeshFeatureID(1, 0, 0);
-            // FeatureID 1: featureCount=1, attribute=1, porpertyTable=1
-            var featureId1Attribute = new MeshExtMeshFeatureID(1, 1, 1);
+            // --------------------------------------------------------------
 
-            // Set the FeatureIds
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("MultipleClassesSchema");            
 
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "MultipleClassesSchema";
+            // classes
 
-            var classes = new Dictionary<string, StructuralMetadataClass>();
-            classes["exampleMetadataClassA"] = GetExampleClassA();
-            classes["exampleMetadataClassB"] = GetExampleClassB();
+            var classA = schema
+                .UseClassMetadata("exampleMetadataClassA")
+                .WithNameAndDesc("Example metadata class A","First example metadata class");
 
-            schema.Classes = classes;
-            var exampleEnum = new StructuralMetadataEnum();
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueA", Value = 0 });
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueB", Value = 1 });
-            exampleEnum.Values.Add(new EnumValue() { Name = "ExampleEnumValueC", Value = 2 });
+            var classAp0 = classA.UseProperty("example_FLOAT32")
+                .WithNameAndDesc("Example FLOAT32 property", "An example property, with component type FLOAT32")
+                .WithValueType(ElementType.SCALAR, DataType.FLOAT32);
+
+            var classAp1 = classA.UseProperty("example_INT64")
+                .WithNameAndDesc("Example INT64 property", "An example property, with component type INT64")
+                .WithValueType(ElementType.SCALAR, DataType.INT64);
+
+            var classB = schema.UseClassMetadata("exampleMetadataClassB")
+                .WithNameAndDesc("Example metadata class B", "Second example metadata class");
+
+            var classBp0 = classB.UseProperty("example_UINT16")
+                .WithNameAndDesc("Example UINT16 property", "An example property, with component type UINT16")
+                .WithValueType(ElementType.SCALAR, DataType.UINT16);
 
-            schema.Enums.Add("exampleEnumType", exampleEnum);
+            var classBp1 = classB.UseProperty("example_FLOAT64")
+                .WithNameAndDesc("Example FLOAT64 property", "An example property, with component type FLOAT64")
+                .WithValueType(ElementType.SCALAR, DataType.FLOAT64);
 
-            var firstPropertyTable = GetFirstPropertyTable(model);
-            var secondPropertyTable = GetSecondPropertyTable(model);
+            // properties
 
-            var propertyTables = new List<PropertyTable>() { firstPropertyTable, secondPropertyTable };
-            model.SetPropertyTables(propertyTables, schema);
+            var firstPropertyTable = classA.AddPropertyTable(1, "First example property table");
+            firstPropertyTable.UseProperty(classAp0).SetValues1D<float>(100);
+            firstPropertyTable.UseProperty(classAp1).SetValues1D<long>(101);
 
-            var featureIds = new List<MeshExtMeshFeatureID>() { featureId0Attribute, featureId1Attribute };
-            model.LogicalMeshes[0].Primitives[0].SetFeatureIds(featureIds);
+            var secondPropertyTable = classB.AddPropertyTable(1, "Second example property table");
+            secondPropertyTable.UseProperty(classBp0).SetValues1D<ushort>(102);
+            secondPropertyTable.UseProperty(classBp1).SetValues1D<double>(103);
+
+            // features
+
+            // FeatureID 0: featureCount=1, attribute=0, porpertyTable=0 
+            var featureId0 = new FeatureIDBuilder(firstPropertyTable, 0);
+            // FeatureID 1: featureCount=1, attribute=1, porpertyTable=1
+            var featureId1 = new FeatureIDBuilder(secondPropertyTable, 1);
+            
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId0, null,null), (featureId1,null,null));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.glb");
@@ -533,9 +545,9 @@ namespace SharpGLTF.Schema2.Tiles3D
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.plotly");
         }
 
-        [Test(Description = "ext_structural_metadata with pointcloud and custom attributes")]
+        
         // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/PropertyAttributesPointCloud/
-
+        [Test(Description = "ext_structural_metadata with pointcloud and custom attributes")]
         public void CreatePointCloudWithCustomAttributesTest()
         {
             var material = new MaterialBuilder("material1").WithUnlitShader();
@@ -564,138 +576,34 @@ namespace SharpGLTF.Schema2.Tiles3D
             model.UseScene("Default")
                 .CreateNode().WithMesh(model.LogicalMeshes[0]);
 
-            var propertyAttribute = new Schema2.PropertyAttribute();
-            propertyAttribute.Class = "exampleMetadataClass";
-            var intensityProperty = new PropertyAttributeProperty();
-            intensityProperty.Attribute = "_INTENSITY";
-            var classificationProperty = new PropertyAttributeProperty();
-            classificationProperty.Attribute = "_CLASSIFICATION";
-            propertyAttribute.Properties["intensity"] = intensityProperty;
-            propertyAttribute.Properties["classification"] = classificationProperty;
-
-            var schemaUri = new Uri("MetadataSchema.json", UriKind.Relative);
-            model.SetPropertyAttribute(propertyAttribute, schemaUri);
-            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.glb");
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.gltf");
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.plotly");
-        }
+            // --------------------------------------------------------------
 
+            var rootMetadata = model.UseStructuralMetadata();
 
-        private static PropertyTable GetFirstPropertyTable(ModelRoot model)
-        {
-            var firstPropertyTable = new PropertyTable("exampleMetadataClassA", 1, "First example property table");
-            var float32Property = model.GetPropertyTableProperty(new List<float>() { 100 });
-            firstPropertyTable.Properties.Add("example_FLOAT32", float32Property);
-            var int64Property = model.GetPropertyTableProperty(new List<long>() { 101 });
-            firstPropertyTable.Properties.Add("example_INT64", int64Property);
-            return firstPropertyTable;
-        }
+            // external references are problematic because the idea behind SharpGLTF is that all files are loaded into memory, so you don't
+            // need to track resources in disk while working with models. The whole mechanism is too complex to be worth the pain of implementing it.
+            // so my idea is that the UseExternalSchema returns a ISchemaProxy interface or something like that, that has pretty much the same methods
+            // of an actual schema, so the API usage remains the same for both an embedded and an external schema.
 
-        private static PropertyTable GetSecondPropertyTable(ModelRoot model)
-        {
-            var secondPropertyTable = new PropertyTable("exampleMetadataClassB", 1, "First example property table");
-            var uint16Property = model.GetPropertyTableProperty(new List<ushort>() { 102 });
-            secondPropertyTable.Properties.Add("example_UINT16", uint16Property);
-            var float64Property = model.GetPropertyTableProperty(new List<double>() { 103 });
-            secondPropertyTable.Properties.Add("example_FLOAT64", float64Property);
-            return secondPropertyTable;
-        }
+            // var schemaUri = new Uri("MetadataSchema.json", UriKind.Relative);            
+            // var schemaProxy = rootMetadata.UseExternalSchema(schemaUri);
 
-        private static StructuralMetadataClass GetExampleClassB()
-        {
-            var classB = new StructuralMetadataClass();
-            classB.Name = "Example metadata class B";
-            classB.Description = "Second example metadata class";
-
-            var uint16Property = new ClassProperty();
-            uint16Property.Name = "Example UINT16 property";
-            uint16Property.Description = "An example property, with component type UINT16";
-            uint16Property.Type = ElementType.SCALAR;
-            uint16Property.ComponentType = DataType.UINT16;
-
-            classB.Properties.Add("example_UINT16", uint16Property);
-
-            var float64Property = new ClassProperty();
-            float64Property.Name = "Example FLOAT64 property";
-            float64Property.Description = "An example property, with component type FLOAT64";
-            float64Property.Type = ElementType.SCALAR;
-            float64Property.ComponentType = DataType.FLOAT64;
-
-            classB.Properties.Add("example_FLOAT64", float64Property);
-            return classB;
-        }
+            var schema = rootMetadata.UseEmbeddedSchema("externalSchema");
 
+            var externalClass = schema.UseClassMetadata("exampleMetadataClass");
 
-        private static StructuralMetadataClass GetExampleClassA()
-        {
-            var classA = new StructuralMetadataClass();
-            classA.Name = "Example metadata class A";
-            classA.Description = "First example metadata class";
-
-            var float32Property = new ClassProperty();
-            float32Property.Name = "Example FLOAT32 property";
-            float32Property.Description = "An example property, with component type FLOAT32";
-            float32Property.Type = ElementType.SCALAR;
-            float32Property.ComponentType = DataType.FLOAT32;
-
-            classA.Properties.Add("example_FLOAT32", float32Property);
-
-            var int64Property = new ClassProperty();
-            int64Property.Name = "Example INT64 property";
-            int64Property.Description = "An example property, with component type INT64";
-            int64Property.Type = ElementType.SCALAR;
-            int64Property.ComponentType = DataType.INT64;
-
-            classA.Properties.Add("example_INT64", int64Property);
-            return classA;
-        }
+            var propertyAttribute = rootMetadata.AddPropertyAttribute(externalClass);
 
-        [Test(Description = "First test with ext_structural_metadata")]
-        public void TriangleWithMetadataTest()
-        {
-            TestContext.CurrentContext.AttachGltfValidatorLinks();
-            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
-            var mesh = new MeshBuilder<VertexPosition>("mesh");
-            var prim = mesh.UsePrimitive(material);
-
-            prim.AddTriangle(new VertexPosition(-10, 0, 0), new VertexPosition(10, 0, 0), new VertexPosition(0, 10, 0));
-
-            var scene = new SceneBuilder();
-            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
-            var model = scene.ToGltf2();
-
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "schema_001";
-            schema.Name = "schema 001";
-            schema.Description = "an example schema";
-            schema.Version = "3.5.1";
-            var classes = new Dictionary<string, StructuralMetadataClass>();
-            var treeClass = new StructuralMetadataClass();
-            treeClass.Name = "Tree";
-            treeClass.Description = "Woody, perennial plant.";
-            classes["tree"] = treeClass;
-            var ageProperty = new ClassProperty();
-            ageProperty.Description = "The age of the tree, in years";
-            ageProperty.Type = ElementType.SCALAR;
-            ageProperty.ComponentType = DataType.UINT32;
-            ageProperty.Required = true;
-
-            treeClass.Properties.Add("age", ageProperty);
-
-            schema.Classes = classes;
-
-            var propertyTable = new PropertyTable("tree", 1, "PropertyTable");
-            var agePropertyTableProperty = model.GetPropertyTableProperty(new List<int>() { 100 });
-            propertyTable.Properties.Add("age", agePropertyTableProperty);
+            var intensityProperty = propertyAttribute.CreateProperty("intensity");             
+            intensityProperty.Attribute = "_INTENSITY";
 
-            model.SetPropertyTable(propertyTable, schema);
+            var classificationProperty = propertyAttribute.CreateProperty("classification");
+            classificationProperty.Attribute = "_CLASSIFICATION";
 
-            // create files
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.glb");
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.gltf");
-            model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.plotly");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.plotly");
         }
     }
 }

+ 2 - 0
tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj

@@ -7,9 +7,11 @@
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
 
+  <!--
   <ItemGroup>
     <Compile Remove="ExtStructuralMetadataTests.cs" />
   </ItemGroup>  
+  -->
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\SharpGLTF.Ext.3DTiles\SharpGLTF.Ext.3DTiles.csproj" />