public override void OnImportAsset(AssetImportContext ctx)
    {
        XmlDocument doc = new XmlDocument();
        {
            String wholeDoc = File.ReadAllText(ctx.assetPath);
            // Datasmith doesn't properly escape & in texture filename XML elements, so we need to do that before we can pass the document to XmlDocument or it will throw a parse error.
            doc.LoadXml(System.Text.RegularExpressions.Regex.Replace(wholeDoc, @"&([^;]{8}?)", "&$1"));
        }

        XmlElement rootElement = doc.DocumentElement;

        Debug.Assert(rootElement.Name == "DatasmithUnrealScene");

        staticMeshElements = new Dictionary <string, UdsStaticMesh>();
        materialElements   = new Dictionary <string, UdsMaterial>();
        textureElements    = new Dictionary <string, UdsTexture>();
        actorMetadata      = new Dictionary <String, Dictionary <String, String> >();

        // Populate actor metadata dictionaries
        foreach (XmlNode metadataNode in rootElement.SelectNodes("child::MetaData"))
        {
            var m = Regex.Match(metadataNode.Attributes["reference"].Value, @"Actor\.(.*)$");
            if (!m.Success)
            {
                continue;
            }
            String actorId = m.Groups[1].Value;

            Dictionary <String, String> metadata = new Dictionary <string, string>();
            foreach (XmlNode kvpNode in metadataNode.SelectNodes("child::KeyValueProperty"))
            {
                metadata.Add(kvpNode.Attributes["name"].Value, kvpNode.Attributes["val"].Value);
            }
            actorMetadata.Add(actorId, metadata);
        }

        // Import textures
        foreach (XmlNode node in rootElement.SelectNodes("child::Texture"))
        {
            UdsTexture tex = UdsTexture.FromNode(ctx, node);
            if (tex != null)
            {
                textureElements.Add(tex.name, tex);
            }
        }

        // Import materials
        foreach (XmlNode node in rootElement.SelectNodes("child::MasterMaterial"))
        {
            UdsMaterial m = UdsMaterial.FromNode(ctx, node, textureElements);
            materialElements.Add(m.name, m);
        }

        // Import StaticMesh nodes and crossreference materials
        foreach (XmlNode node in rootElement.SelectNodes("child::StaticMesh"))
        {
            UdsStaticMesh m = UdsStaticMesh.FromNode(ctx, node);

            for (int materialIdx = 0; materialIdx < m.materialNames.Count; ++materialIdx)
            {
                UdsMaterial mat;
                if (!materialElements.TryGetValue(m.materialNames[materialIdx], out mat))
                {
                    ctx.LogImportError(String.Format("Can't resolve Material ref \"{0}\"", m.materialNames[materialIdx]));
                }
                m.materialRefs.Add(mat);
            }

            staticMeshElements.Add(m.name, m);
        }


        GameObject sceneRoot = new GameObject("datasmithRoot");

        sceneRoot.transform.rotation = Quaternion.Euler(90.0f, 0.0f, 0.0f); // Convert Revit's Z-up orientation to Unity's Y-up orientation
        ctx.AddObjectToAsset("datasmithRoot", sceneRoot);
        ctx.SetMainObject(sceneRoot);

        ImportActorChildren(ctx, sceneRoot, rootElement);

        // cleanup (TODO not sure if this is required / what the lifecycle of this object looks like)
        staticMeshElements = null;
        materialElements   = null;
        textureElements    = null;
        actorMetadata      = null;
    }
        public static UdsMaterial FromNode(AssetImportContext ctx, XmlNode node, Dictionary <String, UdsTexture> textureElements)
        {
            Debug.Assert(node.Name == "MasterMaterial");
            UdsMaterial m = new UdsMaterial();

            m.assetRef      = new Material(Shader.Find("Standard"));
            m.assetRef.name = node.Attributes["label"].Value;
            m.name          = node.Attributes["name"].Value;

            /*
             *      <KeyValueProperty name="DiffuseColor" type="Color" val="(R=0.784314,G=0.619608,B=0.243137,A=1.000000)"/>
             *    <KeyValueProperty name="DiffuseMapFading" type="Float" val="0.350000"/>                                                       // mix between DiffuseColor and DiffuseMap?
             *    <KeyValueProperty name="DiffuseMap" type="Texture" val="woods_and_plastics_finish_carpentry_wood_paneling_1_0"/>
             *    <KeyValueProperty name="DiffuseMap_UVOffsetX" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="DiffuseMap_UVOffsetY" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="DiffuseMap_UVScaleX" type="Float" val="0.169333"/>
             *    <KeyValueProperty name="DiffuseMap_UVScaleY" type="Float" val="0.169333"/>
             *    <KeyValueProperty name="DiffuseMap_UVWAngle" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="TintEnabled" type="Bool" val="True"/>
             *    <KeyValueProperty name="TintColor" type="Color" val="(R=0.172549,G=0.172549,B=0.172549,A=1.000000)"/>
             *    <KeyValueProperty name="SelfIlluminationLuminance" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="SelfIlluminationFilter" type="Color" val="(R=1.000000,G=1.000000,B=1.000000,A=1.000000)"/>
             *    <KeyValueProperty name="SelfIlluminationMapEnable" type="Bool" val="False"/>
             *    <KeyValueProperty name="BumpAmount" type="Float" val="9.310000"/>
             *    <KeyValueProperty name="BumpMap" type="Texture" val="woods_and_plastics_finish_carpentry_wood_paneling_1_bump_0"/>
             *    <KeyValueProperty name="BumpMap_UVOffsetX" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="BumpMap_UVOffsetY" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="BumpMap_UVScaleX" type="Float" val="0.169333"/>
             *    <KeyValueProperty name="BumpMap_UVScaleY" type="Float" val="0.169333"/>
             *    <KeyValueProperty name="BumpMap_UVWAngle" type="Float" val="0.000000"/>
             *    <KeyValueProperty name="IsMetal" type="Bool" val="False"/>
             *    <KeyValueProperty name="Glossiness" type="Float" val="0.000000"/>
             *
             * if (Type == 2) {
             *  <KeyValueProperty name="Transparency" type="Float" val="0.850000"/>
             *        <KeyValueProperty name="TransparencyMapFading" type="Float" val="0.000000"/>
             *        <KeyValueProperty name="RefractionIndex" type="Float" val="1.010000"/>
             * }
             *
             */

            foreach (XmlNode kvpNode in node.ChildNodes)
            {
                if (kvpNode.Name != "KeyValueProperty")
                {
                    continue;
                }
                String keyName = kvpNode.Attributes["name"].Value;
                if (keyName == "DiffuseColor")
                {
                    Color kvpColor = ParseKVPColor(kvpNode);
                    m.assetRef.color = new Color(kvpColor.r, kvpColor.g, kvpColor.b, m.assetRef.color.a); // Alpha channel is preserved since we set it from the "Transparency" key
                }
                else if (keyName == "DiffuseMap")
                {
                    Debug.Assert(kvpNode.Attributes["type"].Value == "Texture");
                    UdsTexture texRef;
                    textureElements.TryGetValue(kvpNode.Attributes["val"].Value, out texRef);
                    if (texRef == null)
                    {
                        ctx.LogImportError(String.Format("Missing diffuse texref \"{0}\" while assembling material node \"{1}\"", kvpNode.Attributes["val"].Value, m.name));
                    }
                    else
                    {
                        Vector2 uvOffset, uvScale;
                        FillUVOffsetScaleParameters(node, "DiffuseMap", out uvOffset, out uvScale);

                        m.assetRef.SetTexture("_MainTex", texRef.assetRef);
                        m.assetRef.SetTextureOffset("_MainTex", uvOffset);
                        m.assetRef.SetTextureScale("_MainTex", uvScale);
                    }
                }
                else if (keyName == "BumpMap")
                {
                    Debug.Assert(kvpNode.Attributes["type"].Value == "Texture");
                    UdsTexture texRef;
                    textureElements.TryGetValue(kvpNode.Attributes["val"].Value, out texRef);
                    if (texRef == null)
                    {
                        ctx.LogImportError(String.Format("Missing bump texref \"{0}\" while assembling material node \"{1}\"", kvpNode.Attributes["val"].Value, m.name));
                    }
                    else
                    {
                        Vector2 uvOffset, uvScale;
                        FillUVOffsetScaleParameters(node, "BumpMap", out uvOffset, out uvScale);

                        if (!texRef.importer.convertToNormalmap)
                        {
                            // Update importer config for this texture to convert it to a normal map
                            texRef.importer.textureType        = TextureImporterType.NormalMap;
                            texRef.importer.convertToNormalmap = true;
                            texRef.importer.SaveAndReimport();
                        }

                        m.assetRef.SetTexture("_BumpMap", texRef.assetRef);
                        m.assetRef.SetTextureOffset("_BumpMap", uvOffset);
                        m.assetRef.SetTextureScale("_BumpMap", uvScale);
                        m.assetRef.EnableKeyword("_NORMALMAP"); // ref: https://docs.unity3d.com/Manual/materials-scripting-standard-shader.html
                    }
                }
                else if (keyName == "BumpAmount")
                {
                    m.assetRef.SetFloat("_BumpScale", Math.Min(Single.Parse(kvpNode.Attributes["val"].Value), 1.0f));
                }
                else if (keyName == "Transparency")
                {
                    float transparency = Single.Parse(kvpNode.Attributes["val"].Value);
                    if (transparency > 0.0f)
                    {
                        // Set up the material for the "Transparent" transparency mode.
                        // This code is borrowed from StandardShaderGUI.cs:365 in the Unity 2018.4.20 builtin shaders package
                        m.assetRef.SetInt("_Mode", 3); // StandardShader.BlendMode enum: {Opaque=0, Cutout=1, Fade=2, Transparent=3}
                        m.assetRef.SetOverrideTag("RenderType", "Transparent");
                        m.assetRef.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                        m.assetRef.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                        m.assetRef.SetInt("_ZWrite", 0);
                        m.assetRef.DisableKeyword("_ALPHATEST_ON");
                        m.assetRef.DisableKeyword("_ALPHABLEND_ON");
                        m.assetRef.EnableKeyword("_ALPHAPREMULTIPLY_ON");
                        m.assetRef.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                        // End borrowed setup code

                        Color c = m.assetRef.color;
                        c.a = 1.0f - transparency;
                        m.assetRef.color = c;
                    }
                }
                else if (keyName == "IsMetal")
                {
                    m.assetRef.SetFloat("_Metallic", Boolean.Parse(kvpNode.Attributes["val"].Value) ? 1.0f : 0.0f);
                }
                else if (keyName == "Glossiness")
                {
                    m.assetRef.SetFloat("_Glossiness", Single.Parse(kvpNode.Attributes["val"].Value));
                }
                else if (keyName == "SelfIlluminationLuminance")
                {
                    float emissiveLuma = Single.Parse(kvpNode.Attributes["val"].Value);
                    if (emissiveLuma > 0.0f)
                    {
                        // Emissive surfaces seem to mostly be used for light fixture glass. Those components also have lights attached,
                        // so we don't need the emissive component to contribute to global illumination.
                        m.assetRef.EnableKeyword("_EMISSION");
                        m.assetRef.globalIlluminationFlags = MaterialGlobalIlluminationFlags.None;
                        m.assetRef.SetColor("_EmissionColor", new Color(emissiveLuma, emissiveLuma, emissiveLuma, emissiveLuma));
                    }
                }
            } // Kvp node loop

            ctx.AddObjectToAsset(m.name, m.assetRef);

            return(m);
        }