private void OpenConfiguration()
        {
            m_CurrentPanelView = PanelView.Configuration;

            if (m_MainCacheMaterials == null || m_CurrentMeshACIndex < 0 || m_CurrentMeshACIndex >= m_MainCacheMaterials.Count)
            {
                return;
            }
            Material mat    = m_MainCacheMaterials[m_CurrentMeshACIndex];
            Shader   shader = mat.shader;

            if (ShaderMetaDataUtility.IsValidShader(shader))
            {
#pragma warning disable 0618
                // Data conversion between Polybrush Beta and Polybrush 1.0.
                string path = ShaderMetaDataUtility.FindPolybrushMetaDataForShader(shader);
                if (!string.IsNullOrEmpty(path))
                {
                    ShaderMetaDataUtility.ConvertMetaDataToNewFormat(shader);
                }
#pragma warning restore 0618

                m_LoadedAttributes = ShaderMetaDataUtility.LoadShaderMetaData(shader);
            }
        }
Ejemplo n.º 2
0
        // Called when the mouse begins hovering an editable object.
        internal override void OnBrushEnter(EditableObject target, BrushSettings settings)
        {
            base.OnBrushEnter(target, settings);

            if (target.editMesh == null)
            {
                return;
            }

            bool refresh = (m_CacheTarget != null && !m_CacheTarget.Equals(target)) || m_CacheTarget == null;

            if (m_CacheTarget != null && m_CacheTarget.Equals(target.gameObjectAttached))
            {
                var targetMaterials = target.gameObjectAttached.GetMaterials();
                refresh = !AreListsEqual(targetMaterials, m_CacheMaterials);
            }

            if (refresh)
            {
                m_CacheTarget             = target;
                m_CacheMaterials          = target.gameObjectAttached.GetMaterials();
                m_MeshAttributesContainer = null;
                currentMeshACIndex        = 0;
                ArrayUtility.Clear(ref m_AvailableMaterialsAsString);
                m_LikelySupportsTextureBlending = CheckForTextureBlendSupport(target.gameObjectAttached);
                RebuildCaches(target.editMesh);
            }

            if (m_LikelySupportsTextureBlending && (brushColor == null || !brushColor.MatchesAttributes(meshAttributes)))
            {
                brushColor = new SplatWeight(SplatWeight.GetChannelMap(meshAttributes));
                SetBrushColorWithAttributeIndex(selectedAttributeIndex);
            }
            RebuildColorTargets(brushColor, settings.strength);
        }
 private void ReloadJson()
 {
     editor    = null;
     container = null;
     modified  = false;
     AssetDatabase.Refresh();
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Test a gameObject and it's mesh renderers for compatible shaders, and if one is found
        /// load it's attribute data into meshAttributes.
        /// </summary>
        /// <param name="go">The GameObject being checked for texture blend support</param>
        /// <returns></returns>
        internal bool CheckForTextureBlendSupport(GameObject go)
        {
            AttributeLayoutContainer detectedMeshAttributes;
            bool supports  = false;
            var  materials = Util.GetMaterials(go);

            m_MeshAttributesContainers.Clear();
            Material   mat;
            List <int> indexes = new List <int>();

            for (int i = 0; i < materials.Count; i++)
            {
                mat = materials[i];
                if (PolyShaderUtil.GetMeshAttributes(mat, out detectedMeshAttributes))
                {
                    m_MeshAttributesContainers.Add(detectedMeshAttributes);
                    indexes.Add(i);
                    supports = true;
                }
            }
            if (supports)
            {
                m_MeshAttributesContainer = m_MeshAttributesContainers.First();
                foreach (int i in indexes)
                {
                    ArrayUtility.Add <string>(ref m_AvailableMaterialsAsString, materials[i].name);
                }
            }
            return(supports);
        }
Ejemplo n.º 5
0
        internal override void OnEnable()
        {
            base.OnEnable();

            m_CurrentPanelView = PanelView.Paint;

            m_LikelySupportsTextureBlending = false;
            m_MeshAttributesContainer       = null;
            brushColor = null;

            if (meshAttributes != null)
            {
                OnMaterialSelected();
            }

            foreach (GameObject go in Selection.gameObjects)
            {
                m_LikelySupportsTextureBlending = CheckForTextureBlendSupport(go);

                if (m_LikelySupportsTextureBlending)
                {
                    break;
                }
            }
        }
Ejemplo n.º 6
0
        private void CloseConfiguration(bool saveOnDisk)
        {
            if (saveOnDisk)
            {
                Material mat    = m_CacheMaterials[currentMeshACIndex];
                Shader   shader = mat.shader;

                ShaderMetaDataUtility.SaveShaderMetaData(shader, m_LoadedAttributes);
                foreach (GameObject go in Selection.gameObjects)
                {
                    m_LikelySupportsTextureBlending = CheckForTextureBlendSupport(go);

                    if (m_LikelySupportsTextureBlending)
                    {
                        break;
                    }
                }

                if (m_CacheTarget != null)
                {
                    RebuildCaches(m_CacheTarget.editMesh);
                }
            }

            m_LoadedAttributes = null;
            m_CurrentPanelView = PanelView.Paint;
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Store user-set shader attribute information.
        /// </summary>
        /// <param name="container">container that will have the shader and the metadata to write</param>
        /// <param name="overwrite">overwrite data if already existing</param>
        /// <param name="logErrors">log errors or not</param>
        /// <returns>Returns the path written to on success, null otherwise.</returns>
        internal static string SaveMeshAttributesData(AttributeLayoutContainer container, bool overwrite = false, bool logErrors = true)
        {
            if (container == null)
            {
                return(string.Empty);
            }

            return(SaveMeshAttributesData(container.shader, container.attributes, overwrite));
        }
Ejemplo n.º 8
0
        public static bool TryReadAttributeLayoutsFromJson(string jsonText, out AttributeLayoutContainer container)
        {
            container = ScriptableObject.CreateInstance <AttributeLayoutContainer>();
            JsonUtility.FromJsonOverwrite(jsonText, container);

            ResolveShaderReference(container);

            return(true);
        }
Ejemplo n.º 9
0
        public void LoadShaderMetaData_ValidShaderWithoutMeta_ReturnsAttributes()
        {
            Shader shaderWithMeta = Shader.Find(k_PathToShaderWithNoMeta);

            Assume.That(shaderWithMeta != null);

            AttributeLayoutContainer attributes = ShaderMetaDataUtility.LoadShaderMetaData(shaderWithMeta);

            Assert.IsNotNull(attributes);
            Assert.IsNull(attributes.attributes);
        }
 void RebuildMaterialCaches()
 {
     ArrayUtility.Clear(ref m_AvailableMaterialsAsString);
     m_CurrentMeshACIndex = 0;
     m_MainCacheMaterials.Clear();
     if (m_MainCacheTarget == null)
     {
         return;
     }
     m_MeshAttributesContainer       = null;
     m_CurrentMeshACIndex            = 0;
     m_LikelySupportsTextureBlending = CheckForTextureBlendSupport(m_MainCacheTarget.gameObjectAttached);
 }
        void OnEnable()
        {
            if (target == null)
            {
                GameObject.DestroyImmediate(this);
                return;
            }

            AttributeLayoutContainer container = target as AttributeLayoutContainer;

            shader = container.shader;

            p_attributes = serializedObject.FindProperty("attributes");
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Deserialize the shader's attributes from UserData in the shader's importer.
        /// If none exists, it returns a new AttributeLayoutContaine instance.
        /// </summary>
        /// <param name="shader"></param>
        /// <returns></returns>
        internal static AttributeLayoutContainer LoadShaderMetaData(Shader shader)
        {
            if (shader == null)
            {
                throw new ArgumentNullException("shader");
            }

            string        path     = AssetDatabase.GetAssetPath(shader);
            AssetImporter importer = AssetImporter.GetAtPath(path);

            AttributeLayoutContainer data = AttributeLayoutContainer.Create(shader, null);

            JsonUtility.FromJsonOverwrite(importer.userData, data);
            return(data);
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Serialize the shader's attributes as UserData in the shader's importer.
        /// </summary>
        /// <param name="shader"></param>
        /// <param name="attributes"></param>
        internal static void SaveShaderMetaData(Shader shader, AttributeLayoutContainer attributes)
        {
            if (shader == null)
            {
                throw new ArgumentNullException("shader");
            }

            if (attributes == null)
            {
                throw new ArgumentNullException("attributes");
            }

            string        path     = AssetDatabase.GetAssetPath(shader);
            AssetImporter importer = AssetImporter.GetAtPath(path);

            importer.userData = JsonUtility.ToJson(attributes);
            importer.SaveAndReimport();
        }
Ejemplo n.º 14
0
        internal override void OnEnable()
        {
            base.OnEnable();

            m_LikelySupportsTextureBlending = false;
            m_MeshAttributesContainer       = null;
            brushColor = null;

            foreach (GameObject go in Selection.gameObjects)
            {
                m_LikelySupportsTextureBlending = CheckForTextureBlendSupport(go);

                if (m_LikelySupportsTextureBlending)
                {
                    break;
                }
            }
        }
        /// <summary>
        /// Test a gameObject and it's mesh renderers for compatible shaders, and if one is found
        /// load it's attribute data into meshAttributes.
        /// </summary>
        /// <param name="go">The GameObject being checked for texture blend support</param>
        /// <returns></returns>
        internal bool CheckForTextureBlendSupport(GameObject go)
        {
            bool supports  = false;
            var  materials = Util.GetMaterials(go);

            m_MeshAttributesContainers.Clear();
            Material   mat;
            List <int> indexes = new List <int>();

            for (int i = 0; i < materials.Count; i++)
            {
                mat = materials[i];
                if (ShaderMetaDataUtility.IsValidShader(mat.shader))
                {
                    AttributeLayoutContainer detectedMeshAttributes = ShaderMetaDataUtility.LoadShaderMetaData(mat.shader);
                    {
                        if (detectedMeshAttributes != null)
                        {
                            m_MeshAttributesContainers.Add(detectedMeshAttributes);
                            indexes.Add(i);
                            m_MainCacheMaterials.Add(mat);
                            supports = true;
                        }
                    }
                }
            }

            if (supports)
            {
                m_MeshAttributesContainer = m_MeshAttributesContainers.First();
                foreach (int i in indexes)
                {
                    ArrayUtility.Add <string>(ref m_AvailableMaterialsAsString, materials[i].name);
                }
            }

            if (meshAttributes == null)
            {
                supports = false;
            }

            return(supports);
        }
Ejemplo n.º 16
0
        private static void TestMeshAttributeContainer(AttributeLayoutContainer meshAttributes, Material mat)
        {
            Assert.IsNotNull(meshAttributes);
            Assert.IsTrue(meshAttributes.shader == mat.shader);
            //test textures preview and default values
            for (int i = 0; i < meshAttributes.attributes.Length; i++)
            {
                AttributeLayout attributeLayout = meshAttributes.attributes[i];

                //preview
                Texture attributeTexture = mat.GetTexture(attributeLayout.propertyTarget);
                Assert.IsTrue(attributeTexture == attributeLayout.previewTexture);
                //default
                Assert.IsTrue(Enum.IsDefined(typeof(MeshChannel), attributeLayout.channel));
                Assert.IsTrue(attributeLayout.min == 0);
                Assert.IsTrue(attributeLayout.max == 1);
                //if the index is part of the enum ComponentIndex
                Assert.IsTrue(Enum.IsDefined(typeof(ComponentIndex), attributeLayout.index));
            }
        }
Ejemplo n.º 17
0
        public void ConvertMetaDataToNewFormat_ArgumentValid_NoException()
        {
            if (!Directory.Exists(k_AssetShaderFolder))
            {
                Directory.CreateDirectory(k_AssetShaderFolder);
            }

            // Setup environment for this specific tests.
            FileUtil.CopyFileOrDirectory(k_FilePathToShaderWithOldMetaFormat, k_DestFilePathToShaderWithOldMetaFormat);
            FileUtil.CopyFileOrDirectory(k_FilePathToShaderWithOldMetaFormatPBS, k_DestFilePathToShaderWithOldMetaFormatPBS);
            AssetDatabase.Refresh();

            Shader shader = Shader.Find(k_PathToShaderWithOldMetaFormat);

            Assume.That(shader != null);

#pragma warning disable 0618
            // We should find a .pbs.json file for the shader.
            Assert.IsFalse(String.IsNullOrEmpty(ShaderMetaDataUtility.FindPolybrushMetaDataForShader(shader)));

            // Convert the data from .pbs.json to meta data.
            ShaderMetaDataUtility.ConvertMetaDataToNewFormat(shader);

            // After conversion, we shouldn't find a .pbs.json file for the shader.
            Assert.IsTrue(String.IsNullOrEmpty(ShaderMetaDataUtility.FindPolybrushMetaDataForShader(shader)));
#pragma warning restore 0618
            // Check tha
            AttributeLayoutContainer attributes = ShaderMetaDataUtility.LoadShaderMetaData(shader);
            Assert.IsNotNull(attributes);
            Assert.IsNotNull(attributes.attributes);

            // Clean up
            FileUtil.DeleteFileOrDirectory(k_DestFilePathToShaderWithOldMetaFormat);
            FileUtil.DeleteFileOrDirectory(k_DestFilePathToShaderWithOldMetaFormat + ".meta");
            FileUtil.DeleteFileOrDirectory(k_DestFilePathToShaderWithOldMetaFormatPBS);
            FileUtil.DeleteFileOrDirectory(k_DestFilePathToShaderWithOldMetaFormatPBS + ".meta");

            AssetDatabase.Refresh();
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Try to read AttributeLayouts from a .pbs.json file located at "path"
        /// </summary>
        /// <param name="path">Path of the file to read from</param>
        /// <param name="container">AttributeLayoutContainer retrieved from the json</param>
        /// <returns>true if it worked, false if the file doesn't exist or is empty</returns>
        public static bool TryReadAttributeLayoutsFromJsonFile(string path, out AttributeLayoutContainer container)
        {
            container = null;

            if (!File.Exists(path))
            {
                return(false);
            }

            string json = File.ReadAllText(path);

            if (string.IsNullOrEmpty(json))
            {
                return(false);
            }

            container = ScriptableObject.CreateInstance <AttributeLayoutContainer>();
            JsonUtility.FromJsonOverwrite(json, container);

            ResolveShaderReference(container);

            return(true);
        }
Ejemplo n.º 19
0
        /// <summary>
        /// Loads AttributeLayout data from a shader.  Checks for both legacy (define Z_TEXTURE_CHANNELS) and
        /// .pbs.json metadata.
        /// </summary>
        /// <param name="material"></param>
        /// <param name="attribContainer"></param>
        /// <returns></returns>
        internal static bool GetMeshAttributes(Material material, out AttributeLayoutContainer attribContainer)
        {
            attribContainer = null;

            if (material == null)
            {
                return(false);
            }

            // first search for json, then fall back on legacy
            if (ShaderMetaDataUtility.FindMeshAttributesForShader(material.shader, out attribContainer))
            {
                Dictionary <string, int> shaderProperties = new Dictionary <string, int>();

                for (int i = 0; i < ShaderUtil.GetPropertyCount(material.shader); i++)
                {
                    shaderProperties.Add(ShaderUtil.GetPropertyName(material.shader, i), i);
                }

                foreach (AttributeLayout a in attribContainer.attributes)
                {
                    int index = -1;

                    if (shaderProperties.TryGetValue(a.propertyTarget, out index))
                    {
                        if (ShaderUtil.GetPropertyType(material.shader, index) == ShaderUtil.ShaderPropertyType.TexEnv)
                        {
                            a.previewTexture = (Texture2D)material.GetTexture(a.propertyTarget);
                        }
                    }
                }

                return(true);
            }
            return(false);
        }
Ejemplo n.º 20
0
        /// <summary>
        /// Searches only by looking for a compatibly named file in the same directory.
        /// </summary>
        /// <param name="shader">Shader associated with the metadata</param>
        /// <param name="attributes">result if any metadata found</param>
        /// <returns></returns>
        internal static bool FindMeshAttributesForShader(Shader shader, out AttributeLayoutContainer attributes)
        {
            attributes = null;

            string path      = AssetDatabase.GetAssetPath(shader);
            string filename  = Path.GetFileNameWithoutExtension(path);
            string directory = Path.GetDirectoryName(path);

            string[] paths = new string[]
            {
                string.Format("{0}/{1}.{2}", directory, filename, SHADER_ATTRIB_FILE_EXTENSION),
                string.Format("{0}/{1}.{2}", directory, PolyShaderUtil.GetMetaDataPath(shader), SHADER_ATTRIB_FILE_EXTENSION)
            };

            foreach (string str in paths)
            {
                if (TryReadAttributeLayoutsFromJsonFile(str, out attributes))
                {
                    return(true);
                }
            }

            return(false);
        }
Ejemplo n.º 21
0
        /// <summary>
        /// Store the shader's attributes in the new format.
        /// Erase the .pbs.json on success.
        /// </summary>
        internal static void ConvertMetaDataToNewFormat(Shader shader)
        {
            if (shader == null)
            {
                throw new NullReferenceException("shader");
            }

            string path = ShaderMetaDataUtility.FindPolybrushMetaDataForShader(shader);

            // If not null, it means we have data stored with the old format.
            // Proceed to conversion.
            if (path != null)
            {
                AttributeLayoutContainer attributesContainer = ScriptableObject.CreateInstance <AttributeLayoutContainer>();
                ShaderMetaDataUtility.TryReadAttributeLayoutsFromJsonFile(path, out attributesContainer);
                if (attributesContainer != null)
                {
                    ShaderMetaDataUtility.SaveShaderMetaData(shader, attributesContainer);
                    FileUtil.DeleteFileOrDirectory(path);
                    FileUtil.DeleteFileOrDirectory(path + ".meta");
                    AssetDatabase.Refresh();
                }
            }
        }
Ejemplo n.º 22
0
        // Inspector GUI shown in the Editor window.  Base class shows BrushSettings by default
        internal override void DrawGUI(BrushSettings brushSettings)
        {
            base.DrawGUI(brushSettings);

            using (new GUILayout.HorizontalScope())
            {
                GUILayout.FlexibleSpace();
                paintMode = (PaintMode)GUILayout.Toolbar((int)paintMode, m_ModeIcons, GUILayout.Width(130));
                GUILayout.FlexibleSpace();
            }

            GUILayout.Space(4);

            if (!m_LikelySupportsTextureBlending)
            {
                EditorGUILayout.HelpBox("It doesn't look like any of the materials on this object support texture blending!\n\nSee the readme for information on creating custom texture blend shaders.", MessageType.Warning);
            }

            // Selection dropdown for material (for submeshes)
            if (m_AvailableMaterialsAsString.Count() > 0)
            {
                EditorGUILayout.BeginHorizontal();
                EditorGUI.BeginChangeCheck();

                EditorGUILayout.LabelField("Material :", GUILayout.Width(60));
                currentMeshACIndex = EditorGUILayout.Popup(currentMeshACIndex, m_AvailableMaterialsAsString, "Popup");

                if (EditorGUI.EndChangeCheck())
                {
                    m_MeshAttributesContainer = m_MeshAttributesContainers[currentMeshACIndex];
                }

                EditorGUILayout.EndHorizontal();
            }

            GUILayout.Space(4);

            if (meshAttributes != null)
            {
                RefreshPreviewTextureCache();
                int prevSelectedAttributeIndex = m_SelectedAttributeIndex;
                m_SelectedAttributeIndex = SplatWeightEditor.OnInspectorGUI(m_SelectedAttributeIndex, ref brushColor, meshAttributes);
                if (prevSelectedAttributeIndex != m_SelectedAttributeIndex)
                {
                    SetBrushColorWithAttributeIndex(m_SelectedAttributeIndex);
                }

#if POLYBRUSH_DEBUG
                GUILayout.BeginHorizontal();

                GUILayout.FlexibleSpace();

                if (GUILayout.Button("MetaData", EditorStyles.miniButton))
                {
                    Debug.Log(meshAttributes.ToString("\n"));

                    string str = EditorUtility.FindPolybrushMetaDataForShader(meshAttributesContainer.shader);

                    if (!string.IsNullOrEmpty(str))
                    {
                        TextAsset asset = AssetDatabase.LoadAssetAtPath <TextAsset>(str);

                        if (asset != null)
                        {
                            EditorGUIUtility.PingObject(asset);
                        }
                        else
                        {
                            Debug.LogWarning("No MetaData found for Shader \"" + meshAttributesContainer.shader.name + "\"");
                        }
                    }
                    else
                    {
                        Debug.LogWarning("No MetaData found for Shader \"" + meshAttributesContainer.shader.name + "\"");
                    }
                }

                GUILayout.EndHorizontal();

                GUILayout.Space(4);

                if (GUILayout.Button("rebuild  targets"))
                {
                    RebuildColorTargets(brushColor, brushSettings.strength);
                }


                GUILayout.Label(brushColor != null ? brushColor.ToString() : "brush color: null\n");
#endif
            }
        }
Ejemplo n.º 23
0
        // Inspector GUI shown in the Editor window.  Base class shows BrushSettings by default
        internal override void DrawGUI(BrushSettings brushSettings)
        {
            base.DrawGUI(brushSettings);

            using (new GUILayout.HorizontalScope())
            {
                GUILayout.FlexibleSpace();
                paintMode = (PaintMode)GUILayout.Toolbar((int)paintMode, Styles.k_ModeIcons, GUILayout.Width(150));
                GUILayout.FlexibleSpace();
            }

            GUILayout.Space(4);

            // Selection dropdown for material (for submeshes)
            if (m_AvailableMaterialsAsString.Count() > 0)
            {
                EditorGUILayout.BeginHorizontal();
                EditorGUI.BeginChangeCheck();

                EditorGUILayout.LabelField("Material :", GUILayout.Width(60));
                if (m_CurrentPanelView == PanelView.Configuration)
                {
                    GUI.enabled = false;
                }

                currentMeshACIndex = EditorGUILayout.Popup(currentMeshACIndex, m_AvailableMaterialsAsString, "Popup");

                if (m_CurrentPanelView == PanelView.Configuration)
                {
                    GUI.enabled = true;
                }

                // Buttons to switch between Paint and Configuration views
                if (m_CurrentPanelView == PanelView.Paint && GUILayout.Button("Configure", GUILayout.Width(70)))
                {
                    OpenConfiguration();
                }
                else if (m_CurrentPanelView == PanelView.Configuration)
                {
                    if (GUILayout.Button("Revert", GUILayout.Width(70)))
                    {
                        CloseConfiguration(false);
                    }
                    if (GUILayout.Button("Save", GUILayout.Width(70)))
                    {
                        CloseConfiguration(true);
                    }
                }

                if (EditorGUI.EndChangeCheck())
                {
                    m_MeshAttributesContainer = m_MeshAttributesContainers[currentMeshACIndex];
                    OnMaterialSelected();
                }

                EditorGUILayout.EndHorizontal();
            }

            EditorGUILayout.Space();

            if (m_CurrentPanelView == PanelView.Paint)
            {
                DrawGUIPaintView();
            }
            else if (m_CurrentPanelView == PanelView.Configuration)
            {
                Material selectedMat = m_CacheMaterials[currentMeshACIndex];

                string[] names = selectedMat.GetTexturePropertyNames();

                using (new GUILayout.VerticalScope())
                {
                    for (int i = 0; i < names.Length; ++i)
                    {
                        string n = names[i];
                        if (selectedMat.HasProperty(n))
                        {
                            DrawConfigurationPanel(GetPropertyInfo(n), n, selectedMat);
                        }
                    }
                }
            }
        }
Ejemplo n.º 24
0
 static void ResolveShaderReference(AttributeLayoutContainer container)
 {
     container.shader = Shader.Find(container.shaderPath);
 }
Ejemplo n.º 25
0
        /// <summary>
        /// Saves the metadata of the shader passed in parameters, can overwrite if necessary
        /// </summary>
        /// <param name="shader">Shader associated with the metadata</param>
        /// <param name="attributes">Metadata to write</param>
        /// <param name="overwrite">Will overwrite if already existing file</param>
        /// <param name="logErrors">Log errors or not</param>
        /// <returns></returns>
        internal static string SaveMeshAttributesData(Shader shader, AttributeLayout[] attributes, bool overwrite = false, bool logErrors = true)
        {
            if (shader == null || attributes == null)
            {
                if (logErrors)
                {
                    Debug.LogError("Cannot save null attributes for shader.");
                }

                return(null);
            }

            string path             = FindPolybrushMetaDataForShader(shader);
            string shader_path      = AssetDatabase.GetAssetPath(shader);
            string shader_directory = Path.GetDirectoryName(shader_path);
            string shader_filename  = Path.GetFileNameWithoutExtension(path);

            // metadata didn't exist before
            if (string.IsNullOrEmpty(path))
            {
                if (string.IsNullOrEmpty(shader_path))
                {
                    // how!?
                    path = EditorUtility.SaveFilePanelInProject(
                        "Save Polybrush Shader Attributes",
                        shader_filename,
                        SHADER_ATTRIB_FILE_EXTENSION,
                        "Please enter a file name to save Polybrush shader metadata to.");

                    if (string.IsNullOrEmpty(path))
                    {
                        Debug.LogWarning(string.Format("Could not save Polybrush shader metadata.  Please try again, possibly with a different file name or folder path."));
                        return(null);
                    }
                }
                else
                {
                    shader_filename = Path.GetFileNameWithoutExtension(shader_path);
                    path            = string.Format("{0}/{1}.{2}", shader_directory, shader_filename, SHADER_ATTRIB_FILE_EXTENSION);
                }
            }

            if (!overwrite && File.Exists(path))
            {
                // @todo
                Debug.LogWarning("shader metadata exists. calling function refuses to overwrite and lazy developer didn't add a save dialog here.");
                return(null);
            }

            try
            {
                AttributeLayoutContainer container = AttributeLayoutContainer.Create(shader, attributes);
                string json = JsonUtility.ToJson(container, true);
                File.WriteAllText(path, json);

                //note: convert it here to be able to load it using AssetDatabase functions
                shader_filename = Path.GetFileNameWithoutExtension(shader_path);
                path            = string.Format("{0}/{1}.{2}", shader_directory, shader_filename, SHADER_ATTRIB_FILE_EXTENSION);
                //-------

                return(path);
            }
            catch (System.Exception e)
            {
                if (logErrors)
                {
                    Debug.LogError("Failed saving Polybrush Shader MetaData\n" + e.ToString());
                }
                return(path);
            }
        }