Browse Source

Merge pull request #206 from bertt/add_cesium_ext_structural_metadata

Add cesium ext structural metadata
Vicente Penades 2 years ago
parent
commit
e96ba20a0f
45 changed files with 7658 additions and 165 deletions
  1. 69 0
      build/SharpGLTF.CodeGen/Ext.EXT_Structural_Metadata.cs
  2. 3 0
      build/SharpGLTF.CodeGen/Program.cs
  3. 1 0
      build/SharpGLTF.CodeGen/SchemaProcessing.cs
  4. 578 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/README.md
  5. BIN
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-attribute.png
  6. 474 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-attribute.svg
  7. BIN
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-table.png
  8. 1501 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-table.svg
  9. BIN
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-texture.png
  10. 1852 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-texture.svg
  11. 26 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/EXT_structural_metadata.schema.json
  12. 182 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/class.property.schema.json
  13. 34 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/class.schema.json
  14. 119 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/definitions.schema.json
  15. 70 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/enum.schema.json
  16. 34 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/enum.value.schema.json
  17. 53 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/glTF.EXT_structural_metadata.schema.json
  18. 32 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/mesh.primitive.EXT_structural_metadata.schema.json
  19. 55 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyAttribute.property.schema.json
  20. 36 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyAttribute.schema.json
  21. 117 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTable.property.schema.json
  22. 42 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTable.schema.json
  23. 62 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTexture.property.schema.json
  24. 36 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTexture.schema.json
  25. 55 0
      build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/schema.schema.json
  26. 1 28
      build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj
  27. 213 0
      src/SharpGLTF.Cesium/Memory/BinaryTable.cs
  28. 3 6
      src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs
  29. 55 44
      src/SharpGLTF.Cesium/Schema2/Ext.InstanceFeatures.cs
  30. 71 50
      src/SharpGLTF.Cesium/Schema2/Ext.MeshFeatures.cs
  31. 82 0
      src/SharpGLTF.Cesium/Schema2/Ext.StructuralMetadataPrimitive.cs
  32. 671 0
      src/SharpGLTF.Cesium/Schema2/Ext.StructuralMetadataRoot.cs
  33. 66 0
      src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_primitive.g.cs
  34. 683 0
      src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_root.g.cs
  35. 4 0
      src/SharpGLTF.Cesium/SharpGLTF.Cesium.csproj
  36. 0 16
      src/SharpGLTF.Cesium/Validator.cs
  37. 2 3
      tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs
  38. 3 10
      tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
  39. 2 6
      tests/SharpGLTF.Cesium.Tests/ExtPrimitiveOutlineTests.cs
  40. 30 0
      tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs
  41. 128 0
      tests/SharpGLTF.Cesium.Tests/Memory/BinaryTableTests.cs
  42. 33 0
      tests/SharpGLTF.Cesium.Tests/VertexBuilder.cs
  43. 92 0
      tests/SharpGLTF.Cesium.Tests/VertexPointcloud.cs
  44. 2 2
      tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs
  45. 86 0
      tests/SharpGLTF.Cesium.Tests/VertexWithFeatureIds.cs

+ 69 - 0
build/SharpGLTF.CodeGen/Ext.EXT_Structural_Metadata.cs

@@ -0,0 +1,69 @@
+using SharpGLTF.CodeGen;
+using SharpGLTF.SchemaReflection;
+using System.Collections.Generic;
+
+namespace SharpGLTF
+{
+    class ExtStructuralMetadataExtension : SchemaProcessor
+    {
+        public override string GetTargetProject() { return Constants.CesiumProjectDirectory; }
+
+        const string ExtensionPropertyTexturePropertyName = "Property Texture Property in EXT_structural_metadata";
+
+        private static string RootSchemaUri => Constants.CustomExtensionsPath("EXT_structural_metadata", "glTF.EXT_structural_metadata.schema.json");
+        private static string MeshPrimitiveSchemaUri => Constants.CustomExtensionsPath("EXT_structural_metadata", "mesh.primitive.EXT_structural_metadata.schema.json");
+
+        public override void PrepareTypes(CSharpEmitter newEmitter, SchemaType.Context ctx)
+        {
+            newEmitter.SetRuntimeName("EXT_structural_metadata glTF Mesh Primitive extension", "ExtStructuralMetadataMeshPrimitive");
+            newEmitter.SetRuntimeName("EXT_structural_metadata glTF extension", "EXTStructuralMetadataRoot");
+            newEmitter.SetRuntimeName("Property Table in EXT_structural_metadata", "PropertyTable");
+            newEmitter.SetRuntimeName("Schema in EXT_structural_metadata", "StructuralMetadataSchema");
+            newEmitter.SetRuntimeName("Property Table Property in EXT_structural_metadata", "PropertyTableProperty");
+            newEmitter.SetRuntimeName("Property Texture in EXT_structural_metadata", "PropertyTexture");
+            newEmitter.SetRuntimeName("Property Texture Property in EXT_structural_metadata", "PropertyTextureProperty");
+            newEmitter.SetRuntimeName("Property Attribute Property in EXT_structural_metadata", "PropertyAttributeProperty");
+            newEmitter.SetRuntimeName("Class Property in EXT_structural_metadata", "ClassProperty");
+            newEmitter.SetRuntimeName("Class in EXT_structural_metadata", "StructuralMetadataClass");
+            newEmitter.SetRuntimeName("Enum Value in EXT_structural_metadata", "EnumValue");
+            newEmitter.SetRuntimeName("Enum in EXT_structural_metadata", "StructuralMetadataEnum");
+            newEmitter.SetRuntimeName("Property Attribute in EXT_structural_metadata", "PropertyAttribute");
+            newEmitter.SetRuntimeName("BOOLEAN-ENUM-MAT2-MAT3-MAT4-SCALAR-STRING-VEC2-VEC3-VEC4", "ElementType");
+            newEmitter.SetRuntimeName("FLOAT32-FLOAT64-INT16-INT32-INT64-INT8-UINT16-UINT32-UINT64-UINT8", "DataType");
+            newEmitter.SetRuntimeName("INT16-INT32-INT64-INT8-UINT16-UINT32-UINT64-UINT8", "IntegerType");
+            newEmitter.SetRuntimeName("UINT16-UINT32-UINT64-UINT8", "ArrayOffsetType");
+        }
+
+        public override IEnumerable<(string TargetFileName, SchemaType.Context Schema)> Process()
+        {
+            yield return ("Ext.CESIUM_ext_structural_metadata_root.g", ProcessRoot());
+            yield return ("Ext.CESIUM_ext_structural_metadata_primitive.g", ProcessMeshPrimitive());
+        }
+
+        private static SchemaType.Context ProcessRoot()
+        {
+            var ctx = SchemaProcessing.LoadSchemaContext(RootSchemaUri);
+            ctx.IgnoredByCodeEmitter("glTF Property");
+            ctx.IgnoredByCodeEmitter("glTF Child of Root Property");
+            ctx.IgnoredByCodeEmitter("Texture Info");
+            var fld = ctx.FindClass(ExtensionPropertyTexturePropertyName).GetField("channels");
+
+            // for now we simply remove the default value, it can be set
+            // in the constructor or on demand when the APIs are Called.
+            fld.RemoveDefaultValue();
+
+            return ctx;
+        }
+
+        private static SchemaType.Context ProcessMeshPrimitive()
+        {
+            var ctx = SchemaProcessing.LoadSchemaContext(MeshPrimitiveSchemaUri);
+            ctx.IgnoredByCodeEmitter("glTF Property");
+            ctx.IgnoredByCodeEmitter("glTF Child of Root Property");
+            ctx.IgnoredByCodeEmitter("Texture Info");
+            return ctx;
+        }
+
+
+    }
+}

+ 3 - 0
build/SharpGLTF.CodeGen/Program.cs

@@ -62,6 +62,9 @@ namespace SharpGLTF
 
 
             processors.Add(new ExtInstanceFeaturesExtension());
             processors.Add(new ExtInstanceFeaturesExtension());
 
 
+            processors.Add(new ExtStructuralMetadataExtension());
+
+
             // ----------------------------------------------  process all files
             // ----------------------------------------------  process all files
 
 
             foreach (var processor in processors)
             foreach (var processor in processors)

+ 1 - 0
build/SharpGLTF.CodeGen/SchemaProcessing.cs

@@ -24,6 +24,7 @@ namespace SharpGLTF
 
 
             var settings = new NJsonSchema.CodeGeneration.CSharp.CSharpGeneratorSettings
             var settings = new NJsonSchema.CodeGeneration.CSharp.CSharpGeneratorSettings
             {
             {
+
                 Namespace = "glTf.POCO",
                 Namespace = "glTf.POCO",
                 ClassStyle = NJsonSchema.CodeGeneration.CSharp.CSharpClassStyle.Poco
                 ClassStyle = NJsonSchema.CodeGeneration.CSharp.CSharpClassStyle.Poco
             };
             };

+ 578 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/README.md

@@ -0,0 +1,578 @@
+
+<!-- omit in toc -->
+# EXT_structural_metadata
+
+PR at Khronos: https://github.com/KhronosGroup/glTF/pull/2151
+
+This directory contains schema's for Cesium extension EXT_structural_metadata
+
+Copied from https://github.com/CesiumGS/glTF/tree/proposal-EXT_structural_metadata
+
+<!-- omit in toc -->
+## Contributors
+
+* Peter Gagliardi, Cesium
+* Sean Lilley, Cesium
+* Sam Suhag, Cesium
+* Don McCurdy, Independent
+* Marco Hutter, Cesium
+* Bao Tran, Cesium
+* Samuel Vargas, Cesium
+* Patrick Cozzi, Cesium
+
+<!-- omit in toc -->
+## Status
+
+Draft
+
+<!-- omit in toc -->
+## Dependencies
+
+Written against the glTF 2.0 specification.
+
+<!-- omit in toc -->
+## Table of Contents
+
+- [Overview](#overview)
+- [Schema Definitions](#schema-definitions)
+    - [Overview](#overview-1)
+    - [Schema](#schema)
+    - [Class](#class)
+    - [Class Property](#class-property)
+    - [Enum](#enum)
+    - [Enum Value](#enum-value)
+- [Metadata Storage](#metadata-storage)
+  - [Property Tables](#property-tables)
+  - [Property Attributes](#property-attributes)
+  - [Property Textures](#property-textures)
+  - [Binary Data Storage](#binary-data-storage)
+- [Assigning Metadata](#assigning-metadata)
+- [Optional vs. Required](#optional-vs-required)
+- [Schema](#schema-1)
+- [Revision History](#revision-history)
+
+## Overview
+
+This extension defines a means of storing structured metadata within a glTF 2.0 asset. The key concepts of this extension are the definition of a schema that describes the structure of the metadata, and methods for storing and associating metadata with different entities within the asset.
+
+The metadata schema definition includes a JSON representation of the schema structure, suitable for being stored inside a glTF asset. The storage formats that are defined in this extension allow storing metadata in standard glTF vertex attributes, or in the channels of a texture. Both representations are compact binary storage formats that are appropriate for efficiently transmitting large quantities of metadata. 
+
+The schema definition and the storage formats in this extension are implementations of the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata). This specification should be considered a normative reference for definitions and requirements. This document provides inline definitions of terms where appropriate.
+
+> **Disambiguation:** glTF has other methods of storing details that could similarly be described as metadata or properties, including [`KHR_xmp_json_ld`](../../Khronos/KHR_xmp_json_ld), Extras, and Extensions. While those methods associate data with discrete objects in a glTF asset — nodes, materials, etc. — `EXT_structural_metadata` is uniquely suited for properties of more granular conceptual features in subregions composed of vertices or texels.
+
+## Schema Definitions
+
+#### Overview
+
+Data types and meanings of properties are provided by a schema, as defined in the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata/) and summarized below.
+
+#### Schema
+
+*Defined in [schema.schema.json](./schema/schema.schema.json).*
+
+Top-level definitions for the structure and data types of properties. The schema provides a set of [classes](#class) and [enums](#enum) the asset can reference.
+
+A schema may be embedded in the extension directly or referenced externally with the `schemaUri` property. Multiple glTF assets may refer to the same external schema to avoid duplication. A schema is defined by an `EXT_structural_metadata` extension attached to the glTF root object.
+
+> **Example:** A simple schema defining enums and classes.
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {
+>         "id": "schema_001",
+>         "name": "Schema 001",
+>         "description": "An example schema.",
+>         "version": "3.5.1",
+>         "enums": { ... },
+>         "classes": { ... }
+>       }
+>     }
+>   }
+> }
+> ```
+
+#### Class
+
+*Defined in [class.schema.json](./schema/class.schema.json).*
+
+Template for metadata entities. Classes provide a list of property definitions. Instances of a class can be created from property values that conform to the class's property definitions. 
+
+Classes are defined as entries in the `schema.classes` dictionary, indexed by class ID. Class IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+
+> **Example:** A "Tree" class, which might describe a table of tree measurements taken in a park. Property definitions are abbreviated here, and introduced in the next section.
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {
+>         "classes": {
+>           "tree": {
+>             "name": "Tree",
+>             "description": "Woody, perennial plant.",
+>             "properties": {
+>               "species": { ... },
+>               "age": { ... },
+>               "height": { ... },
+>               "diameter": { ... }
+>             }
+>           }
+>         }
+>       }
+>     }
+>   }
+> }
+> ```
+
+#### Class Property
+
+*Defined in [class.property.schema.json](./schema/class.property.schema.json).*
+
+Class properties are defined abstractly in a class. The class is instantiated with specific values conforming to these properties. Class properties support a richer variety of data types than glTF accessors or GPU shading languages allow. Details about the supported types can be found in the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#property).
+
+Class properties are defined as entries in the `class.properties` dictionary, indexed by property ID. Property IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+
+> **Example:** A "Tree" class, which might describe a table of tree measurements taken in a park. Properties include species, height, and diameter of each tree, as well as the number of birds observed in its branches.
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {
+>         "classes": {
+>           "tree": {
+>             "name": "Tree",
+>             "description": "Woody, perennial plant.",
+>             "properties": {
+>               "species": {
+>                 "description": "Type of tree.",
+>                 "type": "ENUM",
+>                 "enumType": "speciesEnum",
+>                 "required": true
+>               },
+>               "age": {
+>                 "description": "The age of the tree, in years",
+>                 "type": "SCALAR",
+>                 "componentType": "UINT8",
+>                 "required": true
+>               },
+>               "height": {
+>                 "description": "Height of tree measured from ground level, in meters.",
+>                 "type": "SCALAR",
+>                 "componentType": "FLOAT32"
+>               },
+>               "diameter": {
+>                 "description": "Diameter at trunk base, in meters.",
+>                 "type": "SCALAR",
+>                 "componentType": "FLOAT32"
+>               }
+>             }
+>           }
+>         }
+>       }
+>     }
+>   }
+> }
+> ```
+
+#### Enum
+
+*Defined in [enum.schema.json](./schema/enum.schema.json).*
+
+Set of categorical types, defined as `(name, value)` pairs. Enum properties use an enum as their type.
+
+Enums are defined as entries in the `schema.enums` dictionary, indexed by an enum ID. Enum IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+
+> **Example:** A "Species" enum defining types of trees. An "Unspecified" enum value is optional, but when provided as the `noData` value for a property (see: [3D Metadata → No Data Values](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#required-properties-no-data-values-and-default-values)) may be helpful to identify missing data.
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {
+>         "enums": {
+>           "speciesEnum": {
+>             "name": "Species",
+>             "description": "An example enum for tree species.",
+>             "values": [
+>               {"name": "Unspecified", "value": 0},
+>               {"name": "Oak", "value": 1},
+>               {"name": "Pine", "value": 2},
+>               {"name": "Maple", "value": 3}
+>             ]
+>           }
+>         }
+>       }
+>     }
+>   }
+> }
+> ```
+
+#### Enum Value
+
+*Defined in [enum.value.schema.json](./schema/enum.value.schema.json).*
+
+Pairs of `(name, value)` entries representing possible values of an enum property.
+
+Enum values are defined as entries in the `enum.values` array. Duplicate names or duplicate integer values are not allowed.
+
+
+## Metadata Storage
+
+The classes defined in the schema are templates describing the data types and meanings of properties. An instance of such a metadata class is referred to as a _metadata entity_, and can be created from a set of values that conform to the structure of the class. This extension defines different ways of storing large amounts of property values inside a glTF asset, in compact binary forms: 
+
+- **Property Tables** store property values as parallel arrays in a column-based binary layout, using standard glTF buffer views. These tables can be accessed with a row index, and allow associating complex, structured metadata with arbitrary types with entities of a glTF asset on different levels of granularity.
+- **Property Attributes** associate vertex attributes of a mesh primitive with a particular metadata class. 
+- **Property Textures** store property values in channels of a texture, suitable for very high-frequency data mapped to less-detailed 3D surfaces. 
+
+Each storage type refers to a metadata class, and contains a dictionary of `properties`. Each of these properties corresponds to one property of the metadata class and defines how the actual property data is stored. These property storage definitions may override the [`minimum` and `maximum` values](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#minimum-and-maximum-values) and the [`offset` and `scale`](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#offset-and-scale) from the property definition in the class, to account for the actual range of values that is stored for each property.
+
+The following sections describe these storage formats in more detail.
+
+### Property Tables
+
+*Defined in [propertyTable.schema.json](./schema/propertyTable.schema.json).*
+
+Each property table defines a specified number (`count`) of metadata entities conforming to a particular class (`class`), with property values stored as parallel arrays in a column-based binary layout. Property tables support a richer variety of data types than glTF accessors or GPU shading languages allow, and are suitable for datasets that can be expressed in a tabular layout.
+
+Property tables are defined as entries in the `propertyTables` array of the root-level `EXT_structural_metadata` extension, and may be referenced by [assigning metadata](#assigning-metadata) to glTF elements, or by other extensions. 
+
+The property table may provide value arrays for only a subset of the properties of its class, but class properties marked `required: true` must not be omitted. Each property value array given by the property table must be defined by a class property with the same alphanumeric property ID, with values matching the data type of the class property.
+
+> **Example:** A `tree_survey_2021-09-29` property table, implementing the `tree` class defined in earlier examples. The table contains observations for 10 trees, with each property defined by a buffer view containing 10 values.
+>
+> The glTF asset contains multiple objects (trees) that are associated with unique identifiers. These identifiers can be assigned to the objects in different ways. For example, using the [`EXT_mesh_features`](../EXT_mesh_features) extension, the [`EXT_instance_features`](../EXT_instance_features) extension, or an application-specific mechanism for identifying the objects. The identifiers then serve as an index into the property table row that stores the property values for the properties that are defined in the `tree` class.
+> 
+> > ![Property Table](figures/property-table.png)
+> 
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": { ... },
+>       "propertyTables": [{
+>         "name": "tree_survey_2021-09-29",
+>         "class": "tree",
+>         "count": 10,
+>         "properties": {
+>           "species": {
+>             "values": 0,
+>           },
+>           "age": {
+>             "values": 1
+>           },
+>           "height": {
+>             "values": 2
+>           },
+>           // "diameter" is not required and has been omitted from this table.
+>         }
+>       }]
+>     }
+>   }
+> }
+> ```
+
+Property arrays are stored in glTF buffer views and use the binary encoding defined in the [Binary Table Format](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#binary-table-format) section of the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata).
+
+As in the core glTF specification, values of `NaN`, `+Infinity`, and `-Infinity` are never allowed.
+
+Each buffer view `byteOffset` must be aligned to a multiple of its component size.
+
+> **Implementation note:** Authoring tools may choose to align all buffer views to 8-byte boundaries for consistency, but client implementations should only depend on 8-byte alignment for buffer views containing 64-bit component types.
+
+### Property Attributes
+
+*Defined in [propertyAttribute.schema.json](./schema/propertyAttribute.schema.json).*
+
+Property attributes associate vertex attributes of a mesh primitive with a metadata class. They refer to a certain class from the schema definition via their `class` property, and contain a `properties` dictionary that defines a set of properties that conform to this class. Each property refers to an attribute that may be stored in a mesh primitive. 
+
+The property types that are supported via property attributes are therefore restricted to the types that are supported by standard glTF accessors. These types are a strict subset of the types that are supported with the schema definitions in this extension.
+
+> **Example:** 
+>
+> An example of a property attribute that represents information about the movement of points in a point cloud. 
+> 
+> The schema defines a class called `movement`. It has a `direction` property that is a normalized 3D float vector for the movement direction, and a `magnitude` property that describes the magnitude of the movement. 
+>
+> The top-level `propertyAttributes` array contains a property attribute that refers to this class. The `movement` and `direction` properties of the class are associated with attributes called `_DIRECTION` and `_MAGNITUDE`. 
+> 
+> The mesh primitive defines (non-indexed) vertices with primitive mode 0, and thus, represents a simple point cloud, with the positions of the points being stored in the `POSITION` attribute. Additionally, it defines vertex attributes `_DIRECTION` and `_MAGNITUDE`, which contain the data for the properties from the property attribute. 
+> 
+> ![Property Attribute](figures/property-attribute.png)
+> 
+> _Top-level extension object:_
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {  
+>         "classes": {
+>           "movement": {
+>             "name": "movement",
+>             "description": "The movement of points in a point cloud",
+>             "properties": {
+>               "direction": {
+>                 "description": "The movement direction, as a normalized 3D vector",
+>                 "type": "VEC3",
+>                 "componentType": "FLOAT32",
+>                 "required": true
+>               },
+>               "magnitude": {
+>                 "description": "The magnitude of the movement",
+>                 "type": "SCALAR",
+>                 "componentType": "FLOAT32",
+>                 "required": true
+>               }
+>             }
+>           }
+>         }
+>       },
+>       "propertyAttributes": [{
+>         "class": "movement",
+>         "properties": {
+>           "direction": {
+>             "attribute": "_DIRECTION",
+>           },
+>           "magnitude": {
+>             "attribute": "_MAGNITUDE",
+>           }
+>         }
+>       }]
+>     }
+>   }
+> }
+> ```
+> _Primitive_
+>
+> ```jsonc
+> {
+>   "primitives": [
+>     {
+>       "mode:": 0, 
+>       "attributes": {
+>         "POSITION": 0,
+>         "_DIRECTION": 1,
+>         "_MAGNITUDE": 2,
+>       },
+>       "extensions": {
+>         "EXT_structural_metadata": {
+>           "propertyAttributes": [0]
+>         }
+>       }
+>     }
+>   ]
+> }
+> ```
+
+
+### Property Textures
+
+*Defined in [propertyTexture.schema.json](./schema/propertyTexture.schema.json).*
+
+Property textures use texture channels to store property values conforming to a particular class (identified by ID `class`), with those values accessed directly by texture coordinates. Property textures are especially useful when texture mapping high frequency data to less detailed 3D surfaces. Unlike textures used in glTF materials, property textures are not necessarily visible in a rendered scene. Like property tables, property textures are implementations of the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata).
+
+Property textures are defined as entries in the `propertyTextures` array of the root-level `EXT_structural_metadata` extension, and may be referenced by extensions on primitive objects. 
+
+A property texture may provide channels for only a subset of the properties of its class, but class properties marked `required: true` must not be omitted.
+
+> **Example:** Property texture implementing a "wall" class, with property values stored in a glTF texture at index 0 and indexed by `TEXCOORD_0`. Each property of the `"wall"` class is stored in one channel of the texture.
+>
+> ![Property Texture](figures/property-texture.png)
+>
+> _Class and property texture_
+>
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": {
+>         "classes": {
+>           "wall": {
+>             "name": "Wall Temperature vs. Insulation",
+>             "properties": {
+>               "insideTemperature": {
+>                 "name": "Inside Temperature",
+>                 "type": "SCALAR",
+>                 "componentType": "UINT8"
+>               },
+>               "outsideTemperature": {
+>                 "name": "Outside Temperature",
+>                 "type": "SCALAR",
+>                 "componentType": "UINT8"
+>               },
+>               "insulation": {
+>                 "name": "Insulation Thickness",
+>                 "type": "SCALAR",
+>                 "componentType": "UINT8",
+>                 "normalized": true
+>               }
+>             }
+>           }
+>         }
+>       },
+>       "propertyTextures": [
+>         {
+>           "class": "wall",
+>           "properties": {
+>             "insideTemperature": {
+>               "index": 0,
+>               "texCoord": 0,
+>               "channels": [0]
+>             },
+>             "outsideTemperature": {
+>               "index": 0,
+>               "texCoord": 0,
+>               "channels": [1]
+>             },
+>             "insulation": {
+>               "index": 0,
+>               "texCoord": 0,
+>               "channels": [2]
+>             }
+>           }
+>         }
+>       ]
+>     }
+>   }
+> }
+> ```
+>
+> _Primitive_
+>
+> ```jsonc
+> {
+>   "primitives": [
+>     {
+>       "attributes": {
+>         "POSITION": 0,
+>         "TEXCOORD_0": 1
+>       },
+>       "indices": 2,
+>       "material": 0,
+>       "extensions": {
+>         "EXT_structural_metadata": {
+>           "propertyTextures": [0]
+>         }
+>       }
+>     }
+>   ]
+> }
+> ```
+
+Each property that is defined in the `propertyTexture` object extends the glTF [`textureInfo`](../../../../specification/2.0/schema/textureInfo.schema.json) object. The `texCoord` specifies a texture coordinate set in the referring primitive. The `index` is the index of the glTF texture object that stores the actual data. Additionally, each property specifies an array of `channels`, which are the indices of the texture channels providing data for the respective property. Channels of an `RGBA` texture are numbered 0–3 respectively. 
+
+Texture filtering must be `9728` (NEAREST), `9729` (LINEAR), or undefined, for any texture object referenced as a property texture. Texture values must be encoded with a linear transfer function.
+
+> **Example:** A property texture for 2D wind velocity samples. The "speed" property values are stored in the red channel. The "direction" property values are stored as a unit-length vector, with X/Y components in the green and blue channels. Both properties are indexed by UV coordinates in a `TEXCOORD_0` attribute.
+>
+> ```jsonc
+> // Root EXT_structural_metadata extension:
+> {
+>   "propertyTextures": [
+>     {
+>       "class": "wind",
+>       "properties": {
+>         "speed": {
+>           "index": 0,
+>           "texCoord": 0,
+>           "channels": [0]
+>         },
+>         "direction": {
+>           "index": 0,
+>           "texCoord": 0,
+>           "channels": [1, 2]
+>         }
+>       }
+>     }
+>   ]
+> }
+
+<!-- omit in toc -->
+#### Property Texture Data Storage
+
+Multiple channels of a property texture can be used to represent individual bytes of larger data types. The values from the selected channels represent the bytes of the actual property value, in little-endian order.
+
+> **Implementation note:** Specialized texture formats may allow additional channels, or channels with a higher number of bits per channel. The usage of such texture formats for property textures has to be defined by additional extensions.
+
+Certain property types cannot be encoded in property textures. For example, variable-length arrays or strings are not supported. Enum values may be encoded as integer values according to their enum value type (see [Enum](#enum)). For other property types, the number of channels that are selected must match the number of bytes of the property type.
+
+> **Example:** 
+> 
+> If a property is defined to store (single) `FLOAT32` components, then these values can be stored in the 4 channels of a property texture. The raw bits of the property value can be computed as 
+> ```
+> vec4<uint8> rgba = texture(sampler, coordinates);
+> uint8 byte0 = rgba[channels[0]];
+> uint8 byte1 = rgba[channels[1]];
+> uint8 byte2 = rgba[channels[2]];
+> uint8 byte3 = rgba[channels[3]];
+> uint32 rawBits = byte0 | (byte1 << 8) | (byte2 << 16) | (byte3 << 24);
+> float32 value = uintBitsToFloat(rawBits);
+> ```
+> 
+> If a property has the type `VEC2` with `UIN16` components, or an array with a fixed length of 2 and `UINT16` components, then the respective property can be represented with 4 channels as well:
+> ```
+> vec4<uint8> rgba = texture(sampler, coordinates);
+> uint8 byte0 = rgba[channels[0]];
+> uint8 byte1 = rgba[channels[1]];
+> uint8 byte2 = rgba[channels[2]];
+> uint8 byte3 = rgba[channels[3]];
+> value[0] = byte0 | (byte1 << 8);
+> value[1] = byte2 | (byte3 << 8);
+> ```
+
+### Binary Data Storage
+
+Property values are stored in a compact binary tabular format described in the [3D Metadata Specification](https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata), with each property table array occupying a glTF buffer view. `EXT_structural_metadata` imposes 8-byte binary data alignment requirements on an asset, allowing support for 64-bit data types while remaining compatible with the 4-byte alignments in the core glTF specification:
+
+- GLB-stored `JSON` chunk must be padded with trailing `Space` characters (`0x20`) to 8-byte boundary.
+- GLB-stored `BIN` chunk must be padded with trailing zeroes (`0x00`) to 8-byte boundary.
+
+As a result, byte length of the `BIN` chunk may be up to 7 bytes larger than JSON-defined `buffer.byteLength` to satisfy alignment requirements.
+
+## Assigning Metadata
+
+*Defined in [EXT_structural_metadata.schema.json](./schema/EXT_structural_metadata.schema.json).*
+
+When property values are stored in a [Property Table](#property-tables), then the entries of this table may be referenced from within the glTF asset: Each `node` of the glTF asset can contain an `EXT_structural_metadata` object that defines the source of the metadata values for this node. It contains the `propertyTable`, which is the index of the property table in the array of property tables of the root-level `EXT_structural_metadata` extension object, and the `index`, which is the index of the row in this table that contains the metadata values for the respective node.
+
+> **Example:** 
+>
+> An example of metadata that is assigned to a node. It associates the given node with the metadata values that are stored in row 4 of the property table with index 1, referring to the array of property tables that are defined in the top-level `EXT_structural_metadata` extension object. 
+> 
+> ```jsonc
+> {
+>   "extensions": {
+>     "EXT_structural_metadata": {
+>       "schema": { ... },
+>       "propertyTables": [ ... ]
+>     }
+>   },
+>   "nodes": [
+>     ...
+>     {
+>       "name": "A node with metadata",
+>       "EXT_structural_metadata": {
+>         "propertyTable": 1,
+>         "index": 4
+>       }
+>     }
+>   ]
+> }
+> ```
+
+## Optional vs. Required
+
+This extension is optional, meaning it should be placed in the `extensionsUsed` list, but not in the `extensionsRequired` list.
+
+## Schema
+
+* [glTF.EXT_structural_metadata.schema.json](./schema/glTF.EXT_structural_metadata.schema.json)
+* [mesh.primitive.EXT_structural_metadata.schema.json](./schema/mesh.primitive.EXT_structural_metadata.schema.json)
+
+## Revision History
+
+The revision history of this extension can be found in the [common revision history of the 3D Tiles Next extensions](https://github.com/CesiumGS/3d-tiles/blob/main/next/REVISION_HISTORY.md).

BIN
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-attribute.png


+ 474 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-attribute.svg

@@ -0,0 +1,474 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="210mm"
+   height="297mm"
+   viewBox="0 0 210 297"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1 (c68e22c387, 2021-05-23)"
+   sodipodi:docname="property-mapping.svg"
+   inkscape:export-filename="property-mapping.png"
+   inkscape:export-xdpi="182.19081"
+   inkscape:export-ydpi="182.19081"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="1"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="in"
+     showgrid="true"
+     inkscape:zoom="1.3416246"
+     inkscape:cx="324.60646"
+     inkscape:cy="180.75101"
+     inkscape:window-width="1920"
+     inkscape:window-height="1137"
+     inkscape:window-x="-8"
+     inkscape:window-y="-2"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     scale-x="1"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid1012" />
+  </sodipodi:namedview>
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="2.7257915"
+       y="63.637604"
+       id="text7073-8"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9"
+         x="2.7257915"
+         y="63.637604"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">POSITION</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="2.8367128"
+       y="68.996651"
+       id="text7073-03-9"><tspan
+         sodipodi:role="line"
+         id="tspan7075-09-6"
+         x="2.8367128"
+         y="68.996651"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">_DIRECTION</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="2.7937675"
+       y="74.195015"
+       id="text7073-03-9-4"><tspan
+         sodipodi:role="line"
+         id="tspan7075-09-6-5"
+         x="2.7937675"
+         y="74.195015"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">_MAGNITUDE</tspan></text>
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cae2f6;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9"
+       width="35.71875"
+       height="3.9687507"
+       x="26.458334"
+       y="66.145836" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cae2f6;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-1"
+       width="35.71875"
+       height="3.9687507"
+       x="62.177082"
+       y="66.145836" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cae2f6;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-84"
+       width="35.71875"
+       height="3.9687507"
+       x="97.895836"
+       y="66.145836" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cae2f6;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-0"
+       width="7.9375052"
+       height="3.9687488"
+       x="133.61458"
+       y="66.145836" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="26.886538"
+       y="68.965439"
+       id="text7073-99-94"><tspan
+         sodipodi:role="line"
+         id="tspan7075-7-3"
+         x="26.886538"
+         y="68.965439"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">( 0.59,-0.16, -0.80),( 0.30, 0.69,  0.66),(0.98, 0.14, -0.13),  ...</tspan></text>
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2ecd4;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-3"
+       height="3.9687507"
+       x="26.458334"
+       y="71.437508"
+       width="11.90625" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2ecd4;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-3-9"
+       height="3.9687507"
+       x="38.364582"
+       y="71.437508"
+       width="11.90625" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2ecd4;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-3-6"
+       height="3.9687507"
+       x="50.270832"
+       y="71.437508"
+       width="11.90625" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2ecd4;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-3-6-4"
+       height="3.9687476"
+       x="62.177082"
+       y="71.437508"
+       width="9.2604179" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="26.886538"
+       y="74.257111"
+       id="text7073-99-94-8"><tspan
+         sodipodi:role="line"
+         id="tspan7075-7-3-2"
+         x="26.886538"
+         y="74.257111"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px"> 0.538, 2.011, 1.319, ...</tspan><tspan
+         sodipodi:role="line"
+         x="26.886538"
+         y="76.189865"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan1710" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="32.036407"
+       y="45.628513"
+       id="text7073-99-94-8-6"><tspan
+         sodipodi:role="line"
+         x="32.036407"
+         y="45.628513"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan104610"><tspan
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold'"
+           id="tspan102172">Magnitude</tspan></tspan><tspan
+         sodipodi:role="line"
+         x="32.036407"
+         y="47.561268"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan1710-8" /></text>
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-8"
+       width="35.71875"
+       height="3.9687507"
+       x="26.458334"
+       y="60.854164" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-8-2"
+       width="35.71875"
+       height="3.9687507"
+       x="62.177082"
+       y="60.854164" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-8-9"
+       width="35.71875"
+       height="3.9687507"
+       x="97.895836"
+       y="60.854164" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#e2e2e2;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect7089-9-8-1"
+       height="3.9687502"
+       x="133.61458"
+       y="60.854164"
+       width="7.9375052" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="26.886538"
+       y="63.673767"
+       id="text7073-99-94-6"><tspan
+         sodipodi:role="line"
+         id="tspan7075-7-3-6"
+         x="26.886538"
+         y="63.673767"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">(1.3,   8.3,   2.4 ),(5.5,   4.2,   6.0 ),(2.1,   1.1,   9.8 ), ...</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="4.2667384"
+       y="11.984531"
+       id="text7073-99-94-6-4-4"><tspan
+         sodipodi:role="line"
+         x="4.2667384"
+         y="11.984531"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan98598-0">Metadata stored for each point:</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="3.3017058"
+       y="57.510887"
+       id="text7073-99-94-6-4-4-90"><tspan
+         sodipodi:role="line"
+         x="3.3017058"
+         y="57.510887"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan98598-0-8">Metadata storage in vertex attribute accessors:</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="74.51757"
+       y="11.755486"
+       id="text7073-99-94-6-4-4-9"><tspan
+         sodipodi:role="line"
+         x="74.51757"
+         y="11.755486"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan98598-0-4">Point cloud:</tspan></text>
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217"
+       cx="83.831955"
+       cy="19.840044"
+       r="2.54" />
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-7"
+       cx="85.103523"
+       cy="44.187019"
+       r="2.54" />
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-3"
+       cx="100.72185"
+       cy="33.935642"
+       r="2.54" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 86.427511,19.909223 2.583338,0.57167 0.1715,-0.775001 2.297502,1.863339 -2.869171,0.719998 0.171501,-0.775001 -2.583337,-0.57167 z"
+       id="path19775"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 101.80679,31.355188 4.72121,-6.380763 -0.63808,-0.472121 2.63719,-1.340053 -0.51027,2.913788 -0.63808,-0.472121 -4.7212,6.380763 z"
+       id="path19775-2"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 85.058889,41.685757 1.584889,-5.04875 -0.757312,-0.237733 2.054632,-2.128152 0.469742,2.920596 -0.757312,-0.237733 -1.58489,5.04875 z"
+       id="path19775-2-0"
+       sodipodi:nodetypes="cccccccc" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="82.926918"
+       y="20.783543"
+       id="text7073-8-0"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8"
+         x="82.926918"
+         y="20.783543"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">0</tspan></text>
+    <ellipse
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.507999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-34"
+       cx="24.038437"
+       cy="28.597281"
+       rx="9.6689606"
+       ry="9.9328356" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264582px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 33.918822,28.867817 9.833932,2.235553 0.652855,-3.030693 8.745836,7.28671 -10.922012,2.815598 0.652855,-3.030692 -9.833898,-2.235552 z"
+       id="path19775-5"
+       sodipodi:nodetypes="cccccccc" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="99.804626"
+       y="34.899021"
+       id="text7073-8-0-6"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-0"
+         x="99.804626"
+         y="34.899021"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">1</tspan></text>
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-3-7"
+       cx="118.55717"
+       cy="44.016933"
+       r="2.54" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 120.99621,25.567988 7.80143,-1.463417 -0.14631,-0.78015 2.84438,0.812437 -2.35657,1.788044 -0.14632,-0.780149 -7.80143,1.463418 z"
+       id="path19775-2-01"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 103.10767,17.847439 2.33223,0.480771 0.15784,-0.777899 2.32999,1.822549 -2.85603,0.770465 0.15784,-0.777897 -2.33223,-0.480772 z"
+       id="path19775-2-01-3"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 102.217,45.620875 2.88945,-4.433154 -0.66525,-0.432995 2.55201,-1.495931 -0.33446,2.939164 -0.66525,-0.432996 -2.88945,4.433155 z"
+       id="path19775-2-01-0"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#caf6f6;fill-opacity:1;stroke:#808080;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 119.91906,41.817727 4.93088,-4.796808 -0.55357,-0.568852 2.81876,-0.897265 -0.97344,2.793375 -0.55358,-0.568856 -4.93088,4.796809 z"
+       id="path19775-2-01-34"
+       sodipodi:nodetypes="cccccccc" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="116.05247"
+       y="44.186562"
+       id="text7073-8-0-6-3"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-0-1"
+         x="116.05247"
+         y="44.186562"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">...</tspan></text>
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-3-7-6"
+       cx="100.50022"
+       cy="18.020401"
+       r="2.54" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="97.995506"
+       y="18.190029"
+       id="text7073-8-0-6-3-3"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-0-1-6"
+         x="97.995506"
+         y="18.190029"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">...</tspan></text>
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-3-7-8"
+       cx="101.27965"
+       cy="48.091599"
+       r="2.54" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="98.774925"
+       y="48.261227"
+       id="text7073-8-0-6-3-6"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-0-1-8"
+         x="98.774925"
+         y="48.261227"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">...</tspan></text>
+    <circle
+       style="fill:#e2e2e2;fill-opacity:1;stroke:#808080;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path15217-3-7-7"
+       cx="118.51942"
+       cy="26.855165"
+       r="2.54" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="116.0147"
+       y="27.024794"
+       id="text7073-8-0-6-3-4"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-0-1-1"
+         x="116.0147"
+         y="27.024794"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">...</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="84.186302"
+       y="45.150398"
+       id="text7073-8-0-5"><tspan
+         sodipodi:role="line"
+         id="tspan7075-9-8-7"
+         x="84.186302"
+         y="45.150398"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';baseline-shift:baseline;stroke-width:0.282222px">2</tspan></text>
+    <path
+       style="fill:#008000;stroke:#008000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 32.150953,36.165746 -0.991342,4.432677"
+       id="path113784"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:#008000;stroke:#008000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 53.267221,40.554276 -0.991342,4.432677"
+       id="path113784-2"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:#008000;stroke:#008000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 31.655282,38.382085 21.116268,4.38853"
+       id="path113784-7"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="fill:none;fill-opacity:1;stroke:#404040;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect114424"
+       width="68.791679"
+       height="44.979172"
+       x="72.760414"
+       y="7.9375" />
+    <rect
+       style="fill:none;fill-opacity:1;stroke:#404040;stroke-width:0.508;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect114424-6"
+       width="68.791679"
+       height="44.979168"
+       x="2.6458209"
+       y="7.9375" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="17.06414"
+       y="29.855467"
+       id="text7073-99-94-6-4"><tspan
+         sodipodi:role="line"
+         x="17.06414"
+         y="29.855467"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';baseline-shift:baseline;fill:#000000;fill-opacity:1;stroke-width:0.282222px"
+         id="tspan98598">Position</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.38667px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.282222px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="35.865673"
+       y="33.639225"
+       id="text7073-99-94-1"><tspan
+         sodipodi:role="line"
+         x="35.865673"
+         y="33.639225"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';baseline-shift:baseline;stroke-width:0.282222px"
+         id="tspan100534">Direction</tspan></text>
+  </g>
+</svg>

BIN
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-table.png


+ 1501 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-table.svg

@@ -0,0 +1,1501 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="210mm"
+   height="297mm"
+   viewBox="0 0 210 297"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.2 (76b9e6a115, 2022-02-25)"
+   sodipodi:docname="property-table.svg"
+   inkscape:export-filename="property-table.png"
+   inkscape:export-xdpi="189.7816"
+   inkscape:export-ydpi="189.7816"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="1"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="in"
+     showgrid="true"
+     inkscape:zoom="1.3416246"
+     inkscape:cx="-512.06576"
+     inkscape:cy="122.98523"
+     inkscape:window-width="3840"
+     inkscape:window-height="2111"
+     inkscape:window-x="0"
+     inkscape:window-y="25"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid1608"
+       units="in"
+       spacingx="0.254"
+       spacingy="0.254" />
+  </sodipodi:namedview>
+  <defs
+     id="defs2">
+    <marker
+       style="overflow:visible;"
+       id="Arrow2Send"
+       refX="0.0"
+       refY="0.0"
+       orient="auto"
+       inkscape:stockid="Arrow2Send"
+       inkscape:isstock="true">
+      <path
+         transform="scale(0.3) rotate(180) translate(-2.3,0)"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+         style="fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round;"
+         id="path64916" />
+    </marker>
+  </defs>
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <g
+       id="g129753-4-6"
+       transform="matrix(0.16186867,0,0,0.16186867,-50.779057,-269.11335)"
+       style="display:inline;stroke-width:1.74353">
+      <path
+         sodipodi:nodetypes="ccccccssscccccccccccccssccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-1-9-9"
+         d="m 678.56465,1792.0028 c 1.66655,-1.1646 2.00803,-0.7915 3.33812,-1.9506 0.65448,-1.5252 0.29212,-5.3001 0.55819,-10.6081 -0.36679,-2.2668 -2.6326,-1.0221 -4.34766,-3.5185 l 0.4735,-2.0236 4.15999,1.749 c 0.19922,-0.9601 0.40584,-1.9659 0.19165,-3.9226 -0.0804,-0.7345 -2.50634,-1.4672 -3.79925,-3.1756 -0.52034,-0.6876 -0.79756,-2.3454 -0.38893,-2.1782 2.8404,1.1623 4.2738,2.3322 4.27156,2.1608 0,0 0.19756,-5.7588 0.0463,-7.1565 -0.74164,-1.5307 -3.44428,-2.4974 -3.44428,-2.4974 l 0.22527,-2.1991 3.35973,1.458 0.17094,-6.8746 1.75863,-0.8452 0.0854,9.1312 3.56719,-2.9892 0.43647,1.8933 c 0,0 -3.13465,2.3986 -3.75985,3.9194 -0.15289,1.6206 0.0892,3.6394 0.0892,3.6394 l 4.09443,-2.7491 c 1.15645,0.172 0.56545,0.6012 0.77269,1.7403 0.15333,0.8427 -4.80562,3.4583 -4.74904,4.6223 0.17169,3.532 -0.0811,5.6572 0.43971,6.3445 2.54411,-2.8835 3.80778,-2.8156 5.30799,-2.7728 l 0.28457,2.381 c -3.00225,1.9446 -5.24554,1.1439 -5.17011,6.8249 0.26523,5.8164 -0.26689,4.4584 0.41068,7.5612 1.01892,0.7404 1.01446,0.362 3.38883,1.8371 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-9-9"
+         d="m 688.56997,1775.5502 c -1.16103,-1.2164 -4.12374,0.2976 -3.31451,-2.2205 0.68757,-2.1408 5.46336,-1.9851 6.64355,-0.9342 1.75025,-1.202 2.34589,-1.1837 4.40722,-0.01 2.7471,1.8161 -0.21712,-0.1451 0.9636,3.6284 -3.06807,2.6152 -4.3468,-0.3074 -7.44743,0.8484 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-3-8"
+         d="m 688.07978,1763.3742 c -1.56912,0.2586 -2.11987,1.4884 -1.31063,4.007 0.68756,2.1408 2.24882,-0.3655 3.596,-0.1001 1.87549,0.073 1.42744,2.547 3.48877,1.3689 1.99565,-3.3675 1.32753,-1.8764 1.5898,-3.9105 -3.06806,-2.6152 -2.55166,-0.4916 -5.65229,-1.6474 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-3-7"
+         d="m 686.97769,1757.6923 c -1.28843,0.9465 -2.8127,1.2736 -2.02584,3.4286 0.66856,1.8317 2.93809,-0.3127 4.248,-0.086 1.82364,0.063 0.9705,0.769 2.97482,-0.2391 1.94047,-2.8813 2.29276,-1.6055 2.54778,-3.3459 -2.98321,-2.2376 -2.48109,-0.4206 -5.49598,-1.4096 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-6-7-4"
+         d="m 679.55205,1756.1329 c -2.23158,-0.4224 -2.38375,-0.1269 -3.44526,0.4669 -0.94948,0.5312 -1.9881,2.414 -0.67818,2.6407 1.82364,0.063 1.01225,2.5084 3.01657,1.5003 3.35401,1.5864 3.37819,1.5443 3.63321,-0.1961 -2.98322,-2.2377 1.94412,-3.1473 -1.07077,-4.1362 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccccc"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-9-9-3"
+         d="m 681.9813,1754.1004 c -0.78457,-0.1293 -0.29963,-2.0767 1.52817,-4.0542 -1.01101,-2.2125 -0.29775,-5.84 1.04942,-6.1053 1.8755,-0.073 -0.20069,3.0473 1.86062,4.2254 1.99565,3.3675 0.86832,2.7697 0.92186,5.6501 -3.06806,2.6152 -0.54779,0.4916 -3.64842,1.6474 0,0 -0.92709,-1.2341 -1.71165,-1.3634 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-2-2-8"
+         d="m 683.61677,1769.3273 c 3.46392,-1.3003 -1.58351,-3.6018 -3.90778,-4.9943 -1.1817,-0.708 -2.62455,-1.2329 -3.80474,-0.182 -1.75025,-1.202 -0.88472,-1.9358 -2.94605,-0.7577 -2.11718,3.2352 1.51129,0.043 0.33057,3.8165 3.06806,2.6153 0.4643,0.3977 3.56493,1.5535 3.281,2.0173 5.92812,0.8775 6.76307,0.564 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-5-9-7"
+         d="m 681.1319,1776.8658 c 1.40243,-0.8337 3.12362,-1.081 2.39606,-3.6311 -0.57073,-2.0003 -3.97313,-1.0035 -6.51831,-1.5922 -0.79006,-1.6722 -2.47112,-2.0299 -4.53245,-0.8517 -4.50049,6.0471 1.55304,0.701 0.37236,4.4745 3.06807,2.6152 3.67883,1.338 6.77946,2.4938 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.74351px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129757-5"
+       transform="matrix(0.26221868,0,0,0.26221868,-115.22041,-464.73221)"
+       style="display:inline;stroke-width:1.07629">
+      <path
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-9-5"
+         d="m 643.61016,1851.6952 c 1.94114,-1.2045 2.33888,-0.8186 3.88812,-2.0174 0.76232,-1.5777 -0.14111,-9.4019 0.16879,-14.892 -0.42722,-2.3447 -2.79128,-3.2234 -4.78892,-5.8055 l 1.54864,-1.1991 3.57321,3.0812 c 0.3676,-1.5731 0.74157,-2.7543 0.72429,-3.9293 l 2.36264,0.7483 c 0.1282,1.0782 0.0231,1.5669 0.83586,3.5138 2.96329,-2.9825 2.57845,-2.259 4.25707,-3.5901 l 0.81283,1.947 c -3.49691,2.0114 -4.6657,2.421 -4.57784,8.297 0.30892,6.0161 0.1705,8.5311 0.95971,11.7406 1.1868,0.7657 1.18161,0.3743 3.94719,1.9 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.07628px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="sscccsccscscssscsss"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-1-2"
+         d="m 642.13534,1831.734 c -1.52167,1.0471 -3.52579,0.053 -5.51378,-1.8765 -1.68977,-1.6397 -1.63782,-4.6775 -0.0822,-5.4843 -1.19894,-1.5495 -1.4076,-4.5911 0.68964,-6.2796 2.03365,-1.6384 1.9354,-1.5963 5.88138,0.3033 -0.0289,-1.9585 -2.49093,-3.8302 -2.04644,-5.8466 0.39763,-1.8036 0.72433,-3.9294 4.25641,-3.5903 0.0982,-2.6104 8.20876,-1.2988 8.24988,1.4434 3.13954,0.3446 3.62904,1.5302 3.97667,3.8588 0.30457,2.0422 -0.35665,2.3564 -1.88283,5.1219 2.33893,-0.8186 3.11251,-1.8922 5.075,-1.2516 2.23817,0.7305 3.57313,3.0812 2.03548,5.0634 2.36206,0.7481 1.60657,4.6555 0.4856,6.262 -1.18209,1.693 -2.41302,3.3109 -4.27372,2.4152 -1.86075,-0.8957 -0.80709,-1.5552 -4.75424,-3.4554 -1.17661,-0.5665 -2.32669,1.6024 -4.28586,1.6315 -2.74189,0.041 -1.98739,-1.929 -3.1633,-1.9117 -0.65265,0.01 -1.97044,-0.1081 -2.34416,0.4273 -1.18211,1.6931 -1.13972,2.3682 -2.30358,3.1692 z"
+         style="fill:#197c36;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.07628px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <rect
+       style="fill:#ffffff;fill-opacity:0.60219;stroke:none;stroke-width:0.508"
+       id="rect55705"
+       width="13.96999"
+       height="13.969997"
+       x="49.529984"
+       y="8.7630053" />
+    <g
+       id="g129743"
+       transform="matrix(0.32517089,0,0,0.32517089,-197.85972,-561.0068)"
+       style="display:inline;stroke-width:0.86792">
+      <path
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9"
+         d="m 634.61016,1788.6952 c 1.94114,-1.2045 2.33888,-0.8186 3.88812,-2.0175 0.76232,-1.5776 -0.14111,-9.4018 0.16879,-14.892 -0.42722,-2.3446 -2.79128,-3.2233 -4.78892,-5.8054 l 1.54864,-1.1991 3.57321,3.0812 c 0.3676,-1.5731 0.74157,-2.7543 0.72429,-3.9293 l 2.36264,0.7483 c 0.1282,1.0782 0.0231,1.5668 0.83586,3.5138 2.96329,-2.9825 2.57845,-2.259 4.25707,-3.5901 l 0.81283,1.947 c -3.49691,2.0114 -4.6657,2.421 -4.57784,8.297 0.30892,6.0161 0.1705,8.5311 0.95971,11.7405 1.1868,0.7658 1.18161,0.3744 3.94719,1.9001 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.867914px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="sscccsccscscssscsss"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6"
+         d="m 633.13534,1768.734 c -1.52167,1.0471 -3.52579,0.053 -5.51378,-1.8765 -1.68977,-1.6397 -1.63782,-4.6775 -0.0822,-5.4843 -1.19894,-1.5495 -1.4076,-4.5911 0.68964,-6.2796 2.03365,-1.6384 1.9354,-1.5963 5.88138,0.3033 -0.0289,-1.9585 -2.49093,-3.8302 -2.04644,-5.8466 0.39763,-1.8036 0.72433,-3.9294 4.25641,-3.5903 0.0982,-2.6104 8.20876,-1.2988 8.24988,1.4434 3.13954,0.3446 3.62904,1.5302 3.97667,3.8588 0.30457,2.0422 -0.35665,2.3563 -1.88283,5.1219 2.33893,-0.8186 3.11251,-1.8922 5.075,-1.2516 2.23817,0.7305 3.57313,3.0812 2.03548,5.0634 2.36206,0.7481 1.60657,4.6555 0.4856,6.262 -1.18209,1.693 -2.41302,3.3109 -4.27372,2.4151 -1.86075,-0.8956 -0.80709,-1.5551 -4.75424,-3.4553 -1.17661,-0.5665 -2.32669,1.6024 -4.28586,1.6315 -2.74189,0.041 -1.98739,-1.929 -3.1633,-1.9117 -0.65265,0.01 -1.97044,-0.1081 -2.34416,0.4273 -1.18211,1.6931 -1.13972,2.3682 -2.30358,3.1692 z"
+         style="fill:#84e948;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.867914px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129743-0"
+       transform="matrix(0.44105392,0,0,0.44105392,-235.96661,-767.61013)"
+       style="display:inline;stroke-width:0.639882">
+      <path
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-4"
+         d="m 634.61016,1788.6952 c 1.94114,-1.2045 2.33888,-0.8186 3.88812,-2.0175 0.76232,-1.5776 -0.14111,-9.4018 0.16879,-14.892 -0.42722,-2.3446 -2.79128,-3.2233 -4.78892,-5.8054 l 1.54864,-1.1991 3.57321,3.0812 c 0.3676,-1.5731 0.74157,-2.7543 0.72429,-3.9293 l 2.36264,0.7483 c 0.1282,1.0782 0.0231,1.5668 0.83586,3.5138 2.96329,-2.9825 2.57845,-2.259 4.25707,-3.5901 l 0.81283,1.947 c -3.49691,2.0114 -4.6657,2.421 -4.57784,8.297 0.30892,6.0161 0.1705,8.5311 0.95971,11.7405 1.1868,0.7658 1.18161,0.3744 3.94719,1.9001 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.639877px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="sscccsccscscssscsss"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-4"
+         d="m 633.13534,1768.734 c -1.52167,1.0471 -3.52579,0.053 -5.51378,-1.8765 -1.68977,-1.6397 -1.63782,-4.6775 -0.0822,-5.4843 -1.19894,-1.5495 -1.4076,-4.5911 0.68964,-6.2796 2.03365,-1.6384 1.9354,-1.5963 5.88138,0.3033 -0.0289,-1.9585 -2.49093,-3.8302 -2.04644,-5.8466 0.39763,-1.8036 0.72433,-3.9294 4.25641,-3.5903 0.0982,-2.6104 8.20876,-1.2988 8.24988,1.4434 3.13954,0.3446 3.62904,1.5302 3.97667,3.8588 0.30457,2.0422 -0.35665,2.3563 -1.88283,5.1219 2.33893,-0.8186 3.11251,-1.8922 5.075,-1.2516 2.23817,0.7305 3.57313,3.0812 2.03548,5.0634 2.36206,0.7481 1.60657,4.6555 0.4856,6.262 -1.18209,1.693 -2.41302,3.3109 -4.27372,2.4151 -1.86075,-0.8956 -0.80709,-1.5551 -4.75424,-3.4553 -1.17661,-0.5665 -2.32669,1.6024 -4.28586,1.6315 -2.74189,0.041 -1.98739,-1.929 -3.1633,-1.9117 -0.65265,0.01 -1.97044,-0.1081 -2.34416,0.4273 -1.18211,1.6931 -1.13972,2.3682 -2.30358,3.1692 z"
+         style="fill:#84e948;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.639877px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129757"
+       transform="matrix(0.26221868,0,0,0.26221868,-144.43041,-466.00221)"
+       style="display:inline;stroke-width:1.07629">
+      <path
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-9"
+         d="m 643.61016,1851.6952 c 1.94114,-1.2045 2.33888,-0.8186 3.88812,-2.0174 0.76232,-1.5777 -0.14111,-9.4019 0.16879,-14.892 -0.42722,-2.3447 -2.79128,-3.2234 -4.78892,-5.8055 l 1.54864,-1.1991 3.57321,3.0812 c 0.3676,-1.5731 0.74157,-2.7543 0.72429,-3.9293 l 2.36264,0.7483 c 0.1282,1.0782 0.0231,1.5669 0.83586,3.5138 2.96329,-2.9825 2.57845,-2.259 4.25707,-3.5901 l 0.81283,1.947 c -3.49691,2.0114 -4.6657,2.421 -4.57784,8.297 0.30892,6.0161 0.1705,8.5311 0.95971,11.7406 1.1868,0.7657 1.18161,0.3743 3.94719,1.9 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.07628px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="sscccsccscscssscsss"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-1"
+         d="m 642.13534,1831.734 c -1.52167,1.0471 -3.52579,0.053 -5.51378,-1.8765 -1.68977,-1.6397 -1.63782,-4.6775 -0.0822,-5.4843 -1.19894,-1.5495 -1.4076,-4.5911 0.68964,-6.2796 2.03365,-1.6384 1.9354,-1.5963 5.88138,0.3033 -0.0289,-1.9585 -2.49093,-3.8302 -2.04644,-5.8466 0.39763,-1.8036 0.72433,-3.9294 4.25641,-3.5903 0.0982,-2.6104 8.20876,-1.2988 8.24988,1.4434 3.13954,0.3446 3.62904,1.5302 3.97667,3.8588 0.30457,2.0422 -0.35665,2.3564 -1.88283,5.1219 2.33893,-0.8186 3.11251,-1.8922 5.075,-1.2516 2.23817,0.7305 3.57313,3.0812 2.03548,5.0634 2.36206,0.7481 1.60657,4.6555 0.4856,6.262 -1.18209,1.693 -2.41302,3.3109 -4.27372,2.4152 -1.86075,-0.8957 -0.80709,-1.5552 -4.75424,-3.4554 -1.17661,-0.5665 -2.32669,1.6024 -4.28586,1.6315 -2.74189,0.041 -1.98739,-1.929 -3.1633,-1.9117 -0.65265,0.01 -1.97044,-0.1081 -2.34416,0.4273 -1.18211,1.6931 -1.13972,2.3682 -2.30358,3.1692 z"
+         style="fill:#197c36;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.07628px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129753"
+       transform="matrix(0.35856765,0,0,0.35856765,-225.99724,-620.36136)"
+       style="display:inline;stroke-width:0.787082">
+      <path
+         sodipodi:nodetypes="ccccccssscccccccccccccssccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-1"
+         d="m 678.56465,1792.0028 c 1.66655,-1.1646 2.00803,-0.7915 3.33812,-1.9506 0.65448,-1.5252 0.29212,-5.3001 0.55819,-10.6081 -0.36679,-2.2668 -2.6326,-1.0221 -4.34766,-3.5185 l 0.4735,-2.0236 4.15999,1.749 c 0.19922,-0.9601 0.40584,-1.9659 0.19165,-3.9226 -0.0804,-0.7345 -2.50634,-1.4672 -3.79925,-3.1756 -0.52034,-0.6876 -0.79756,-2.3454 -0.38893,-2.1782 2.8404,1.1623 4.2738,2.3322 4.27156,2.1608 0,0 0.19756,-5.7588 0.0463,-7.1565 -0.74164,-1.5307 -3.44428,-2.4974 -3.44428,-2.4974 l 0.22527,-2.1991 3.35973,1.458 0.17094,-6.8746 1.75863,-0.8452 0.0854,9.1312 3.56719,-2.9892 0.43647,1.8933 c 0,0 -3.13465,2.3986 -3.75985,3.9194 -0.15289,1.6206 0.0892,3.6394 0.0892,3.6394 l 4.09443,-2.7491 c 1.15645,0.172 0.56545,0.6012 0.77269,1.7403 0.15333,0.8427 -4.80562,3.4583 -4.74904,4.6223 0.17169,3.532 -0.0811,5.6572 0.43971,6.3445 2.54411,-2.8835 3.80778,-2.8156 5.30799,-2.7728 l 0.28457,2.381 c -3.00225,1.9446 -5.24554,1.1439 -5.17011,6.8249 0.26523,5.8164 -0.26689,4.4584 0.41068,7.5612 1.01892,0.7404 1.01446,0.362 3.38883,1.8371 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2"
+         d="m 688.56997,1775.5502 c -1.16103,-1.2164 -4.12374,0.2976 -3.31451,-2.2205 0.68757,-2.1408 5.46336,-1.9851 6.64355,-0.9342 1.75025,-1.202 2.34589,-1.1837 4.40722,-0.01 2.7471,1.8161 -0.21712,-0.1451 0.9636,3.6284 -3.06807,2.6152 -4.3468,-0.3074 -7.44743,0.8484 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0"
+         d="m 688.07978,1763.3742 c -1.56912,0.2586 -2.11987,1.4884 -1.31063,4.007 0.68756,2.1408 2.24882,-0.3655 3.596,-0.1001 1.87549,0.073 1.42744,2.547 3.48877,1.3689 1.99565,-3.3675 1.32753,-1.8764 1.5898,-3.9105 -3.06806,-2.6152 -2.55166,-0.4916 -5.65229,-1.6474 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2"
+         d="m 686.97769,1757.6923 c -1.28843,0.9465 -2.8127,1.2736 -2.02584,3.4286 0.66856,1.8317 2.93809,-0.3127 4.248,-0.086 1.82364,0.063 0.9705,0.769 2.97482,-0.2391 1.94047,-2.8813 2.29276,-1.6055 2.54778,-3.3459 -2.98321,-2.2376 -2.48109,-0.4206 -5.49598,-1.4096 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-6"
+         d="m 679.55205,1756.1329 c -2.23158,-0.4224 -2.38375,-0.1269 -3.44526,0.4669 -0.94948,0.5312 -1.9881,2.414 -0.67818,2.6407 1.82364,0.063 1.01225,2.5084 3.01657,1.5003 3.35401,1.5864 3.37819,1.5443 3.63321,-0.1961 -2.98322,-2.2377 1.94412,-3.1473 -1.07077,-4.1362 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccccc"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-9"
+         d="m 681.9813,1754.1004 c -0.78457,-0.1293 -0.29963,-2.0767 1.52817,-4.0542 -1.01101,-2.2125 -0.29775,-5.84 1.04942,-6.1053 1.8755,-0.073 -0.20069,3.0473 1.86062,4.2254 1.99565,3.3675 0.86832,2.7697 0.92186,5.6501 -3.06806,2.6152 -0.54779,0.4916 -3.64842,1.6474 0,0 -0.92709,-1.2341 -1.71165,-1.3634 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-2"
+         d="m 683.61677,1769.3273 c 3.46392,-1.3003 -1.58351,-3.6018 -3.90778,-4.9943 -1.1817,-0.708 -2.62455,-1.2329 -3.80474,-0.182 -1.75025,-1.202 -0.88472,-1.9358 -2.94605,-0.7577 -2.11718,3.2352 1.51129,0.043 0.33057,3.8165 3.06806,2.6153 0.4643,0.3977 3.56493,1.5535 3.281,2.0173 5.92812,0.8775 6.76307,0.564 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-5"
+         d="m 681.1319,1776.8658 c 1.40243,-0.8337 3.12362,-1.081 2.39606,-3.6311 -0.57073,-2.0003 -3.97313,-1.0035 -6.51831,-1.5922 -0.79006,-1.6722 -2.47112,-2.0299 -4.53245,-0.8517 -4.50049,6.0471 1.55304,0.701 0.37236,4.4745 3.06807,2.6152 3.67883,1.338 6.77946,2.4938 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.787076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129753-4"
+       transform="matrix(0.27860853,0,0,0.27860853,-158.01916,-478.66707)"
+       style="display:inline;stroke-width:1.01297">
+      <path
+         sodipodi:nodetypes="ccccccssscccccccccccccssccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-1-9"
+         d="m 678.56465,1792.0028 c 1.66655,-1.1646 2.00803,-0.7915 3.33812,-1.9506 0.65448,-1.5252 0.29212,-5.3001 0.55819,-10.6081 -0.36679,-2.2668 -2.6326,-1.0221 -4.34766,-3.5185 l 0.4735,-2.0236 4.15999,1.749 c 0.19922,-0.9601 0.40584,-1.9659 0.19165,-3.9226 -0.0804,-0.7345 -2.50634,-1.4672 -3.79925,-3.1756 -0.52034,-0.6876 -0.79756,-2.3454 -0.38893,-2.1782 2.8404,1.1623 4.2738,2.3322 4.27156,2.1608 0,0 0.19756,-5.7588 0.0463,-7.1565 -0.74164,-1.5307 -3.44428,-2.4974 -3.44428,-2.4974 l 0.22527,-2.1991 3.35973,1.458 0.17094,-6.8746 1.75863,-0.8452 0.0854,9.1312 3.56719,-2.9892 0.43647,1.8933 c 0,0 -3.13465,2.3986 -3.75985,3.9194 -0.15289,1.6206 0.0892,3.6394 0.0892,3.6394 l 4.09443,-2.7491 c 1.15645,0.172 0.56545,0.6012 0.77269,1.7403 0.15333,0.8427 -4.80562,3.4583 -4.74904,4.6223 0.17169,3.532 -0.0811,5.6572 0.43971,6.3445 2.54411,-2.8835 3.80778,-2.8156 5.30799,-2.7728 l 0.28457,2.381 c -3.00225,1.9446 -5.24554,1.1439 -5.17011,6.8249 0.26523,5.8164 -0.26689,4.4584 0.41068,7.5612 1.01892,0.7404 1.01446,0.362 3.38883,1.8371 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-9"
+         d="m 688.56997,1775.5502 c -1.16103,-1.2164 -4.12374,0.2976 -3.31451,-2.2205 0.68757,-2.1408 5.46336,-1.9851 6.64355,-0.9342 1.75025,-1.202 2.34589,-1.1837 4.40722,-0.01 2.7471,1.8161 -0.21712,-0.1451 0.9636,3.6284 -3.06807,2.6152 -4.3468,-0.3074 -7.44743,0.8484 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-3"
+         d="m 688.07978,1763.3742 c -1.56912,0.2586 -2.11987,1.4884 -1.31063,4.007 0.68756,2.1408 2.24882,-0.3655 3.596,-0.1001 1.87549,0.073 1.42744,2.547 3.48877,1.3689 1.99565,-3.3675 1.32753,-1.8764 1.5898,-3.9105 -3.06806,-2.6152 -2.55166,-0.4916 -5.65229,-1.6474 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-3"
+         d="m 686.97769,1757.6923 c -1.28843,0.9465 -2.8127,1.2736 -2.02584,3.4286 0.66856,1.8317 2.93809,-0.3127 4.248,-0.086 1.82364,0.063 0.9705,0.769 2.97482,-0.2391 1.94047,-2.8813 2.29276,-1.6055 2.54778,-3.3459 -2.98321,-2.2376 -2.48109,-0.4206 -5.49598,-1.4096 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-6-7"
+         d="m 679.55205,1756.1329 c -2.23158,-0.4224 -2.38375,-0.1269 -3.44526,0.4669 -0.94948,0.5312 -1.9881,2.414 -0.67818,2.6407 1.82364,0.063 1.01225,2.5084 3.01657,1.5003 3.35401,1.5864 3.37819,1.5443 3.63321,-0.1961 -2.98322,-2.2377 1.94412,-3.1473 -1.07077,-4.1362 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccccc"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-9-9"
+         d="m 681.9813,1754.1004 c -0.78457,-0.1293 -0.29963,-2.0767 1.52817,-4.0542 -1.01101,-2.2125 -0.29775,-5.84 1.04942,-6.1053 1.8755,-0.073 -0.20069,3.0473 1.86062,4.2254 1.99565,3.3675 0.86832,2.7697 0.92186,5.6501 -3.06806,2.6152 -0.54779,0.4916 -3.64842,1.6474 0,0 -0.92709,-1.2341 -1.71165,-1.3634 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-2-2"
+         d="m 683.61677,1769.3273 c 3.46392,-1.3003 -1.58351,-3.6018 -3.90778,-4.9943 -1.1817,-0.708 -2.62455,-1.2329 -3.80474,-0.182 -1.75025,-1.202 -0.88472,-1.9358 -2.94605,-0.7577 -2.11718,3.2352 1.51129,0.043 0.33057,3.8165 3.06806,2.6153 0.4643,0.3977 3.56493,1.5535 3.281,2.0173 5.92812,0.8775 6.76307,0.564 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-5-9"
+         d="m 681.1319,1776.8658 c 1.40243,-0.8337 3.12362,-1.081 2.39606,-3.6311 -0.57073,-2.0003 -3.97313,-1.0035 -6.51831,-1.5922 -0.79006,-1.6722 -2.47112,-2.0299 -4.53245,-0.8517 -4.50049,6.0471 1.55304,0.701 0.37236,4.4745 3.06807,2.6152 3.67883,1.338 6.77946,2.4938 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.01296px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <g
+       id="g129753-0"
+       transform="matrix(0.25241662,0,0,0.25241662,-134.23664,-433.09092)"
+       style="display:inline;stroke-width:1.11808">
+      <path
+         sodipodi:nodetypes="ccccccssscccccccccccccssccccccc"
+         inkscape:connector-curvature="0"
+         id="path7257-7-9-0-9-1-3"
+         d="m 678.56465,1792.0028 c 1.66655,-1.1646 2.00803,-0.7915 3.33812,-1.9506 0.65448,-1.5252 0.29212,-5.3001 0.55819,-10.6081 -0.36679,-2.2668 -2.6326,-1.0221 -4.34766,-3.5185 l 0.4735,-2.0236 4.15999,1.749 c 0.19922,-0.9601 0.40584,-1.9659 0.19165,-3.9226 -0.0804,-0.7345 -2.50634,-1.4672 -3.79925,-3.1756 -0.52034,-0.6876 -0.79756,-2.3454 -0.38893,-2.1782 2.8404,1.1623 4.2738,2.3322 4.27156,2.1608 0,0 0.19756,-5.7588 0.0463,-7.1565 -0.74164,-1.5307 -3.44428,-2.4974 -3.44428,-2.4974 l 0.22527,-2.1991 3.35973,1.458 0.17094,-6.8746 1.75863,-0.8452 0.0854,9.1312 3.56719,-2.9892 0.43647,1.8933 c 0,0 -3.13465,2.3986 -3.75985,3.9194 -0.15289,1.6206 0.0892,3.6394 0.0892,3.6394 l 4.09443,-2.7491 c 1.15645,0.172 0.56545,0.6012 0.77269,1.7403 0.15333,0.8427 -4.80562,3.4583 -4.74904,4.6223 0.17169,3.532 -0.0811,5.6572 0.43971,6.3445 2.54411,-2.8835 3.80778,-2.8156 5.30799,-2.7728 l 0.28457,2.381 c -3.00225,1.9446 -5.24554,1.1439 -5.17011,6.8249 0.26523,5.8164 -0.26689,4.4584 0.41068,7.5612 1.01892,0.7404 1.01446,0.362 3.38883,1.8371 z"
+         style="fill:#a06f47;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-3"
+         d="m 688.56997,1775.5502 c -1.16103,-1.2164 -4.12374,0.2976 -3.31451,-2.2205 0.68757,-2.1408 5.46336,-1.9851 6.64355,-0.9342 1.75025,-1.202 2.34589,-1.1837 4.40722,-0.01 2.7471,1.8161 -0.21712,-0.1451 0.9636,3.6284 -3.06807,2.6152 -4.3468,-0.3074 -7.44743,0.8484 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-4"
+         d="m 688.07978,1763.3742 c -1.56912,0.2586 -2.11987,1.4884 -1.31063,4.007 0.68756,2.1408 2.24882,-0.3655 3.596,-0.1001 1.87549,0.073 1.42744,2.547 3.48877,1.3689 1.99565,-3.3675 1.32753,-1.8764 1.5898,-3.9105 -3.06806,-2.6152 -2.55166,-0.4916 -5.65229,-1.6474 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-7"
+         d="m 686.97769,1757.6923 c -1.28843,0.9465 -2.8127,1.2736 -2.02584,3.4286 0.66856,1.8317 2.93809,-0.3127 4.248,-0.086 1.82364,0.063 0.9705,0.769 2.97482,-0.2391 1.94047,-2.8813 2.29276,-1.6055 2.54778,-3.3459 -2.98321,-2.2376 -2.48109,-0.4206 -5.49598,-1.4096 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-2-6-9"
+         d="m 679.55205,1756.1329 c -2.23158,-0.4224 -2.38375,-0.1269 -3.44526,0.4669 -0.94948,0.5312 -1.9881,2.414 -0.67818,2.6407 1.82364,0.063 1.01225,2.5084 3.01657,1.5003 3.35401,1.5864 3.37819,1.5443 3.63321,-0.1961 -2.98322,-2.2377 1.94412,-3.1473 -1.07077,-4.1362 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccccc"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-0-9-2"
+         d="m 681.9813,1754.1004 c -0.78457,-0.1293 -0.29963,-2.0767 1.52817,-4.0542 -1.01101,-2.2125 -0.29775,-5.84 1.04942,-6.1053 1.8755,-0.073 -0.20069,3.0473 1.86062,4.2254 1.99565,3.3675 0.86832,2.7697 0.92186,5.6501 -3.06806,2.6152 -0.54779,0.4916 -3.64842,1.6474 0,0 -0.92709,-1.2341 -1.71165,-1.3634 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-2-6"
+         d="m 683.61677,1769.3273 c 3.46392,-1.3003 -1.58351,-3.6018 -3.90778,-4.9943 -1.1817,-0.708 -2.62455,-1.2329 -3.80474,-0.182 -1.75025,-1.202 -0.88472,-1.9358 -2.94605,-0.7577 -2.11718,3.2352 1.51129,0.043 0.33057,3.8165 3.06806,2.6153 0.4643,0.3977 3.56493,1.5535 3.281,2.0173 5.92812,0.8775 6.76307,0.564 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ssccccs"
+         inkscape:connector-curvature="0"
+         id="path7259-9-9-6-6-3-2-5-0"
+         d="m 681.1319,1776.8658 c 1.40243,-0.8337 3.12362,-1.081 2.39606,-3.6311 -0.57073,-2.0003 -3.97313,-1.0035 -6.51831,-1.5922 -0.79006,-1.6722 -2.47112,-2.0299 -4.53245,-0.8517 -4.50049,6.0471 1.55304,0.701 0.37236,4.4745 3.06807,2.6152 3.67883,1.338 6.77946,2.4938 z"
+         style="display:inline;fill:#25a348;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.11807px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="3.3539546"
+       y="25.751534"
+       id="text319676-5-3"><tspan
+         sodipodi:role="line"
+         id="tspan319674-9-9"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="3.3539546"
+         y="25.751534">ID:</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="9.6045055"
+       y="25.772892"
+       id="text319676-9"><tspan
+         sodipodi:role="line"
+         id="tspan319674-4"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="9.6045055"
+         y="25.772892">0</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="18.153103"
+       y="25.897606"
+       id="text319676-6-4"><tspan
+         sodipodi:role="line"
+         id="tspan319674-6-6"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="18.153103"
+         y="25.897606">1</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="25.121853"
+       y="25.917133"
+       id="text319676-1-4"><tspan
+         sodipodi:role="line"
+         id="tspan319674-0-6"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="25.121853"
+         y="25.917133">2</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="31.471859"
+       y="25.917133"
+       id="text319676-1-4-7"><tspan
+         sodipodi:role="line"
+         id="tspan319674-0-6-3"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="31.471859"
+         y="25.917133">3</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="37.493141"
+       y="25.951996"
+       id="text319676-1-4-5"><tspan
+         sodipodi:role="line"
+         id="tspan319674-0-6-6"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="37.493141"
+         y="25.951996">4</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="45.511566"
+       y="25.862358"
+       id="text319676-1-4-1"><tspan
+         sodipodi:role="line"
+         id="tspan319674-0-6-2"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="45.511566"
+         y="25.862358">5</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+       x="53.515045"
+       y="25.005753"
+       id="text319676-1-4-1-2"><tspan
+         sodipodi:role="line"
+         id="tspan319674-0-6-2-3"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+         x="53.515045"
+         y="25.005753">...</tspan></text>
+    <g
+       id="g58241"
+       transform="translate(5.08,-0.63500358)">
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-6"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="13.97"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-1-0-5"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="10.159999"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-7"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="6.3500009"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-4-6"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="13.97"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-1-0-4-7"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="10.159999"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-6-6"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="6.3500009"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367188"
+         y="4.9249711"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-9-24"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-5-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367188"
+           y="4.9249711" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367188"
+           y="8.9458885"
+           id="tspan110831-0-6-1-6-6-8-4-54-9-8">0</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367188"
+         y="8.7350035"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-0-9-8-2"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-8-8-9-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367188"
+           y="8.7350035" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367188"
+           y="12.75592"
+           id="tspan110831-0-6-1-6-6-8-4-4-9-0-4">1</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367203"
+         y="12.544971"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-8-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-0-6"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367203"
+           y="12.544971" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367203"
+           y="16.565889"
+           id="tspan112808-0-2-8-9">2</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="66.367203"
+           y="19.085136"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-9-0" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-62-8"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="2.5399997"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-6-7-1"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="2.5399997"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="65.605194"
+         y="1.11497"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-9-2-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-5-8-5"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="65.605194"
+           y="1.11497" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="65.605194"
+           y="5.1358871"
+           id="tspan110831-0-6-1-6-6-8-4-54-9-3-1">ID</tspan></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="13.97"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="10.159999"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="6.3500009"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="69.927841"
+         y="4.8839402"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-73-4-8"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-8-0-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="69.927841"
+           y="4.8839402" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="69.927841"
+           y="8.9048576"
+           id="tspan110831-0-6-1-6-6-8-4-2-3-3-8">&quot;Oak&quot;</tspan></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-6-7-9"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="2.5399997"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="70.784225"
+         y="1.0739391"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-73-5-5-8"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-8-3-2-6"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="70.784225"
+           y="1.0739391" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="70.784225"
+           y="5.0948563"
+           id="tspan110831-0-6-1-6-6-8-4-2-3-8-6-4">species</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="70.181923"
+         y="8.6940069"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-3-3-9-8"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-0-3-0-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="70.181923"
+           y="8.6940069" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="70.181923"
+           y="12.714924"
+           id="tspan110831-0-6-1-6-6-8-4-2-2-9-5-1">&quot;Pine&quot;</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="70.181953"
+         y="12.504043"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-6-5-8-1"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-07-7-0-9"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="70.181953"
+           y="12.504043" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="70.181953"
+           y="16.52496"
+           id="tspan112808-1-2-8-9-9">&quot;Maple&quot;</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="70.181953"
+           y="19.044207"
+           id="tspan110831-0-6-1-6-6-8-4-2-7-7-2-4" /></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="87.903732"
+         y="1.068774"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-3-6"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-8-0"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="87.903732"
+           y="1.068774" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="87.903732"
+           y="5.0896912"
+           id="tspan110831-0-6-1-6-6-8-4-54-3-1">age</tspan></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-8"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="13.97"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8-2"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="10.159999"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1-9"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="6.3500009"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-6-7-9-0"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="2.5399997"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="101.26117"
+         y="1.2779491"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-3-6-0"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-8-0-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="101.26117"
+           y="1.2779491" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="101.26117"
+           y="5.2988663"
+           id="tspan110831-0-6-1-6-6-8-4-54-3-1-0">height</tspan></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-8-4"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="13.97"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8-2-0"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="10.159999"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1-9-3"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="6.3500009"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.508;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-6-7-9-0-0"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="2.5399997"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="115.17639"
+         y="1.3476741"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-3-6-0-7"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-8-0-7-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="115.17639"
+           y="1.3476741" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="115.17639"
+           y="5.3685913"
+           id="tspan110831-0-6-1-6-6-8-4-54-3-1-0-5">diameter</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="5.076323"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="5.076323" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="9.0972404"
+           id="tspan110831-0-6-1-6-6-8-4-54-6"> 8.1</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="5.2014565"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="5.2014565" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="9.222374"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="9.0114584"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="9.0114584" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="13.032375"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-3">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="12.821456"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-1"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-9"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="12.821456" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="16.842373"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-6">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.872223"
+         y="5.2014565"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.872223"
+           y="5.2014565" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.872223"
+           y="9.222374"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2">19</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.872223"
+         y="9.096137"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2-3"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.872223"
+           y="9.096137" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.872223"
+           y="13.117054"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2-9">34</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.941948"
+         y="12.891181"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2-19"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.941948"
+           y="12.891181" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.941948"
+           y="16.912098"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2-95">22</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="8.8863554"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-0-9-86"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-8-8-3"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="8.8863554" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="12.907272"
+           id="tspan110831-0-6-1-6-6-8-4-4-9-1">11.7</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="12.696322"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-9"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="12.696322" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="16.717239"
+           id="tspan112808-0-2-7"> 9.3</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="19.236486"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-2" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-6-1"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="25.400007"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-1-0-5-6"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="21.59001"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-7-2"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="17.780012"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-4-6-6"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="25.400007"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-1-0-4-7-6"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="21.59001"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-5-8-6-6-9"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="17.780012"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367188"
+         y="16.354982"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-9-24-0"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-5-7-8"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367188"
+           y="16.354982" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367188"
+           y="20.3759"
+           id="tspan110831-0-6-1-6-6-8-4-54-9-8-3">3</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367188"
+         y="20.165014"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-0-9-8-2-1"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-8-8-9-1-5"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367188"
+           y="20.165014" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367188"
+           y="24.185932"
+           id="tspan110831-0-6-1-6-6-8-4-4-9-0-4-8">4</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="66.367203"
+         y="23.974981"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-8-5-0"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-0-6-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="66.367203"
+           y="23.974981" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="66.367203"
+           y="27.995899"
+           id="tspan112808-0-2-8-9-4">5</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="66.367203"
+           y="30.515146"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-9-0-7" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-3"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="25.400007"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8-8"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="21.59001"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1-4"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="17.780012"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="69.927841"
+         y="16.313951"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-73-4-8-7"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-8-0-1-1"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="69.927841"
+           y="16.313951" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="69.927841"
+           y="20.334869"
+           id="tspan110831-0-6-1-6-6-8-4-2-3-3-8-6">&quot;Pine&quot;</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="70.181923"
+         y="20.124018"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-3-3-9-8-1"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-0-3-0-2-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="70.181923"
+           y="20.124018" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="70.181923"
+           y="24.144936"
+           id="tspan110831-0-6-1-6-6-8-4-2-2-9-5-1-2">&quot;Pine&quot;</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="70.181953"
+         y="23.93405"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-6-5-8-1-0"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-07-7-0-9-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="70.181953"
+           y="23.93405" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="70.181953"
+           y="27.954967"
+           id="tspan112808-1-2-8-9-9-0">&quot;Oak&quot;</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="70.181953"
+           y="30.474215"
+           id="tspan110831-0-6-1-6-6-8-4-2-7-7-2-4-4" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-6-3-4"
+         width="13.970005"
+         height="3.8099923"
+         x="69.849998"
+         y="29.209993"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="72.20919"
+         y="27.784967"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-9-7-1"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-1-3-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="72.20919"
+           y="27.784967" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="72.20919"
+           y="31.805885"
+           id="tspan112808-0-2-7-4-5">...</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="72.20919"
+           y="34.32513"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-2-5-5" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-8-0-3-9-4-6-1-6"
+         width="5.0799999"
+         height="3.8099923"
+         x="64.770004"
+         y="29.209993"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="64.843193"
+         y="27.784967"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-8-5-8-9"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-0-6-9-8"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="64.843193"
+           y="27.784967" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="64.843193"
+           y="31.805885"
+           id="tspan112808-0-2-8-9-5-8">...</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="64.843193"
+           y="34.32513"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-9-0-5-9" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-7-9"
+         width="15.240007"
+         height="3.8099923"
+         x="83.820007"
+         y="29.209993"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.60759"
+         y="27.744043"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-6-5-8-1-9-0"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-07-7-0-9-5-8"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.60759"
+           y="27.744043" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.60759"
+           y="31.764961"
+           id="tspan112808-1-2-8-9-9-5-7">...</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="88.60759"
+           y="34.284206"
+           id="tspan110831-0-6-1-6-6-8-4-2-7-7-2-4-8-4" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-8-7"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="25.400007"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8-2-4"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="21.59001"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1-9-2"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="17.780012"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-7-2-10"
+         width="15.240007"
+         height="3.8099923"
+         x="99.060005"
+         y="29.209993"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.8476"
+         y="27.744043"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-8-6-5-8-1-9-1-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-2-07-7-0-9-5-2-4"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.8476"
+           y="27.744043" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.8476"
+           y="31.764961"
+           id="tspan112808-1-2-8-9-9-5-9-1">...</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="103.8476"
+           y="34.284206"
+           id="tspan110831-0-6-1-6-6-8-4-2-7-7-2-4-8-9-4" /></text>
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-8-4-6"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="25.400007"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-6-4-0-8-2-0-8"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="21.59001"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-89-5-2-1-9-3-8"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="17.780012"
+         rx="0"
+         ry="0" />
+      <rect
+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.254;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         id="rect6030-0-3-62-5-7-0-0-3-1-7-2-2-7-2-1-6"
+         width="15.240007"
+         height="3.8099923"
+         x="114.3"
+         y="29.209993"
+         rx="0"
+         ry="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="16.506334"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-9"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-22"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="16.506334" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="20.527252"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-4"> 7.4</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="16.631468"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-7"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-5"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="16.631468" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="20.652386"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-9">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="20.441469"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-5-3"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-7-5"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="20.441469" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="24.462387"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-3-9">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="24.251463"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-1-5"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-9-7"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="24.251463" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="28.272381"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-6-6">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="121.01065"
+         y="28.061457"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-5-8-7"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-0-6-3"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="121.01065"
+           y="28.061457" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="121.01065"
+           y="32.082375"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-6-5-6">-</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.872223"
+         y="16.631468"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2-59"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2-9"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.872223"
+           y="16.631468" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.872223"
+           y="20.652386"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2-1">38</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.872223"
+         y="20.526148"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2-3-6"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2-1-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.872223"
+           y="20.526148" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.872223"
+           y="24.547066"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2-9-4">29</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="88.941948"
+         y="24.321188"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-4-6-2-5-6"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-3-4-2-19-2"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="88.941948"
+           y="24.321188" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="88.941948"
+           y="28.342106"
+           id="tspan110831-0-6-1-6-6-8-4-54-6-2-95-6">43</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="20.316366"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-0-9-86-9"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-8-8-3-9"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="20.316366" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="24.337284"
+           id="tspan110831-0-6-1-6-6-8-4-4-9-1-8"> 9.1</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-size:5.64444px;line-height:0;font-family:sans-serif;-inkscape-font-specification:sans-serif;display:inline;stroke-width:0.264583"
+         x="103.13609"
+         y="24.126333"
+         id="text32207-6-1-6-2-3-48-5-7-6-44-5-35-8-5-9-9-4"><tspan
+           sodipodi:role="line"
+           id="tspan32205-7-9-0-2-2-70-8-9-6-89-8-27-5-9-0-1-4"
+           style="font-size:4.23333px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="24.126333" /><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.264583"
+           x="103.13609"
+           y="28.147251"
+           id="tspan112808-0-2-7-7">13.3</tspan><tspan
+           sodipodi:role="line"
+           style="font-size:2.82222px;line-height:1.25;stroke-width:0.264583"
+           x="103.13609"
+           y="30.666498"
+           id="tspan110831-0-6-1-6-6-8-4-49-7-2-9" /></text>
+    </g>
+    <path
+       style="fill:none;stroke:#0000ff;stroke-width:1.27;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Send)"
+       d="m 48.26,26.03499 c 0,0 5.44446,2.250512 8.89,2.54 3.44554,0.289488 12.446,-1.778 12.446,-1.778"
+       id="path64881"
+       sodipodi:nodetypes="czc" />
+  </g>
+</svg>

BIN
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-texture.png


File diff suppressed because it is too large
+ 1852 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/figures/property-texture.svg


+ 26 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/EXT_structural_metadata.schema.json

@@ -0,0 +1,26 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "EXT_structural_metadata.schema.json",
+    "title": "EXT_structural_metadata glTF extension",
+    "type": "object",
+    "description": "Structural metadata about a glTF element.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "propertyTable": {
+            "type": "integer",
+            "minimum": 0,
+            "description": "The index of the property table containing per-feature property values."
+        },
+        "index": {
+            "type": "integer",
+            "minimum": 0,
+            "description": "The feature index (row index) used for looking up property values for this element."
+        },
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 182 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/class.property.schema.json

@@ -0,0 +1,182 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "class.property.schema.json",
+    "title": "Class Property in EXT_structural_metadata",
+    "type": "object",
+    "description": "A class property.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the property, e.g. for display purposes."
+        },
+        "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The description of the property."
+        },
+        "type": {
+            "description": "The element type.",
+            "anyOf": [
+                {
+                    "const": "SCALAR"
+                },
+                {
+                    "const": "VEC2"
+                },
+                {
+                    "const": "VEC3"
+                },
+                {
+                    "const": "VEC4"
+                },
+                {
+                    "const": "MAT2"
+                },
+                {
+                    "const": "MAT3"
+                },
+                {
+                    "const": "MAT4"
+                },
+                {
+                    "const": "STRING"
+                },
+                {
+                    "const": "BOOLEAN"
+                },
+                {
+                    "const": "ENUM"
+                },
+                {
+                    "type": "string"
+                }
+            ]
+        },
+        "componentType": {
+            "description": "The datatype of the element's components. Only applicable to `SCALAR`, `VECN`, and `MATN` types.",
+            "anyOf": [
+                {
+                    "const": "INT8"
+                },
+                {
+                    "const": "UINT8"
+                },
+                {
+                    "const": "INT16"
+                },
+                {
+                    "const": "UINT16"
+                },
+                {
+                    "const": "INT32"
+                },
+                {
+                    "const": "UINT32"
+                },
+                {
+                    "const": "INT64"
+                },
+                {
+                    "const": "UINT64"
+                },
+                {
+                    "const": "FLOAT32"
+                },
+                {
+                    "const": "FLOAT64"
+                },
+                {
+                    "type": "string"
+                }
+            ]
+        },
+        "enumType": {
+            "type": "string",
+            "description": "Enum ID as declared in the `enums` dictionary. Required when `type` is `ENUM`."
+        },
+        "array": {
+            "type": "boolean",
+            "default": false,
+            "description": "Whether the property is an array. When `count` is defined the property is a fixed-length array. Otherwise the property is a variable-length array."
+        },
+        "count": {
+            "type": "integer",
+            "minimum": 2,
+            "description": "The number of array elements. May only be defined when `array` is true."
+        },
+        "normalized": {
+            "type": "boolean",
+            "description": "Specifies whether integer values are normalized. Only applicable to `SCALAR`, `VECN`, and `MATN` types with integer component types. For unsigned integer component types, values are normalized between `[0.0, 1.0]`. For signed integer component types, values are normalized between `[-1.0, 1.0]`. For all other component types, this property must be false.",
+            "default": false
+        },
+        "offset": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "An offset to apply to property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`."
+        },
+        "scale": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "A scale to apply to property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`."
+        },
+        "max": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Maximum allowed value for the property. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the maximum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "min": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Minimum allowed value for the property. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the minimum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "required": {
+            "type": "boolean",
+            "description": "If required, the property must be present in every entity conforming to the class. If not required, individual entities may include `noData` values, or the entire property may be omitted. As a result, `noData` has no effect on a required property. Client implementations may use required properties to make performance optimizations.",
+            "default": false
+        },
+        "noData": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/noDataValue"
+                }
+            ],
+            "description": "A `noData` value represents missing data — also known as a sentinel value — wherever it appears. `BOOLEAN` properties may not specify `noData` values. This is given as the plain property value, without the transforms from the `normalized`, `offset`, and `scale` properties. Must not be defined if `required` is true."
+        },
+        "default": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/anyValue"
+                }
+            ],
+            "description": "A default value to use when encountering a `noData` value or an omitted property. The value is given in its final form, taking the effect of `normalized`, `offset`, and `scale` properties into account. Must not be defined if `required` is true."
+        },
+        "semantic": {
+            "type": "string",
+            "minLength": 1,
+            "description": "An identifier that describes how this property should be interpreted. The semantic cannot be used by other properties in the class."
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "type"
+    ]
+}

+ 34 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/class.schema.json

@@ -0,0 +1,34 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "class.schema.json",
+    "title": "Class in EXT_structural_metadata",
+    "type": "object",
+    "description": "A class containing a set of properties.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the class, e.g. for display purposes."
+        },
+        "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The description of the class."
+        },
+        "properties": {
+            "type": "object",
+            "description": "A dictionary, where each key is a property ID and each value is an object defining the property. Property IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "class.property.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 119 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/definitions.schema.json

@@ -0,0 +1,119 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "definitions.schema.json",
+    "title": "Definitions",
+    "description": "Common definitions used in schema files.",
+    "definitions": {
+        "numericValue": {
+            "title": "Numeric Value",
+            "oneOf": [
+                {
+                    "type": "number"
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "number"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "array",
+                        "items": {
+                            "type": "number"
+                        },
+                        "minItems": 1
+                    },
+                    "minItems": 1
+                }
+            ],
+            "description": "For `SCALAR` this is a number. For `VECN` this is an array of `N` numbers. For `MATN` this is an array of `N²` numbers. For fixed-length arrays this is an array of `count` elements of the given `type`."
+        },
+        "noDataValue": {
+            "title": "No Data Value",
+            "oneOf": [
+                {
+                    "type": "number"
+                },
+                {
+                    "type": "string"
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "number"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "array",
+                        "items": {
+                            "type": "number"
+                        },
+                        "minItems": 1
+                    },
+                    "minItems": 1
+                }
+            ],
+            "description": "For `SCALAR` this is a number. For `STRING` this is a string. For `ENUM` this is a string that must be a valid enum `name`, not an integer value. For `VECN` this is an array of `N` numbers. For `MATN` this is an array of `N²` numbers. For fixed-length arrays this is an array of `count` elements of the given `type`."
+        },
+        "anyValue": {
+            "title": "Any Value",
+            "oneOf": [
+                {
+                    "type": "number"
+                },
+                {
+                    "type": "string"
+                },
+                {
+                    "type": "boolean"
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "number"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "boolean"
+                    },
+                    "minItems": 1
+                },
+                {
+                    "type": "array",
+                    "items": {
+                        "type": "array",
+                        "items": {
+                            "type": "number"
+                        },
+                        "minItems": 1
+                    },
+                    "minItems": 1
+                }
+            ],
+            "description": "For `SCALAR` this is a number. For `STRING` this is a string. For `ENUM` this is a string that must be a valid enum `name`, not an integer value. For `BOOLEAN` this is a boolean. For `VECN` this is an array of `N` numbers. For `MATN` this is an array of `N²` numbers. For fixed-length array this is an array of `count` elements of the given `type`. For variable-length arrays this is an array of any length of the given `type`."
+        }
+    }
+}

+ 70 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/enum.schema.json

@@ -0,0 +1,70 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "enum.schema.json",
+    "title": "Enum in EXT_structural_metadata",
+    "type": "object",
+    "description": "An object defining the values of an enum.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the enum, e.g. for display purposes."
+        },
+        "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The description of the enum."
+        },
+        "valueType": {
+            "default": "UINT16",
+            "description": "The type of the integer enum value.",
+            "anyOf": [
+                {
+                    "const": "INT8"
+                },
+                {
+                    "const": "UINT8"
+                },
+                {
+                    "const": "INT16"
+                },
+                {
+                    "const": "UINT16"
+                },
+                {
+                    "const": "INT32"
+                },
+                {
+                    "const": "UINT32"
+                },
+                {
+                    "const": "INT64"
+                },
+                {
+                    "const": "UINT64"
+                },
+                {
+                    "type": "string"
+                }
+            ]
+        },
+        "values": {
+            "type": "array",
+            "description": "An array of enum values. Duplicate names or duplicate integer values are not allowed.",
+            "items": {
+                "$ref": "enum.value.schema.json"
+            },
+            "minItems": 1
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "values"
+    ]
+}

+ 34 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/enum.value.schema.json

@@ -0,0 +1,34 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "enum.value.schema.json",
+    "title": "Enum Value in EXT_structural_metadata",
+    "type": "object",
+    "description": "An enum value.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the enum value."
+        },
+        "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The description of the enum value."
+        },
+        "value": {
+            "type": "integer",
+            "description": "The integer enum value."
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "name",
+        "value"
+    ]
+}

+ 53 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/glTF.EXT_structural_metadata.schema.json

@@ -0,0 +1,53 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "glTF.EXT_structural_metadata.schema.json",
+    "title": "EXT_structural_metadata glTF extension",
+    "type": "object",
+    "description": "glTF extension that provides structural metadata about vertices, texels, and features in a glTF asset.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "schema": {
+            "allOf": [
+                {
+                    "$ref": "schema.schema.json"
+                }
+            ],
+            "description": "An object defining classes and enums."
+        },
+        "schemaUri": {
+            "type": "string",
+            "description": "The URI (or IRI) of the external schema file.",
+            "format": "iri-reference"
+        },
+        "propertyTables": {
+            "type": "array",
+            "description": "An array of property table definitions, which may be referenced by index.",
+            "minItems": 1,
+            "items": {
+                "$ref": "propertyTable.schema.json"
+            }
+        },
+        "propertyTextures": {
+            "type": "array",
+            "description": "An array of property texture definitions, which may be referenced by index.",
+            "minItems": 1,
+            "items": {
+                "$ref": "propertyTexture.schema.json"
+            }
+        },
+        "propertyAttributes": {
+            "type": "array",
+            "description": "An array of property attribute definitions, which may be referenced by index.",
+            "minItems": 1,
+            "items": {
+                "$ref": "propertyAttribute.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 32 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/mesh.primitive.EXT_structural_metadata.schema.json

@@ -0,0 +1,32 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "mesh.primitive.EXT_structural_metadata.schema.json",
+    "title": "EXT_structural_metadata glTF Mesh Primitive extension",
+    "type": "object",
+    "description": "Structural metadata about a glTF primitive.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "propertyTextures": {
+            "type": "array",
+            "description": "An array of indexes of property textures in the root `EXT_structural_metadata` object.",
+            "minItems": 1,
+            "items": {
+                "type": "integer"
+            }
+        },
+        "propertyAttributes": {
+            "type": "array",
+            "description": "An array of indexes of property attributes in the root `EXT_structural_metadata` object.",
+            "minItems": 1,
+            "items": {
+                "type": "integer"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 55 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyAttribute.property.schema.json

@@ -0,0 +1,55 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyAttribute.property.schema.json",
+    "title": "Property Attribute Property in EXT_structural_metadata",
+    "type": "object",
+    "description": "An attribute containing property values.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "attribute": {
+            "type": "string",
+            "description": "The name of the attribute containing property values."
+        },
+        "offset": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "An offset to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `offset` if both are defined."
+        },
+        "scale": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "A scale to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `scale` if both are defined."
+        },
+        "max": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Maximum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the maximum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "min": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Minimum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the minimum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "attribute"
+    ]
+}

+ 36 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyAttribute.schema.json

@@ -0,0 +1,36 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyAttribute.schema.json",
+    "title": "Property Attribute in EXT_structural_metadata",
+    "type": "object",
+    "description": "Properties conforming to a class, organized as property values stored in attributes.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the property attribute, e.g. for display purposes."
+        },
+        "class": {
+            "type": "string",
+            "description": "The class that property values conform to. The value must be a class ID declared in the `classes` dictionary."
+        },
+        "properties": {
+            "type": "object",
+            "description": "A dictionary, where each key corresponds to a property ID in the class' `properties` dictionary and each value is an object describing where property values are stored. Required properties must be included in this dictionary.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "propertyAttribute.property.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "class"
+    ]
+}

+ 117 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTable.property.schema.json

@@ -0,0 +1,117 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyTable.property.schema.json",
+    "title": "Property Table Property in EXT_structural_metadata",
+    "type": "object",
+    "description": "An array of binary property values.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "values": {
+            "allOf": [
+                {
+                    "$ref": "glTFid.schema.json"
+                }
+            ],
+            "description": "The index of the buffer view containing property values. The data type of property values is determined by the property definition: When `type` is `BOOLEAN` values are packed into a bitstream. When `type` is `STRING` values are stored as byte sequences and decoded as UTF-8 strings. When `type` is `SCALAR`, `VECN`, or `MATN` the values are stored as the provided `componentType` and the buffer view `byteOffset` must be aligned to a multiple of the `componentType` size. When `type` is `ENUM` values are stored as the enum's `valueType` and the buffer view `byteOffset` must be aligned to a multiple of the `valueType` size. Each enum value in the array must match one of the allowed values in the enum definition. `arrayOffsets` is required for variable-length arrays and `stringOffsets` is required for strings (for variable-length arrays of strings, both are required)."
+        },
+        "arrayOffsets": {
+            "allOf": [
+                {
+                    "$ref": "glTFid.schema.json"
+                }
+            ],
+            "description": "The index of the buffer view containing offsets for variable-length arrays. The number of offsets is equal to the property table `count` plus one. The offsets represent the start positions of each array, with the last offset representing the position after the last array. The array length is computed using the difference between the subsequent offset and the current offset. If `type` is `STRING` the offsets index into the string offsets array (stored in `stringOffsets`), otherwise they index into the property array (stored in `values`). The data type of these offsets is determined by `arrayOffsetType`. The buffer view `byteOffset` must be aligned to a multiple of the `arrayOffsetType` size."
+        },
+        "stringOffsets": {
+            "allOf": [
+                {
+                    "$ref": "glTFid.schema.json"
+                }
+            ],
+            "description": "The index of the buffer view containing offsets for strings. The number of offsets is equal to the number of string elements plus one. The offsets represent the byte offsets of each string in the property array (stored in `values`), with the last offset representing the byte offset after the last string. The string byte length is computed using the difference between the subsequent offset and the current offset. The data type of these offsets is determined by `stringOffsetType`. The buffer view `byteOffset` must be aligned to a multiple of the `stringOffsetType` size."
+        },
+        "arrayOffsetType": {
+            "description": "The type of values in `arrayOffsets`.",
+            "default": "UINT32",
+            "anyOf": [
+                {
+                    "const": "UINT8"
+                },
+                {
+                    "const": "UINT16"
+                },
+                {
+                    "const": "UINT32"
+                },
+                {
+                    "const": "UINT64"
+                },
+                {
+                    "type": "string"
+                }
+            ]
+        },
+        "stringOffsetType": {
+            "description": "The type of values in `stringOffsets`.",
+            "default": "UINT32",
+            "anyOf": [
+                {
+                    "const": "UINT8"
+                },
+                {
+                    "const": "UINT16"
+                },
+                {
+                    "const": "UINT32"
+                },
+                {
+                    "const": "UINT64"
+                },
+                {
+                    "type": "string"
+                }
+            ]
+        },
+        "offset": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "An offset to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `offset` if both are defined."
+        },
+        "scale": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "A scale to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `scale` if both are defined."
+        },
+        "max": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Maximum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the maximum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "min": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Minimum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the minimum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "values"
+    ]
+}

+ 42 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTable.schema.json

@@ -0,0 +1,42 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyTable.schema.json",
+    "title": "Property Table in EXT_structural_metadata",
+    "type": "object",
+    "description": "Properties conforming to a class, organized as property values stored in binary columnar arrays.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the property table, e.g. for display purposes."
+        },
+        "class": {
+            "type": "string",
+            "description": "The class that property values conform to. The value must be a class ID declared in the `classes` dictionary."
+        },
+        "count": {
+            "type": "integer",
+            "minimum": 1,
+            "description": "The number of elements in each property array."
+        },
+        "properties": {
+            "type": "object",
+            "description": "A dictionary, where each key corresponds to a property ID in the class' `properties` dictionary and each value is an object describing where property values are stored. Required properties must be included in this dictionary.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "propertyTable.property.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "class",
+        "count"
+    ]
+}

+ 62 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTexture.property.schema.json

@@ -0,0 +1,62 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyTexture.schema.json",
+    "title": "Property Texture Property in EXT_structural_metadata",
+    "type": "object",
+    "description": "A texture containing property values.",
+    "allOf": [
+        {
+            "$ref": "textureInfo.schema.json"
+        }
+    ],
+    "properties": {
+        "channels": {
+            "type": "array",
+            "items": {
+                "type": "integer",
+                "minimum": 0
+            },
+            "minItems": 1,
+            "description": "Texture channels containing property values, identified by index. The values may be packed into multiple channels if a single channel does not have sufficient bit depth. The values are packed in little-endian order.",
+            "default": [
+                0
+            ]
+        },
+        "offset": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "An offset to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `offset` if both are defined."
+        },
+        "scale": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "A scale to apply to property values. Only applicable when the component type is `FLOAT32` or `FLOAT64`, or when the property is `normalized`. Overrides the class property's `scale` if both are defined."
+        },
+        "max": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Maximum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the maximum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "min": {
+            "allOf": [
+                {
+                    "$ref": "definitions.schema.json#/definitions/numericValue"
+                }
+            ],
+            "description": "Minimum value present in the property values. Only applicable to `SCALAR`, `VECN`, and `MATN` types. This is the minimum of all property values, after the transforms based on the `normalized`, `offset`, and `scale` properties have been applied."
+        },
+        "index": {},
+        "texCoord": {},
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 36 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/propertyTexture.schema.json

@@ -0,0 +1,36 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "propertyTexture.schema.json",
+    "title": "Property Texture in EXT_structural_metadata",
+    "type": "object",
+    "description": "Properties conforming to a class, organized as property values stored in textures.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the property texture, e.g. for display purposes."
+        },
+        "class": {
+            "type": "string",
+            "description": "The class that property values conform to. The value must be a class ID declared in the `classes` dictionary."
+        },
+        "properties": {
+            "type": "object",
+            "description": "A dictionary, where each key corresponds to a property ID in the class' `properties` dictionary and each value is an object describing where property values are stored. Required properties must be included in this dictionary.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "propertyTexture.property.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "class"
+    ]
+}

+ 55 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_structural_metadata/schema/schema.schema.json

@@ -0,0 +1,55 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "schema.schema.json",
+    "title": "Schema in EXT_structural_metadata",
+    "type": "object",
+    "description": "An object defining classes and enums.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "id": {
+            "type": "string",
+            "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$",
+            "description": "Unique identifier for the schema. Schema IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`."
+        },
+        "name": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The name of the schema, e.g. for display purposes."
+        },
+        "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The description of the schema."
+        },
+        "version": {
+            "type": "string",
+            "minLength": 1,
+            "description": "Application-specific version of the schema."
+        },
+        "classes": {
+            "type": "object",
+            "description": "A dictionary, where each key is a class ID and each value is an object defining the class. Class IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "class.schema.json"
+            }
+        },
+        "enums": {
+            "type": "object",
+            "description": "A dictionary, where each key is an enum ID and each value is an object defining the values for the enum. Enum IDs must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.",
+            "minProperties": 1,
+            "additionalProperties": {
+                "$ref": "enum.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "id"
+    ]
+}

+ 1 - 28
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -13,33 +13,6 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <Folder Include="Schemas\" />
     <Folder Include="Schemas\" />
+	<None Update="Schemas\**\*" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
   </ItemGroup>
-
-  <ItemGroup>
-    <None Update="Schemas\EXT_mesh_features\schema\featureId.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Update="Schemas\EXT_mesh_features\schema\featureIdAttribute.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Update="Schemas\EXT_mesh_features\schema\featureIdTexture.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Update="Schemas\EXT_mesh_features\schema\mesh.primitive.EXT_mesh_features.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-  </ItemGroup>
-
-  <ItemGroup>
-    <None Update="Schemas\EXT_instance_features\schema\featureId.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Update="Schemas\EXT_instance_features\schema\featureIdAttribute.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Update="Schemas\EXT_instance_features\schema\node.EXT_instance_features.schema.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-  </ItemGroup>
-
 </Project>
 </Project>

+ 213 - 0
src/SharpGLTF.Cesium/Memory/BinaryTable.cs

@@ -0,0 +1,213 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace SharpGLTF.Memory
+{
+    /// <summary>
+    /// Function for converting data into binary buffers
+    /// Specs see https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#binary-table-format
+    /// </summary>
+    public static class BinaryTable
+    {
+        public static List<byte> GetBytesForArray<T>(List<List<T>> values)
+        {
+            var bytes = new List<byte>();
+            foreach (var value in values)
+            {
+                var b = GetBytes(value);
+                bytes.AddRange(b);
+            }
+
+            return bytes;
+        }
+
+
+        /// <summary>
+        /// Converts a list of primitive types into a byte array
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="values"></param>
+        /// <returns>byte array</returns>
+        public static byte[] GetBytes<T>(IReadOnlyList<T> values)
+        {
+            Guard.IsTrue(values.Count > 0, nameof(values), "values must have at least one element");
+
+            if (typeof(T) == typeof(string))
+            {
+                var res = string.Join("", values);
+                return Encoding.UTF8.GetBytes(res);
+            }
+            else if (typeof(T) == typeof(Vector2))
+            {
+                return Vector2ToBytes(values);
+            }
+            else if (typeof(T) == typeof(Vector3))
+            {
+                return Vector3ToBytes(values);
+            }
+            else if (typeof(T) == typeof(Vector4))
+            {
+                return Vector4ToBytes(values);
+            }
+            else if (typeof(T) == typeof(Matrix4x4))
+            {
+                return Matrix4x4ToBytes(values);
+            }
+            else if (typeof(T).IsPrimitive)
+            {
+                if (typeof(T) == typeof(bool))
+                {
+                    var bits = new BitArray(values.Cast<bool>().ToArray());
+                    byte[] ret = new byte[(bits.Length - 1) / 8 + 1];
+                    bits.CopyTo(ret, 0);
+                    return ret;
+                }
+
+                var size = GetSize<T>();
+                var result = new byte[values.Count * size];
+                Buffer.BlockCopy(values.ToArray(), 0, result, 0, result.Length);
+                return result;
+            }
+            else
+            {
+                // other types (like datetime, mat2, mat3) are not implemented
+                // see https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#binary-table-format
+                throw new NotImplementedException();
+            }
+        }
+
+        private static byte[] Matrix4x4ToBytes<T>(IReadOnlyList<T> values)
+        {
+            var result = new byte[values.Count * 64];
+
+            for (int i = 0; i < values.Count; i++)
+            {
+                var mat = (Matrix4x4)(object)values[i];
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M11), 0, result, i * 64, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M12), 0, result, i * 64 + 4, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M13), 0, result, i * 64 + 8, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M14), 0, result, i * 64 + 12, 4);
+
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M21), 0, result, i * 64 + 16, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M22), 0, result, i * 64 + 20, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M23), 0, result, i * 64 + 24, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M24), 0, result, i * 64 + 28, 4);
+
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M31), 0, result, i * 64 + 32, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M32), 0, result, i * 64 + 36, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M33), 0, result, i * 64 + 40, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M34), 0, result, i * 64 + 44, 4);
+
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M41), 0, result, i * 64 + 48, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M42), 0, result, i * 64 + 52, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M43), 0, result, i * 64 + 56, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(mat.M44), 0, result, i * 64 + 60, 4);
+            }
+
+            return result;
+        }
+
+        private static byte[] Vector2ToBytes<T>(IReadOnlyList<T> values)
+        {
+            var result = new byte[values.Count * 8];
+            for (int i = 0; i < values.Count; i++)
+            {
+                var vec = (Vector2)(object)values[i];
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.X), 0, result, i * 8, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.Y), 0, result, i * 8 + 4, 4);
+            }
+            return result;
+        }
+
+        private static byte[] Vector3ToBytes<T>(IReadOnlyList<T> values)
+        {
+            var result = new byte[values.Count * 12];
+            for (int i = 0; i < values.Count; i++)
+            {
+                var vec = (Vector3)(object)values[i];
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.X), 0, result, i * 12, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.Y), 0, result, i * 12 + 4, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.Z), 0, result, i * 12 + 8, 4);
+            }
+            return result;
+        }
+
+        private static byte[] Vector4ToBytes<T>(IReadOnlyList<T> values)
+        {
+            var result = new byte[values.Count * 16];
+            for (int i = 0; i < values.Count; i++)
+            {
+                var vec = (Vector4)(object)values[i];
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.X), 0, result, i * 16, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.Y), 0, result, i * 16 + 4, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.Z), 0, result, i * 16 + 8, 4);
+                Buffer.BlockCopy(BitConverter.GetBytes(vec.W), 0, result, i * 16 + 12, 4);
+            }
+            return result;
+        }
+
+        public static List<int> GetStringOffsets(List<string> values)
+        {
+            var offsets = new List<int>() { 0 };
+            foreach (var value in values)
+            {
+                var length = Encoding.UTF8.GetBytes(value).Length;
+                offsets.Add(offsets.Last() + length);
+            }
+
+            return offsets;
+        }
+
+
+        public static List<int> GetStringOffsets(List<List<string>> values)
+        {
+            var offsets = new List<int>() { };
+            foreach (var arr in values)
+            {
+                var arrOffsets = GetStringOffsets(arr);
+                var last = offsets.LastOrDefault();
+                foreach (var offset in arrOffsets)
+                {
+                    if (!offsets.Contains(last + offset))
+                    {
+                        offsets.Add(last + offset);
+                    }
+                }
+            }
+
+            return offsets;
+        }
+
+
+        public static List<int> GetArrayOffsets<T>(List<List<T>> values)
+        {
+            var offsets = new List<int>() { 0 };
+            foreach (var value in values)
+            {
+                offsets.Add(offsets.Last() + value.Count);
+
+            }
+            return offsets;
+        }
+
+        public static int GetSize<T>()
+        {
+#if NETSTANDARD2_0
+                var isValueType = typeof(T).IsValueType;
+#else
+            var isValueType = !System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<T>();
+#endif
+
+            Guard.IsTrue(isValueType, nameof(T), "T must be a value type");
+
+            var type = typeof(T);
+            var size = Marshal.SizeOf<T>();
+            return size;
+        }
+    }
+}

+ 3 - 6
src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs

@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2
 {
 {
     /// <summary>
     /// <summary>
     /// Extension methods for Cesium glTF Extensions
     /// Extension methods for Cesium glTF Extensions
@@ -23,7 +19,8 @@ namespace SharpGLTF.Schema2
             ExtensionsFactory.RegisterExtension<MeshPrimitive, CesiumPrimitiveOutline>("CESIUM_primitive_outline");
             ExtensionsFactory.RegisterExtension<MeshPrimitive, CesiumPrimitiveOutline>("CESIUM_primitive_outline");
             ExtensionsFactory.RegisterExtension<Node, MeshExtInstanceFeatures>("EXT_instance_features");
             ExtensionsFactory.RegisterExtension<Node, MeshExtInstanceFeatures>("EXT_instance_features");
             ExtensionsFactory.RegisterExtension<MeshPrimitive, MeshExtMeshFeatures>("EXT_mesh_features");
             ExtensionsFactory.RegisterExtension<MeshPrimitive, MeshExtMeshFeatures>("EXT_mesh_features");
-
+            ExtensionsFactory.RegisterExtension<ModelRoot, EXTStructuralMetadataRoot>("EXT_structural_metadata");
+            ExtensionsFactory.RegisterExtension<MeshPrimitive, ExtStructuralMetadataMeshPrimitive>("EXT_structural_metadata");
         }
         }
     }
     }
 }
 }

+ 55 - 44
src/SharpGLTF.Cesium/Schema2/MeshExtInstanceFeatures.cs → src/SharpGLTF.Cesium/Schema2/Ext.InstanceFeatures.cs

@@ -1,8 +1,5 @@
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Linq;
-using System.Xml.Linq;
-
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
     public partial class MeshExtInstanceFeatures
     public partial class MeshExtInstanceFeatures
@@ -22,22 +19,68 @@ namespace SharpGLTF.Schema2
             }
             }
             set
             set
             {
             {
-                if (value == null) { _featureIds = null; return; }
-
                 _featureIds = value;
                 _featureIds = value;
             }
             }
         }
         }
 
 
-        protected override void OnValidateContent(ValidationContext validate)
+        protected override void OnValidateReferences(ValidationContext validate)
         {
         {
-            var extInstanceFeatures = _node.Extensions.Where(item => item is MeshExtInstanceFeatures).FirstOrDefault();
+            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
             validate.NotNull(nameof(extInstanceFeatures), extInstanceFeatures);
             validate.NotNull(nameof(extInstanceFeatures), extInstanceFeatures);
-            var ext = (MeshExtInstanceFeatures)extInstanceFeatures;
-            var extMeshGpInstancing = _node.Extensions.Where(item => item is MeshGpuInstancing).FirstOrDefault();
+            var extMeshGpInstancing = _node.GetExtension<MeshGpuInstancing>();
             validate.NotNull(nameof(extMeshGpInstancing), extMeshGpInstancing);
             validate.NotNull(nameof(extMeshGpInstancing), extMeshGpInstancing);
 
 
-            validate.NotNull(nameof(FeatureIds), ext.FeatureIds);
-            validate.IsTrue(nameof(FeatureIds), ext.FeatureIds.Count > 0, "Instance FeatureIds has items");
+            foreach (var instanceFeatureId in FeatureIds)
+            {
+                if (instanceFeatureId.Attribute.HasValue)
+                {
+                    var expectedVertexAttribute = $"_FEATURE_ID_{instanceFeatureId.Attribute}";
+                    var gpuInstancing = _node.GetGpuInstancing();
+                    var featureIdAccessors = gpuInstancing.GetAccessor(expectedVertexAttribute);
+                    Guard.NotNull(featureIdAccessors, expectedVertexAttribute);
+                }
+
+                if (instanceFeatureId.PropertyTable.HasValue)
+                {
+                    var metadataExtension = _node.LogicalParent.GetExtension<EXTStructuralMetadataRoot>();
+                    Guard.NotNull(metadataExtension, nameof(metadataExtension), "EXT_Structural_Metadata extension is not found.");
+                    Guard.NotNull(metadataExtension.PropertyTables[instanceFeatureId.PropertyTable.Value], nameof(instanceFeatureId.PropertyTable), $"Property table index {instanceFeatureId.PropertyTable.Value} does not exist");
+                }
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+        protected override void OnValidateContent(ValidationContext validate)
+        {
+            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
+            validate.NotNull(nameof(FeatureIds), extInstanceFeatures.FeatureIds);
+            validate.IsTrue(nameof(FeatureIds), extInstanceFeatures.FeatureIds.Count > 0, "Instance FeatureIds has items");
+
+
+            foreach (var instanceFeatureId in FeatureIds)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.FeatureCount, 1, nameof(instanceFeatureId.FeatureCount));
+
+                if (instanceFeatureId.NullFeatureId.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.NullFeatureId, 0, nameof(instanceFeatureId.NullFeatureId));
+                }
+                if (instanceFeatureId.Label != null)
+                {
+                    var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
+                    Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(instanceFeatureId.Label, regex), nameof(instanceFeatureId.Label));
+                }
+
+                if (instanceFeatureId.Attribute.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.Attribute, 0, nameof(instanceFeatureId.Attribute));
+                }
+                if (instanceFeatureId.PropertyTable.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.PropertyTable, 0, nameof(instanceFeatureId.PropertyTable));
+                }
+            }
 
 
             base.OnValidateContent(validate);
             base.OnValidateContent(validate);
         }
         }
@@ -97,43 +140,11 @@ namespace SharpGLTF.Schema2
 
 
             Guard.NotNullOrEmpty(instanceFeatureIds, nameof(instanceFeatureIds));
             Guard.NotNullOrEmpty(instanceFeatureIds, nameof(instanceFeatureIds));
 
 
-            var extMeshGpInstancing = node.Extensions.Where(item => item is MeshGpuInstancing).FirstOrDefault();
+            var extMeshGpInstancing = node.GetExtension<MeshGpuInstancing>();
             Guard.NotNull(extMeshGpInstancing, nameof(extMeshGpInstancing));
             Guard.NotNull(extMeshGpInstancing, nameof(extMeshGpInstancing));
 
 
-            foreach (var instanceFeatureId in instanceFeatureIds)
-            {
-                ValidateInstanceFeatureId(node, instanceFeatureId);
-            };
-
             var ext = node.UseExtension<MeshExtInstanceFeatures>();
             var ext = node.UseExtension<MeshExtInstanceFeatures>();
             ext.FeatureIds = instanceFeatureIds;
             ext.FeatureIds = instanceFeatureIds;
         }
         }
-
-        private static void ValidateInstanceFeatureId(Node node, MeshExtInstanceFeatureID instanceFeatureId)
-        {
-            Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.FeatureCount, 1, nameof(instanceFeatureId.FeatureCount));
-
-            if (instanceFeatureId.NullFeatureId.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.NullFeatureId, 0, nameof(instanceFeatureId.NullFeatureId));
-            }
-            if (instanceFeatureId.Label != null)
-            {
-                var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
-                Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(instanceFeatureId.Label, regex), nameof(instanceFeatureId.Label));
-            }
-            if (instanceFeatureId.Attribute.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.Attribute, 0, nameof(instanceFeatureId.Attribute));
-                var expectedVertexAttribute = $"_FEATURE_ID_{instanceFeatureId.Attribute}";
-                var gpuInstancing = node.GetGpuInstancing();
-                var featureIdAccessors = gpuInstancing.GetAccessor(expectedVertexAttribute);
-                Guard.NotNull(featureIdAccessors, expectedVertexAttribute);
-            }
-            if (instanceFeatureId.PropertyTable.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.PropertyTable, 0, nameof(instanceFeatureId.PropertyTable));
-            }
-        }
     }
     }
 }
 }

+ 71 - 50
src/SharpGLTF.Cesium/Schema2/MeshExtMeshFeatures.cs → src/SharpGLTF.Cesium/Schema2/Ext.MeshFeatures.cs

@@ -1,7 +1,6 @@
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using System.Xml.Linq;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
@@ -20,25 +19,81 @@ namespace SharpGLTF.Schema2
             get => _featureIds;
             get => _featureIds;
             set
             set
             {
             {
-                if (value == null) { _featureIds = null; return; }
                 _featureIds = value;
                 _featureIds = value;
             }
             }
         }
         }
 
 
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            foreach (var featureId in _featureIds)
+            {
+                if (featureId.Attribute.HasValue)
+                {
+                    var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
+                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
+                }
+                if (featureId.PropertyTable.HasValue)
+                {
+                    var metadataExtension = _meshPrimitive.LogicalParent.LogicalParent.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");
+                }
+                if (featureId.Texture != null)
+                {
+                    var expectedTexCoordAttribute = $"TEXCOORD_{featureId.Texture.TextureCoordinate}";
+                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
+
+                    var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
+                    validate.IsNullOrIndex(nameof(featureId.Texture), featureId.Texture.TextureCoordinate, modelRoot.LogicalTextures);
+                }
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+
         protected override void OnValidateContent(ValidationContext validate)
         protected override void OnValidateContent(ValidationContext validate)
         {
         {
             var extMeshFeatures = _meshPrimitive.Extensions.Where(item => item is MeshExtMeshFeatures).FirstOrDefault();
             var extMeshFeatures = _meshPrimitive.Extensions.Where(item => item is MeshExtMeshFeatures).FirstOrDefault();
             validate.NotNull(nameof(extMeshFeatures), extMeshFeatures);
             validate.NotNull(nameof(extMeshFeatures), extMeshFeatures);
-            var ext = (MeshExtMeshFeatures)extMeshFeatures;
-            validate.NotNull(nameof(FeatureIds), ext.FeatureIds);
-            validate.IsTrue(nameof(FeatureIds), ext.FeatureIds.Count > 0, "FeatureIds has items");
+            validate.NotNull(nameof(FeatureIds), _featureIds);
+            validate.IsTrue(nameof(FeatureIds), _featureIds.Count > 0, "FeatureIds has items");
 
 
-            base.OnValidateContent(validate);
+            foreach (var featureId in _featureIds)
+            {
+                if (featureId.NullFeatureId.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.NullFeatureId, 0, nameof(featureId.NullFeatureId));
+                }
+                if (featureId.Label != null)
+                {
+                    var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
+                    Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(featureId.Label, regex), nameof(featureId.Label));
+                }
+                if (featureId.Attribute.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
+                }
+                if (featureId.PropertyTable.HasValue)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.PropertyTable, 0, nameof(featureId.PropertyTable));
+                }
+                if (featureId.Texture != null)
+                {
+                    Guard.MustBeGreaterThanOrEqualTo(featureId.Texture.TextureCoordinate, 0, nameof(featureId.Texture.TextureCoordinate));
+                }
+                base.OnValidateContent(validate);
+            }
         }
         }
     }
     }
 
 
     public partial class MeshExtMeshFeatureIDTexture
     public partial class MeshExtMeshFeatureIDTexture
     {
     {
+        public MeshExtMeshFeatureIDTexture()
+        {
+            _channels = new List<int>();
+        }
+
         public MeshExtMeshFeatureIDTexture(List<int> channels, int? index = null, int? texCoord = null)
         public MeshExtMeshFeatureIDTexture(List<int> channels, int? index = null, int? texCoord = null)
         {
         {
             Guard.NotNullOrEmpty(channels, nameof(channels));
             Guard.NotNullOrEmpty(channels, nameof(channels));
@@ -55,6 +110,9 @@ namespace SharpGLTF.Schema2
 
 
     public partial class MeshExtMeshFeatureID
     public partial class MeshExtMeshFeatureID
     {
     {
+        public MeshExtMeshFeatureID()
+        {
+        }
         public MeshExtMeshFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null, MeshExtMeshFeatureIDTexture texture = null)
         public MeshExtMeshFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null, MeshExtMeshFeatureIDTexture texture = null)
         {
         {
             _featureCount = featureCount;
             _featureCount = featureCount;
@@ -100,6 +158,11 @@ namespace SharpGLTF.Schema2
 
 
     public static class ExtMeshFeatures
     public static class ExtMeshFeatures
     {
     {
+        public static void SetFeatureId(this MeshPrimitive primitive, MeshExtMeshFeatureID featureId)
+        {
+            primitive.SetFeatureIds(new List<MeshExtMeshFeatureID>() { featureId });
+        }
+
         /// <summary>
         /// <summary>
         /// Set the FeatureIds for a MeshPrimitive
         /// Set the FeatureIds for a MeshPrimitive
         /// </summary>
         /// </summary>
@@ -107,52 +170,10 @@ namespace SharpGLTF.Schema2
         /// <param name="featureIds"></param>
         /// <param name="featureIds"></param>
         public static void SetFeatureIds(this MeshPrimitive primitive, List<MeshExtMeshFeatureID> featureIds)
         public static void SetFeatureIds(this MeshPrimitive primitive, List<MeshExtMeshFeatureID> featureIds)
         {
         {
-            if (featureIds == null) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return; }
-
-            Guard.NotNullOrEmpty(featureIds, nameof(featureIds));
-
-            foreach (var featureId in featureIds)
-            {
-                ValidateFeature(primitive, featureId);
-            };
+            if (featureIds == null || featureIds.Count == 0) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return; }
 
 
             var ext = primitive.UseExtension<MeshExtMeshFeatures>();
             var ext = primitive.UseExtension<MeshExtMeshFeatures>();
             ext.FeatureIds = featureIds;
             ext.FeatureIds = featureIds;
         }
         }
-
-        private static void ValidateFeature(MeshPrimitive primitive, MeshExtMeshFeatureID item)
-        {
-            Guard.MustBeGreaterThanOrEqualTo((int)item.FeatureCount, 1, nameof(item.FeatureCount));
-
-            if (item.NullFeatureId.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)item.NullFeatureId, 0, nameof(item.NullFeatureId));
-            }
-            if (item.Label != null)
-            {
-                var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
-                Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(item.Label, regex), nameof(item.Label));
-            }
-            if (item.Attribute.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)item.Attribute, 0, nameof(item.Attribute));
-                // Guard that the custom vertex attribute (_FEATURE_ID_{attribute}) exists when FeatureID has attribute set
-                var expectedVertexAttribute = $"_FEATURE_ID_{item.Attribute}";
-                Guard.NotNull(primitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
-            }
-            if (item.PropertyTable.HasValue)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)item.PropertyTable, 0, nameof(item.PropertyTable));
-            }
-            if (item.Texture != null)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)item.Texture.TextureCoordinate, 0, nameof(item.Texture.TextureCoordinate));
-                var expectedTexCoordAttribute = $"TEXCOORD_{item.Texture.TextureCoordinate}";
-                Guard.NotNull(primitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
-
-                var image = primitive.LogicalParent.LogicalParent.LogicalImages[item.Texture.Index];
-                Guard.NotNull(image, "Texture " + nameof(item.Texture.Index));
-            }
-        }
     }
     }
-}
+}

+ 82 - 0
src/SharpGLTF.Cesium/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -0,0 +1,82 @@
+using SharpGLTF.Validation;
+using System.Collections.Generic;
+
+namespace SharpGLTF.Schema2
+{
+    partial class ExtStructuralMetadataMeshPrimitive
+    {
+        internal ExtStructuralMetadataMeshPrimitive(MeshPrimitive meshPrimitive)
+        {
+            this.meshPrimitive = meshPrimitive;
+            _propertyTextures = new List<int>();
+            _propertyAttributes = new List<int>();
+        }
+
+        private MeshPrimitive meshPrimitive;
+
+        public List<int> PropertyTextures
+        {
+            get
+            {
+                return _propertyTextures;
+            }
+            set
+            {
+                _propertyTextures = value;
+            }
+        }
+
+        public List<int> PropertyAttributes
+        {
+            get
+            {
+                return _propertyAttributes;
+            }
+            set
+            {
+                _propertyAttributes = value;
+            }
+        }
+
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            foreach (var propertyTexture in PropertyTextures)
+            {
+                var propertyTextures = meshPrimitive.LogicalParent.LogicalParent.GetExtension<EXTStructuralMetadataRoot>().PropertyTextures;
+                validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
+            }
+
+            foreach (var propertyAttribute in PropertyAttributes)
+            {
+                var propertyAttributes = meshPrimitive.LogicalParent.LogicalParent.GetExtension<EXTStructuralMetadataRoot>().PropertyAttributes;
+                validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, propertyAttributes);
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+        protected override void OnValidateContent(ValidationContext result)
+        {
+            base.OnValidateContent(result);
+        }
+    }
+
+    partial class CesiumExtensions
+    {
+        public static void SetPropertyTextures(this MeshPrimitive primitive, List<int> propertyTextures)
+        {
+            if (propertyTextures == null) { primitive.RemoveExtensions<ExtStructuralMetadataMeshPrimitive>(); return; }
+
+            var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
+            ext.PropertyTextures = propertyTextures;
+        }
+
+        public static void SetPropertyAttributes(this MeshPrimitive primitive, List<int> propertyAttributes)
+        {
+            if (propertyAttributes == null) { primitive.RemoveExtensions<ExtStructuralMetadataMeshPrimitive>(); return; }
+
+            var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
+            ext.PropertyAttributes = propertyAttributes;
+        }
+    }
+}

+ 671 - 0
src/SharpGLTF.Cesium/Schema2/Ext.StructuralMetadataRoot.cs

@@ -0,0 +1,671 @@
+using OneOf;
+using SharpGLTF.Memory;
+using SharpGLTF.Validation;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace SharpGLTF.Schema2
+{
+    public static class ExtStructuralMetadataRoot
+    {
+        public static void SetPropertyAttribute(
+            this ModelRoot modelRoot,
+            PropertyAttribute propertyAttribute,
+            OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            SetPropertyAttributes(modelRoot, new List<PropertyAttribute>() { propertyAttribute }, schema);
+        }
+
+        public static void SetPropertyAttributes(
+this ModelRoot modelRoot,
+List<PropertyAttribute> propertyAttributes,
+OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            if (propertyAttributes == null || propertyAttributes.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
+
+            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
+            ext.PropertyAttributes = propertyAttributes;
+            ext.AddSchema(schema);
+        }
+
+
+        public static void SetPropertyTexture(
+    this ModelRoot modelRoot,
+    PropertyTexture propertyTexture,
+    OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            SetPropertyTextures(modelRoot, new List<PropertyTexture>() { propertyTexture }, schema);
+        }
+
+
+        public static void SetPropertyTextures(
+    this ModelRoot modelRoot,
+    List<PropertyTexture> propertyTextures,
+    OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            if (propertyTextures == null || propertyTextures.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
+
+            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
+            ext.PropertyTextures = propertyTextures;
+            ext.AddSchema(schema);
+        }
+
+        public static void SetPropertyTable(
+            this ModelRoot modelRoot,
+            PropertyTable propertyTable,
+            OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            SetPropertyTables(modelRoot, new List<PropertyTable>() { propertyTable }, schema);
+        }
+
+        public static void SetPropertyTables(
+            this ModelRoot modelRoot,
+            List<PropertyTable> propertyTables,
+            OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            if (propertyTables == null || propertyTables.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
+
+            var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
+            ext.PropertyTables = propertyTables;
+            ext.AddSchema(schema);
+        }
+
+        public static PropertyTableProperty GetArrayPropertyTableProperty<T>(this ModelRoot model, List<List<T>> values, bool CreateArrayOffsets = true)
+        {
+            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;
+        }
+    }
+
+    public partial class EXTStructuralMetadataRoot
+    {
+        private ModelRoot modelRoot;
+
+        internal EXTStructuralMetadataRoot(ModelRoot modelRoot)
+        {
+            this.modelRoot = modelRoot;
+            _propertyTables = new List<PropertyTable>();
+            _propertyAttributes = new List<PropertyAttribute>();
+            _propertyTextures = new List<PropertyTexture>();
+        }
+
+        internal void AddSchema(OneOf<StructuralMetadataSchema, Uri> schema)
+        {
+            schema.Switch(
+                StructuralMetadataSchema => _schema = StructuralMetadataSchema,
+                Uri => this.SchemaUri = Uri.ToString()
+                );
+        }
+
+        internal List<PropertyTable> PropertyTables
+        {
+            get { return _propertyTables; }
+            set { _propertyTables = value; }
+        }
+
+        internal string SchemaUri
+        {
+            get { return _schemaUri; }
+            set { _schemaUri = value; }
+        }
+
+        internal List<PropertyAttribute> PropertyAttributes
+        {
+            get { return _propertyAttributes; }
+            set { _propertyAttributes = value; }
+        }
+
+        internal StructuralMetadataSchema Schema
+        {
+            get { return _schema; }
+            set { _schema = value; }
+        }
+
+        internal List<PropertyTexture> PropertyTextures
+        {
+            get { return _propertyTextures; }
+            set { _propertyTextures = value; }
+        }
+
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            foreach (var propertyTexture in PropertyTextures)
+            {
+                foreach (var propertyTextureProperty in propertyTexture.Properties)
+                {
+                    var textureId = propertyTextureProperty.Value._LogicalTextureIndex;
+                    validate.IsNullOrIndex(nameof(propertyTexture), textureId, modelRoot.LogicalTextures);
+                }
+            }
+
+            foreach (var propertyTable in PropertyTables)
+            {
+                Guard.NotNull(Schema.Classes[propertyTable.Class], nameof(propertyTable.Class), $"Schema must have class {propertyTable.Class}");
+
+                foreach (var property in propertyTable.Properties)
+                {
+                    Guard.NotNull(Schema.Classes[propertyTable.Class].Properties[property.Key], nameof(property.Key), $"Schema must have property {property.Key}");
+
+                    var values = property.Value.Values;
+                    validate.IsNullOrIndex(nameof(propertyTable), values, modelRoot.LogicalBufferViews);
+
+                    if (property.Value.ArrayOffsets.HasValue)
+                    {
+                        var arrayOffsets = property.Value.ArrayOffsets.Value;
+                        validate.IsNullOrIndex(nameof(propertyTable), arrayOffsets, modelRoot.LogicalBufferViews);
+                    }
+
+                    if (property.Value.StringOffsets.HasValue)
+                    {
+                        var stringOffsets = property.Value.StringOffsets.Value;
+                        validate.IsNullOrIndex(nameof(propertyTable), stringOffsets, modelRoot.LogicalBufferViews);
+                    }
+                }
+            }
+
+            if (Schema != null)
+            {
+                foreach (var @class in Schema.Classes)
+                {
+                    foreach (var property in @class.Value.Properties)
+                    {
+                        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");
+                        }
+                    }
+                }
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+        protected override void OnValidateContent(ValidationContext result)
+        {
+            // check schema id is defined and valid
+            if (Schema != null && !String.IsNullOrEmpty(Schema.Id))
+            {
+                var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
+                Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(Schema.Id, regex), nameof(Schema.Id));
+
+
+                foreach(var _class in Schema.Classes)
+                {
+                    Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(_class.Key, regex), nameof(_class.Key));
+
+                    foreach(var property in _class.Value.Properties)
+                    {
+                        if (property.Value.Count.HasValue)
+                        {
+                            Guard.MustBeGreaterThanOrEqualTo(property.Value.Count.Value, 2, nameof(property.Value.Count));
+                        }
+                    }
+                }
+            }
+
+            foreach (var propertyTexture in PropertyTextures)
+            {
+                foreach (var propertyTextureProperty in propertyTexture.Properties)
+                {
+                    var texCoord = propertyTextureProperty.Value.TextureCoordinate;
+                    var channels = propertyTextureProperty.Value.Channels;
+                    var index = propertyTextureProperty.Value._LogicalTextureIndex;
+                    Guard.MustBeGreaterThanOrEqualTo(texCoord, 0, nameof(texCoord));
+                    Guard.IsTrue(channels.Count > 0, nameof(channels), "Channels must be defined");
+                    Guard.IsTrue(index >= 0, nameof(index), "Index must be defined");
+                }
+            }
+
+            foreach (var propertyTable in PropertyTables)
+            {
+                Guard.IsTrue(propertyTable.Class != null, nameof(propertyTable.Class), "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");
+            }
+
+            // Check one of schema or schemaUri is defined, but not both
+            Guard.IsFalse(Schema != null && SchemaUri != null, "Schema/SchemaUri", "Schema and SchemaUri cannot both be defined");
+            Guard.IsFalse(Schema == null && SchemaUri == null, "Schema/SchemaUri", "One of Schema and SchemaUri must be defined");
+
+            base.OnValidateContent(result);
+        }
+    }
+
+    public partial class PropertyTexture
+    {
+        public PropertyTexture()
+        {
+            _properties = new Dictionary<string, PropertyTextureProperty>();
+        }
+
+        public string Class
+        {
+            get { return _class; }
+            set { _class = value; }
+        }
+
+        public Dictionary<string, PropertyTextureProperty> Properties
+        {
+            get { return _properties; }
+            set { _properties = value; }
+        }
+    }
+
+    public partial class PropertyTextureProperty
+    {
+        public PropertyTextureProperty()
+        {
+            _channels = new List<int>();
+        }
+
+        public List<int> Channels
+        {
+            get { return _channels; }
+            set { _channels = value; }
+        }
+    }
+
+    public partial class PropertyAttribute
+    {
+        public PropertyAttribute()
+        {
+            _properties = new Dictionary<string, PropertyAttributeProperty>();
+        }
+        public string Class
+        {
+            get { return _class; }
+            set
+            {
+                _class = value;
+            }
+        }
+
+        public Dictionary<string, PropertyAttributeProperty> Properties
+        {
+            get { return _properties; }
+            set
+            {
+                _properties = value;
+            }
+        }
+
+    }
+
+    public partial class PropertyAttributeProperty
+    {
+        public string Attribute
+        {
+            get { return _attribute; }
+            set
+            {
+                _attribute = value;
+            }
+        }
+    }
+
+    public partial class StructuralMetadataSchema
+    {
+        public StructuralMetadataSchema()
+        {
+            _classes = new Dictionary<string, StructuralMetadataClass>();
+            _enums = new Dictionary<string, StructuralMetadataEnum>();
+        }
+
+        public Dictionary<string, StructuralMetadataClass> Classes
+        {
+            get { return _classes; }
+            set
+            {
+                _classes = value;
+            }
+        }
+
+        public string Id
+        {
+            get { return _id; }
+            set
+            {
+                _id = value;
+            }
+        }
+
+        public string Version
+        {
+            get { return _version; }
+            set
+            {
+                _version = value;
+            }
+        }
+
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                _name = value;
+            }
+        }
+
+        public string Description
+        {
+            get { return _description; }
+            set
+            {
+                _description = value;
+            }
+        }
+
+        public Dictionary<string, StructuralMetadataEnum> Enums
+        {
+            get { return _enums; }
+            set
+            {
+                _enums = value;
+            }
+        }
+    }
+
+    public partial class StructuralMetadataEnum
+    {
+        public StructuralMetadataEnum()
+        {
+            _values = new List<EnumValue>();
+        }
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                _name = value;
+            }
+        }
+        public string Description
+        {
+            get { return _description; }
+            set
+            {
+                _description = value;
+            }
+        }
+        public List<EnumValue> Values
+        {
+            get { return _values; }
+            set
+            {
+                _values = value;
+            }
+        }
+    }
+
+    public partial class EnumValue
+    {
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                _name = value;
+            }
+        }
+        public int Value
+        {
+            get { return _value; }
+            set
+            {
+                _value = value;
+            }
+        }
+    }
+
+    public partial class StructuralMetadataClass
+    {
+        public StructuralMetadataClass()
+        {
+            _properties = new Dictionary<string, ClassProperty>();
+        }
+
+        public Dictionary<string, ClassProperty> Properties
+        {
+            get { return _properties; }
+            set
+            {
+                _properties = value;
+            }
+        }
+
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                if (value == null) { _name = null; return; }
+                _name = value;
+            }
+        }
+
+        public string Description
+        {
+            get { return _description; }
+            set
+            {
+                _description = value;
+            }
+        }
+
+    }
+
+    public partial class ClassProperty
+    {
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                _name = value;
+            }
+        }
+
+        public string Description
+        {
+            get { return _description; }
+            set
+            {
+                _description = value;
+            }
+        }
+
+        public ElementType Type
+        {
+            get { return _type; }
+            set
+            {
+                _type = value;
+            }
+        }
+
+        public string EnumType
+        {
+            get { return _enumType; }
+            set
+            {
+                _enumType = value;
+            }
+        }
+
+        public DataType? ComponentType
+        {
+            get { return _componentType; }
+            set
+            {
+                _componentType = value;
+            }
+        }
+
+        public bool? Required
+        {
+            get { return _required; }
+            set
+            {
+                _required = value;
+            }
+        }
+
+        public bool? Normalized
+        {
+            get { return _normalized; }
+            set
+            {
+                _normalized = value;
+            }
+        }
+
+        public bool? Array
+        {
+            get { return _array; }
+            set
+            {
+                _array = value;
+            }
+
+        }
+
+        public int? Count
+        {
+            get { return _count; }
+            set
+            {
+                _count = value;
+            }
+        }
+    }
+
+    public partial class PropertyTable
+    {
+        public PropertyTable()
+        {
+            _properties = new Dictionary<string, PropertyTableProperty>();
+        }
+        public PropertyTable(string Class, int Count, string Name = "") : this()
+        {
+            _class = Class;
+            _count = Count;
+            _name = Name;
+        }
+
+        public string Name
+        {
+            get { return _name; }
+            set
+            {
+                _name = value;
+            }
+        }
+
+        public string Class
+        {
+            get { return _class; }
+            set
+            {
+                _class = value;
+            }
+        }
+
+        public int Count
+        {
+            get { return _count; }
+            set
+            {
+                _count = value;
+            }
+        }
+
+        public Dictionary<string, PropertyTableProperty> Properties
+        {
+            get { return _properties; }
+            set
+            {
+                _properties = value;
+            }
+        }
+    }
+
+    public partial class PropertyTableProperty
+    {
+        public int Values
+        {
+            get { return _values; }
+            set { _values = value; }
+        }
+
+        public int? ArrayOffsets
+        {
+            get { return _arrayOffsets; }
+            set
+            {
+                _arrayOffsets = value;
+            }
+        }
+
+        public int? StringOffsets
+        {
+            get { return _stringOffsets; }
+            set
+            {
+                _stringOffsets = value;
+            }
+        }
+    }
+}
+

+ 66 - 0
src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_primitive.g.cs

@@ -0,0 +1,66 @@
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// Structural metadata about a glTF primitive.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 ExtStructuralMetadataMeshPrimitive : ExtraProperties
+	{
+	
+		private const int _propertyAttributesMinItems = 1;
+		private List<Int32> _propertyAttributes;
+		
+		private const int _propertyTexturesMinItems = 1;
+		private List<Int32> _propertyTextures;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "propertyAttributes", _propertyAttributes, _propertyAttributesMinItems);
+			SerializeProperty(writer, "propertyTextures", _propertyTextures, _propertyTexturesMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "propertyAttributes": DeserializePropertyList<Int32>(ref reader, _propertyAttributes); break;
+				case "propertyTextures": DeserializePropertyList<Int32>(ref reader, _propertyTextures); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 683 - 0
src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_root.g.cs

@@ -0,0 +1,683 @@
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// The element type.
+	/// </summary>
+	public enum ElementType
+	{
+		SCALAR,
+		VEC2,
+		VEC3,
+		VEC4,
+		MAT2,
+		MAT3,
+		MAT4,
+		STRING,
+		BOOLEAN,
+		ENUM,
+	}
+
+
+	/// <summary>
+	/// The datatype of the element's components. Only applicable to `SCALAR`, `VECN`, and `MATN` types.
+	/// </summary>
+	public enum DataType
+	{
+		INT8,
+		UINT8,
+		INT16,
+		UINT16,
+		INT32,
+		UINT32,
+		INT64,
+		UINT64,
+		FLOAT32,
+		FLOAT64,
+	}
+
+
+	/// <summary>
+	/// The type of the integer enum value.
+	/// </summary>
+	public enum IntegerType
+	{
+		INT8,
+		UINT8,
+		INT16,
+		UINT16,
+		INT32,
+		UINT32,
+		INT64,
+		UINT64,
+	}
+
+
+	/// <summary>
+	/// The type of values in `stringOffsets`.
+	/// </summary>
+	public enum ArrayOffsetType
+	{
+		UINT8,
+		UINT16,
+		UINT32,
+		UINT64,
+	}
+
+
+	/// <summary>
+	/// A class property.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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
+	{
+	
+		private static readonly Boolean _arrayDefault = false;
+		private Boolean? _array = _arrayDefault;
+		
+		private DataType? _componentType;
+		
+		private const Int32 _countMinimum = 2;
+		private Int32? _count;
+		
+		private System.Text.Json.Nodes.JsonNode _default;
+		
+		private String _description;
+		
+		private String _enumType;
+		
+		private System.Text.Json.Nodes.JsonNode _max;
+		
+		private System.Text.Json.Nodes.JsonNode _min;
+		
+		private String _name;
+		
+		private System.Text.Json.Nodes.JsonNode _noData;
+		
+		private static readonly Boolean _normalizedDefault = false;
+		private Boolean? _normalized = _normalizedDefault;
+		
+		private System.Text.Json.Nodes.JsonNode _offset;
+		
+		private static readonly Boolean _requiredDefault = false;
+		private Boolean? _required = _requiredDefault;
+		
+		private System.Text.Json.Nodes.JsonNode _scale;
+		
+		private String _semantic;
+		
+		private ElementType _type;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "array", _array, _arrayDefault);
+			SerializePropertyEnumSymbol<DataType>(writer, "componentType", _componentType);
+			SerializeProperty(writer, "count", _count);
+			SerializeProperty(writer, "default", _default);
+			SerializeProperty(writer, "description", _description);
+			SerializeProperty(writer, "enumType", _enumType);
+			SerializeProperty(writer, "max", _max);
+			SerializeProperty(writer, "min", _min);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "noData", _noData);
+			SerializeProperty(writer, "normalized", _normalized, _normalizedDefault);
+			SerializeProperty(writer, "offset", _offset);
+			SerializeProperty(writer, "required", _required, _requiredDefault);
+			SerializeProperty(writer, "scale", _scale);
+			SerializeProperty(writer, "semantic", _semantic);
+			SerializePropertyEnumSymbol<ElementType>(writer, "type", _type);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "array": _array = DeserializePropertyValue<Boolean?>(ref reader); break;
+				case "componentType": _componentType = DeserializePropertyValue<DataType>(ref reader); break;
+				case "count": _count = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "default": _default = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
+				case "enumType": _enumType = DeserializePropertyValue<String>(ref reader); break;
+				case "max": _max = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "min": _min = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "noData": _noData = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "normalized": _normalized = DeserializePropertyValue<Boolean?>(ref reader); break;
+				case "offset": _offset = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "required": _required = DeserializePropertyValue<Boolean?>(ref reader); break;
+				case "scale": _scale = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "semantic": _semantic = DeserializePropertyValue<String>(ref reader); break;
+				case "type": _type = DeserializePropertyValue<ElementType>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// A class containing a set of properties.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 StructuralMetadataClass : ExtraProperties
+	{
+	
+		private String _description;
+		
+		private String _name;
+		
+		private Dictionary<String,ClassProperty> _properties;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "description", _description);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "properties", _properties);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "properties": DeserializePropertyDictionary<ClassProperty>(ref reader, _properties); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An enum value.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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
+	{
+	
+		private String _description;
+		
+		private String _name;
+		
+		private Int32 _value;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "description", _description);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "value", _value);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "value": _value = DeserializePropertyValue<Int32>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An object defining the values of an enum.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 StructuralMetadataEnum : ExtraProperties
+	{
+	
+		private String _description;
+		
+		private String _name;
+		
+		private const IntegerType _valueTypeDefault = IntegerType.UINT16;
+		private IntegerType? _valueType = _valueTypeDefault;
+		
+		private const int _valuesMinItems = 1;
+		private List<EnumValue> _values;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "description", _description);
+			SerializeProperty(writer, "name", _name);
+			SerializePropertyEnumSymbol<IntegerType>(writer, "valueType", _valueType, _valueTypeDefault);
+			SerializeProperty(writer, "values", _values, _valuesMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				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;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An object defining classes and enums.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 StructuralMetadataSchema : ExtraProperties
+	{
+	
+		private Dictionary<String,StructuralMetadataClass> _classes;
+		
+		private String _description;
+		
+		private Dictionary<String,StructuralMetadataEnum> _enums;
+		
+		private String _id;
+		
+		private String _name;
+		
+		private String _version;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "classes", _classes);
+			SerializeProperty(writer, "description", _description);
+			SerializeProperty(writer, "enums", _enums);
+			SerializeProperty(writer, "id", _id);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "version", _version);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "classes": DeserializePropertyDictionary<StructuralMetadataClass>(ref reader, _classes); break;
+				case "description": _description = DeserializePropertyValue<String>(ref reader); break;
+				case "enums": DeserializePropertyDictionary<StructuralMetadataEnum>(ref reader, _enums); break;
+				case "id": _id = DeserializePropertyValue<String>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "version": _version = DeserializePropertyValue<String>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An array of binary property values.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyTableProperty : ExtraProperties
+	{
+	
+		private const ArrayOffsetType _arrayOffsetTypeDefault = ArrayOffsetType.UINT32;
+		private ArrayOffsetType? _arrayOffsetType = _arrayOffsetTypeDefault;
+		
+		private Int32? _arrayOffsets;
+		
+		private System.Text.Json.Nodes.JsonNode _max;
+		
+		private System.Text.Json.Nodes.JsonNode _min;
+		
+		private System.Text.Json.Nodes.JsonNode _offset;
+		
+		private System.Text.Json.Nodes.JsonNode _scale;
+		
+		private const ArrayOffsetType _stringOffsetTypeDefault = ArrayOffsetType.UINT32;
+		private ArrayOffsetType? _stringOffsetType = _stringOffsetTypeDefault;
+		
+		private Int32? _stringOffsets;
+		
+		private Int32 _values;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializePropertyEnumSymbol<ArrayOffsetType>(writer, "arrayOffsetType", _arrayOffsetType, _arrayOffsetTypeDefault);
+			SerializeProperty(writer, "arrayOffsets", _arrayOffsets);
+			SerializeProperty(writer, "max", _max);
+			SerializeProperty(writer, "min", _min);
+			SerializeProperty(writer, "offset", _offset);
+			SerializeProperty(writer, "scale", _scale);
+			SerializePropertyEnumSymbol<ArrayOffsetType>(writer, "stringOffsetType", _stringOffsetType, _stringOffsetTypeDefault);
+			SerializeProperty(writer, "stringOffsets", _stringOffsets);
+			SerializeProperty(writer, "values", _values);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "arrayOffsetType": _arrayOffsetType = DeserializePropertyValue<ArrayOffsetType>(ref reader); break;
+				case "arrayOffsets": _arrayOffsets = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "max": _max = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "min": _min = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "offset": _offset = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "scale": _scale = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "stringOffsetType": _stringOffsetType = DeserializePropertyValue<ArrayOffsetType>(ref reader); break;
+				case "stringOffsets": _stringOffsets = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "values": _values = DeserializePropertyValue<Int32>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// Properties conforming to a class, organized as property values stored in binary columnar arrays.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyTable : ExtraProperties
+	{
+	
+		private String _class;
+		
+		private const Int32 _countMinimum = 1;
+		private Int32 _count;
+		
+		private String _name;
+		
+		private Dictionary<String,PropertyTableProperty> _properties;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "class", _class);
+			SerializeProperty(writer, "count", _count);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "properties", _properties);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "class": _class = DeserializePropertyValue<String>(ref reader); break;
+				case "count": _count = DeserializePropertyValue<Int32>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "properties": DeserializePropertyDictionary<PropertyTableProperty>(ref reader, _properties); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// A texture containing property values.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyTextureProperty : TextureInfo
+	{
+	
+		private const int _channelsMinItems = 1;
+		private List<Int32> _channels;
+		
+		private System.Text.Json.Nodes.JsonNode _max;
+		
+		private System.Text.Json.Nodes.JsonNode _min;
+		
+		private System.Text.Json.Nodes.JsonNode _offset;
+		
+		private System.Text.Json.Nodes.JsonNode _scale;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "channels", _channels, _channelsMinItems);
+			SerializeProperty(writer, "max", _max);
+			SerializeProperty(writer, "min", _min);
+			SerializeProperty(writer, "offset", _offset);
+			SerializeProperty(writer, "scale", _scale);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "channels": DeserializePropertyList<Int32>(ref reader, _channels); break;
+				case "max": _max = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "min": _min = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "offset": _offset = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "scale": _scale = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// Properties conforming to a class, organized as property values stored in textures.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyTexture : ExtraProperties
+	{
+	
+		private String _class;
+		
+		private String _name;
+		
+		private Dictionary<String,PropertyTextureProperty> _properties;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "class", _class);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "properties", _properties);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "class": _class = DeserializePropertyValue<String>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "properties": DeserializePropertyDictionary<PropertyTextureProperty>(ref reader, _properties); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An attribute containing property values.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyAttributeProperty : ExtraProperties
+	{
+	
+		private String _attribute;
+		
+		private System.Text.Json.Nodes.JsonNode _max;
+		
+		private System.Text.Json.Nodes.JsonNode _min;
+		
+		private System.Text.Json.Nodes.JsonNode _offset;
+		
+		private System.Text.Json.Nodes.JsonNode _scale;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "attribute", _attribute);
+			SerializeProperty(writer, "max", _max);
+			SerializeProperty(writer, "min", _min);
+			SerializeProperty(writer, "offset", _offset);
+			SerializeProperty(writer, "scale", _scale);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "attribute": _attribute = DeserializePropertyValue<String>(ref reader); break;
+				case "max": _max = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "min": _min = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "offset": _offset = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				case "scale": _scale = DeserializePropertyValue<System.Text.Json.Nodes.JsonNode>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// Properties conforming to a class, organized as property values stored in attributes.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 PropertyAttribute : ExtraProperties
+	{
+	
+		private String _class;
+		
+		private String _name;
+		
+		private Dictionary<String,PropertyAttributeProperty> _properties;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "class", _class);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "properties", _properties);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "class": _class = DeserializePropertyValue<String>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "properties": DeserializePropertyDictionary<PropertyAttributeProperty>(ref reader, _properties); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// glTF extension that provides structural metadata about vertices, texels, and features in a glTF asset.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[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 EXTStructuralMetadataRoot : ExtraProperties
+	{
+	
+		private const int _propertyAttributesMinItems = 1;
+		private List<PropertyAttribute> _propertyAttributes;
+		
+		private const int _propertyTablesMinItems = 1;
+		private List<PropertyTable> _propertyTables;
+		
+		private const int _propertyTexturesMinItems = 1;
+		private List<PropertyTexture> _propertyTextures;
+		
+		private StructuralMetadataSchema _schema;
+		
+		private String _schemaUri;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "propertyAttributes", _propertyAttributes, _propertyAttributesMinItems);
+			SerializeProperty(writer, "propertyTables", _propertyTables, _propertyTablesMinItems);
+			SerializeProperty(writer, "propertyTextures", _propertyTextures, _propertyTexturesMinItems);
+			SerializePropertyObject(writer, "schema", _schema);
+			SerializeProperty(writer, "schemaUri", _schemaUri);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "propertyAttributes": DeserializePropertyList<PropertyAttribute>(ref reader, _propertyAttributes); break;
+				case "propertyTables": DeserializePropertyList<PropertyTable>(ref reader, _propertyTables); break;
+				case "propertyTextures": DeserializePropertyList<PropertyTexture>(ref reader, _propertyTextures); break;
+				case "schema": _schema = DeserializePropertyValue<StructuralMetadataSchema>(ref reader); break;
+				case "schemaUri": _schemaUri = DeserializePropertyValue<String>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 4 - 0
src/SharpGLTF.Cesium/SharpGLTF.Cesium.csproj

@@ -20,4 +20,8 @@
     <Folder Include="Schema2\Generated\" />
     <Folder Include="Schema2\Generated\" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+    <PackageReference Include="OneOf" Version="3.0.263" />
+  </ItemGroup>
+
 </Project>
 </Project>

+ 0 - 16
src/SharpGLTF.Cesium/Validator.cs

@@ -1,16 +0,0 @@
-using SharpGLTF.Schema2;
-
-namespace SharpGLTF
-{
-    internal static class Validator
-    {
-        internal static void ValidateAccessor(ModelRoot model, Accessor accessor)
-        {
-            Guard.NotNull(accessor, nameof(accessor));
-            Guard.MustShareLogicalParent(model, "this", accessor, nameof(accessor));
-            Guard.IsTrue(accessor.Encoding == EncodingType.UNSIGNED_INT, nameof(accessor));
-            Guard.IsTrue(accessor.Dimensions == DimensionType.SCALAR, nameof(accessor));
-            Guard.IsFalse(accessor.Normalized, nameof(accessor));
-        }
-    }
-}

+ 2 - 3
tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs

@@ -4,7 +4,6 @@ using SharpGLTF.Schema2;
 using SharpGLTF.Transforms;
 using SharpGLTF.Transforms;
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text.Json.Nodes;
 using System.Text.Json.Nodes;
 
 
@@ -39,8 +38,8 @@ namespace SharpGLTF.Cesium
                     WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":1}"));
                     WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":1}"));
 
 
 
 
-            var featureId0 = new MeshExtInstanceFeatureID(2, 0, 0, "Forests", 2);
-            var featureId1 = new MeshExtInstanceFeatureID(9, propertyTable: 1, label: "Trees");
+            var featureId0 = new MeshExtInstanceFeatureID(2, 0, label: "Forests");
+            var featureId1 = new MeshExtInstanceFeatureID(9, label: "Trees");
 
 
             var featureIds = new List<MeshExtInstanceFeatureID>() { featureId0, featureId1 };
             var featureIds = new List<MeshExtInstanceFeatureID>() { featureId0, featureId1 };
 
 

+ 3 - 10
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -14,7 +14,6 @@ namespace SharpGLTF.Cesium
 {
 {
     using VBTexture1 = VertexBuilder<VertexPosition, VertexTexture1, VertexEmpty>;
     using VBTexture1 = VertexBuilder<VertexPosition, VertexTexture1, VertexEmpty>;
 
 
-
     [Category("Toolkit.Scenes")]
     [Category("Toolkit.Scenes")]
     public class ExtMeshFeaturesTests
     public class ExtMeshFeaturesTests
     {
     {
@@ -37,9 +36,9 @@ namespace SharpGLTF.Cesium
             var prim = mesh.UsePrimitive(material);
             var prim = mesh.UsePrimitive(material);
 
 
             // All the vertices in the triangle have the same feature ID
             // All the vertices in the triangle have the same feature ID
-            var vt0 = GetVertexBuilderWithFeatureId(new Vector3(-10, 0, 0), new Vector3(0, 0, 1), featureId);
-            var vt1 = GetVertexBuilderWithFeatureId(new Vector3(10, 0, 0), new Vector3(0, 0, 1), featureId);
-            var vt2 = GetVertexBuilderWithFeatureId(new Vector3(0, 10, 0), new Vector3(0, 0, 1), featureId);
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(-10, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(10, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 10, 0), new Vector3(0, 0, 1), featureId);
 
 
             prim.AddTriangle(vt0, vt1, vt2);
             prim.AddTriangle(vt0, vt1, vt2);
             var scene = new SceneBuilder();
             var scene = new SceneBuilder();
@@ -135,11 +134,5 @@ namespace SharpGLTF.Cesium
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.plotly");
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.plotly");
         }
         }
 
 
-        private static VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty> GetVertexBuilderWithFeatureId(Vector3 position, Vector3 normal, int featureid)
-        {
-            var vp0 = new VertexPositionNormal(position, normal);
-            var vb0 = new VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>(vp0, featureid);
-            return vb0;
-        }
     }
     }
 }
 }

+ 2 - 6
tests/SharpGLTF.Cesium.Tests/CesiumTests.cs → tests/SharpGLTF.Cesium.Tests/ExtPrimitiveOutlineTests.cs

@@ -1,10 +1,6 @@
-using System;
-using System.Linq;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
-using System.Collections.Generic;
-
 using NUnit.Framework;
 using NUnit.Framework;
-
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
@@ -16,7 +12,7 @@ namespace SharpGLTF
 {
 {
 
 
     [Category("Cesium")]
     [Category("Cesium")]
-    public partial class CesiumTests
+    public partial class ExtPrimitiveOutlineTests
     {
     {
         [SetUp]
         [SetUp]
         public void SetUp()
         public void SetUp()

File diff suppressed because it is too large
+ 30 - 0
tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs


+ 128 - 0
tests/SharpGLTF.Cesium.Tests/Memory/BinaryTableTests.cs

@@ -0,0 +1,128 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace SharpGLTF.Memory
+{
+    public class BinaryTableTests
+    {
+        [Test]
+        public void ConvertListofListofIntToBytes()
+        {
+            var values = new List<List<int>>();
+            values.Add(new List<int>() { 0, 1 });
+            values.Add(new List<int>() { 2, 3 });
+            var bytes = BinaryTable.GetBytesForArray(values);
+            Assert.That(bytes.Count, Is.EqualTo(BinaryTable.GetSize<int>() * 4));
+        }
+
+        [Test]
+        public void ConvertVector2ToBytes()
+        {
+            var values = new List<System.Numerics.Vector2>();
+            values.Add(new System.Numerics.Vector2(0, 1));
+            values.Add(new System.Numerics.Vector2(2, 3));
+            var bytes = BinaryTable.GetBytes(values);
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<System.Numerics.Vector2>() * 2));
+        }
+
+        [Test]
+        public void ConvertVector3ToBytes()
+        {
+            var values = new List<System.Numerics.Vector3>();
+            values.Add(new System.Numerics.Vector3(0, 1, 2));
+            values.Add(new System.Numerics.Vector3(3, 4, 5));
+            var bytes = BinaryTable.GetBytes(values);
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<System.Numerics.Vector3>() * 2));
+        }
+
+        [Test]
+        public void ConvertVector4ToBytes()
+        {
+            var values = new List<System.Numerics.Vector4>();
+            values.Add(new System.Numerics.Vector4(0, 1, 2, 3));
+            values.Add(new System.Numerics.Vector4(4, 5, 6, 7));
+            var bytes = BinaryTable.GetBytes(values);
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<System.Numerics.Vector4>() * 2));
+        }
+
+        [Test]
+        public void ConvertMatrix4x4ToBytes()
+        {
+            // create list of identity matrices
+            var values = new List<System.Numerics.Matrix4x4>();
+            values.Add(System.Numerics.Matrix4x4.Identity);
+
+            // convert to bytes
+            var bytes = BinaryTable.GetBytes(values);
+
+            // check size
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<System.Numerics.Matrix4x4>()));
+
+        }
+
+        [Test]
+        public void TestGetArrayOffset()
+        {
+            // arrange
+            var list0 = new List<string>() { "hello", "world!" };
+            var list1 = new List<string>() { "test", "testtest" };
+            var arrays = new List<List<string>>() { list0, list1 };
+
+            // act
+            var arrayOffsets = BinaryTable.GetArrayOffsets(arrays);
+            var stringOffsets = BinaryTable.GetStringOffsets(arrays);
+
+            // assert
+            Assert.That(arrayOffsets.Count, Is.EqualTo(arrays.Count + 1));
+            Assert.That(arrayOffsets[0], Is.EqualTo(0));
+            var l0 = list0.Count;
+            var l1 = list1.Count;
+
+            Assert.That(arrayOffsets[1], Is.EqualTo(l0));
+            Assert.That(arrayOffsets[2], Is.EqualTo(l0 + l1));
+
+            Assert.That(stringOffsets.Count, Is.EqualTo(list0.Count + list1.Count + 1));
+            Assert.That(stringOffsets[0], Is.EqualTo(0));
+            Assert.That(stringOffsets[1], Is.EqualTo(list0[0].Length));
+            Assert.That(stringOffsets[2], Is.EqualTo(list0[0].Length + list0[1].Length));
+            Assert.That(stringOffsets[3], Is.EqualTo(list0[0].Length + list0[1].Length + list1[0].Length));
+            Assert.That(stringOffsets[4], Is.EqualTo(list0[0].Length + list0[1].Length + list1[0].Length + list1[1].Length));
+        }
+
+        [Test]
+        public void TestBinaryConversion()
+        {
+            var bytes = BinaryTable.GetBytes(GetTestArray<byte>());
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<byte>() * 2));
+
+            bytes = BinaryTable.GetBytes(GetTestArray<int>());
+            Assert.That(bytes.Length, Is.EqualTo(BinaryTable.GetSize<int>() * 2));
+
+            bytes = BinaryTable.GetBytes(new List<string>() { "a", "b" });
+            Assert.That(bytes.Length, Is.EqualTo(2));
+
+            bytes = BinaryTable.GetBytes(new List<bool>() { true, false });
+            Assert.That(bytes.Length, Is.EqualTo(1));
+
+            var bits = new System.Collections.BitArray(bytes);
+            Assert.That(bits[0] == true);
+            Assert.That(bits[1] == false);
+
+            // test exception for datetime
+            var dates = new List<DateTime>();
+            dates.Add(new DateTime());
+            Assert.Throws<NotImplementedException>(() => BinaryTable.GetBytes(dates));
+        }
+
+        private static List<T> GetTestArray<T>()
+        {
+            var l = new List<T>();
+            l.Add((T)Convert.ChangeType(0, typeof(T), CultureInfo.InvariantCulture));
+            l.Add((T)Convert.ChangeType(1, typeof(T), CultureInfo.InvariantCulture));
+            return l;
+        }
+
+    }
+}

+ 33 - 0
tests/SharpGLTF.Cesium.Tests/VertexBuilder.cs

@@ -0,0 +1,33 @@
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using System.Numerics;
+
+namespace SharpGLTF
+{
+    public static class VertexBuilder
+    {
+        internal static VertexBuilder<VertexPositionNormal, VertexWithFeatureIds, VertexEmpty> GetVertexWithFeatureIds(Vector3 position, Vector3 normal, int featureId0, int featureId1)
+        {
+            var vertexWithFeatureIds = new VertexWithFeatureIds(featureId0, featureId1);
+
+            var vp0 = new VertexPositionNormal(position, normal);
+            var vb0 = new VertexBuilder<VertexPositionNormal, VertexWithFeatureIds, VertexEmpty>(vp0, vertexWithFeatureIds);
+            return vb0;
+        }
+
+        internal static VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty> GetVertexWithFeatureId(Vector3 position, Vector3 normal, int featureid)
+        {
+            var vp0 = new VertexPositionNormal(position, normal);
+            var vb0 = new VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>(vp0, featureid);
+            return vb0;
+        }
+
+        internal static VertexBuilder<VertexPosition, VertexPointcloud, VertexEmpty> GetVertexPointcloud(Vector3 position, Vector4 color, float intensity, float classification)
+        {
+            var vertexPointcloud = new VertexPointcloud(color, intensity, classification);
+            var vp0 = new VertexPosition(position);
+            var vb0 = new VertexBuilder<VertexPosition, VertexPointcloud, VertexEmpty>(vp0, vertexPointcloud);
+            return vb0;
+        }
+    }
+}

+ 92 - 0
tests/SharpGLTF.Cesium.Tests/VertexPointcloud.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF
+{
+    #if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
+    #endif
+
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕:{TexCoord}")]
+    public struct VertexPointcloud : IVertexCustom
+    {
+        public VertexPointcloud(Vector4 color, float intensity, float classification)
+        {
+            Color = color;
+            Intensity = intensity;
+            Classification = classification;
+        }
+
+        public const string INTENSITYATTRIBUTENAME = "_INTENSITY";
+        public const string CLASSIFICATIONATTRIBUTENAME = "_CLASSIFICATION";
+
+        [VertexAttribute("COLOR_0", EncodingType.UNSIGNED_BYTE, true)]
+        public Vector4 Color;
+
+        [VertexAttribute(INTENSITYATTRIBUTENAME, EncodingType.FLOAT, false)]
+        public float Intensity;
+        
+        [VertexAttribute(CLASSIFICATIONATTRIBUTENAME, EncodingType.FLOAT, false)]
+        public float Classification;
+
+        public int MaxColors => 1;
+
+        public int MaxTextCoords => 0;
+
+        public IEnumerable<string> CustomAttributes => throw new NotImplementedException();
+
+        void IVertexMaterial.SetColor(int setIndex, Vector4 color) {
+            if (setIndex == 0) Color = color;
+        }
+
+        public void SetTexCoord(int setIndex, Vector2 coord) { }
+
+        public Vector4 GetColor(int index) {
+            return Color;
+        }
+
+        public Vector2 GetTexCoord(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
+
+        public void Validate() { }
+
+        public object GetCustomAttribute(string attributeName)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool TryGetCustomAttribute(string attributeName, out object value)
+        {
+            if (attributeName == INTENSITYATTRIBUTENAME) {
+                value = Intensity; return true;
+            }
+            else if(attributeName == CLASSIFICATIONATTRIBUTENAME)
+            {
+                value = Classification; return true;
+            }
+            else
+            {
+                value = null; return false;
+            }
+        }
+
+        public void SetCustomAttribute(string attributeName, object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Add(in VertexMaterialDelta delta)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 2 - 2
tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs

@@ -51,9 +51,9 @@ namespace SharpGLTF
             return attributeName == CUSTOMATTRIBUTENAME ? (Object)BatchId : null;
             return attributeName == CUSTOMATTRIBUTENAME ? (Object)BatchId : null;
         }
         }
 
 
-        public bool TryGetCustomAttribute(string attribute, out object value)
+        public bool TryGetCustomAttribute(string attributeName, out object value)
         {
         {
-            if (attribute != CUSTOMATTRIBUTENAME) { value = null; return false; }
+            if (attributeName != CUSTOMATTRIBUTENAME) { value = null; return false; }
             value = BatchId; return true;
             value = BatchId; return true;
         }
         }
 
 

+ 86 - 0
tests/SharpGLTF.Cesium.Tests/VertexWithFeatureIds.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF
+{
+    #if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
+    #endif
+
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕:{TexCoord}")]
+    public struct VertexWithFeatureIds : IVertexCustom
+    {
+        public VertexWithFeatureIds(float featureId0, float featureId1)
+        {
+            FeatureId0 = featureId0;
+            FeatureId1 = featureId1;
+        }
+
+        public const string FEATUREID0ATTRIBUTENAME = "_FEATURE_ID_0";
+        public const string FEATUREID1ATTRIBUTENAME = "_FEATURE_ID_1";
+
+        [VertexAttribute(FEATUREID0ATTRIBUTENAME, EncodingType.FLOAT, false)]
+        public float FeatureId0;
+
+        [VertexAttribute(FEATUREID1ATTRIBUTENAME, EncodingType.FLOAT, false)]
+        public float FeatureId1;
+
+        public int MaxColors => 0;
+
+        public int MaxTextCoords => 0;
+
+        public IEnumerable<string> CustomAttributes => throw new NotImplementedException();
+
+        public void SetColor(int setIndex, Vector4 color) { }
+
+        public void SetTexCoord(int setIndex, Vector2 coord) { }
+
+        public Vector4 GetColor(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
+
+        public Vector2 GetTexCoord(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
+
+        public void Validate() { }
+
+        public object GetCustomAttribute(string attributeName)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool TryGetCustomAttribute(string attribute, out object value)
+        {
+            if (attribute == FEATUREID0ATTRIBUTENAME)
+            {
+                value = FeatureId0; return true;
+            }
+            else if (attribute == FEATUREID1ATTRIBUTENAME)
+            {
+                value = FeatureId1; return true;
+            }
+            else
+            {
+                value = null; return false;
+            }
+
+        }
+
+        public void SetCustomAttribute(string attributeName, object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Add(in VertexMaterialDelta delta)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

Some files were not shown because too many files changed in this diff