diff --git a/Editor/Scripts/GLTFExportMenu.cs b/Editor/Scripts/GLTFExportMenu.cs index 924429fca..135aa6763 100644 --- a/Editor/Scripts/GLTFExportMenu.cs +++ b/Editor/Scripts/GLTFExportMenu.cs @@ -108,7 +108,7 @@ private static void ExportSceneGLB() private static void Export(Transform[] transforms, bool binary, string sceneName) { var settings = GLTFSettings.GetOrCreateSettings(); - var exportOptions = new ExportOptions { TexturePathRetriever = RetrieveTexturePath }; + var exportOptions = new ExportContext { TexturePathRetriever = RetrieveTexturePath }; var exporter = new GLTFSceneExporter(transforms, exportOptions); var invokedByShortcut = Event.current?.type == EventType.KeyDown; diff --git a/Editor/Scripts/GLTFImporter.cs b/Editor/Scripts/GLTFImporter.cs index 1ebadab32..ddf2eb28f 100644 --- a/Editor/Scripts/GLTFImporter.cs +++ b/Editor/Scripts/GLTFImporter.cs @@ -46,7 +46,7 @@ namespace UnityGLTF #else [ScriptedImporter(ImporterVersion, new[] { "glb" })] #endif - public class GLTFImporter : ScriptedImporter, IGLTFImportRemap + public class GLTFImporter : ScriptedImporter { private const int ImporterVersion = 9; @@ -236,7 +236,7 @@ public override void OnImportAsset(AssetImportContext ctx) if (plugin != null && plugin.Enabled) { var instance = plugin.CreateInstance(context); - if(instance != null) plugins.Add(instance); + if (instance != null) plugins.Add(instance); } } diff --git a/Editor/Scripts/Plugins/GltfPluginEditor.cs b/Editor/Scripts/Plugins/GltfPluginEditor.cs new file mode 100644 index 000000000..77b1987fd --- /dev/null +++ b/Editor/Scripts/Plugins/GltfPluginEditor.cs @@ -0,0 +1,21 @@ +using UnityEditor; +using UnityGLTF.Plugins; + +namespace UnityGLTF +{ + [CustomEditor(typeof(GltfPlugin), true)] + public class GltfPluginEditor: Editor + { + // Follows the default implementation of OnInspectorGUI, but skips the script field + public override void OnInspectorGUI() + { + serializedObject.Update(); + var iterator = serializedObject.GetIterator(); + // skip script field + iterator.NextVisible(true); + while (iterator.NextVisible(false)) + EditorGUILayout.PropertyField(iterator, true); + serializedObject.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Editor/Scripts/Plugins/GltfPluginEditor.cs.meta b/Editor/Scripts/Plugins/GltfPluginEditor.cs.meta new file mode 100644 index 000000000..c3ab18f28 --- /dev/null +++ b/Editor/Scripts/Plugins/GltfPluginEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5df1106545064293aebd7efc2fc87905 +timeCreated: 1703883677 \ No newline at end of file diff --git a/Editor/Scripts/ShaderGraph/MaterialEditorBridge.cs b/Editor/Scripts/ShaderGraph/MaterialEditorBridge.cs index e3d093cd5..fc1d6993b 100644 --- a/Editor/Scripts/ShaderGraph/MaterialEditorBridge.cs +++ b/Editor/Scripts/ShaderGraph/MaterialEditorBridge.cs @@ -23,7 +23,7 @@ private static void OnImmutableMaterialChanged(Material material) // var mainAsset = AssetDatabase.LoadMainAssetAtPath(assetPath); // Transform[] rootTransforms = null; - var exporter = new GLTFSceneExporter((Transform[]) null, new ExportOptions()); + var exporter = new GLTFSceneExporter((Transform[]) null, new ExportContext()); // load all materials from mainAsset var importer = AssetImporter.GetAtPath(assetPath) as GLTFImporter; if (!importer) return; diff --git a/Runtime/Scripts/Extensions/MaterialExtensions.cs b/Runtime/Scripts/Extensions/MaterialExtensions.cs index 7547a56ec..d5f05936d 100644 --- a/Runtime/Scripts/Extensions/MaterialExtensions.cs +++ b/Runtime/Scripts/Extensions/MaterialExtensions.cs @@ -2,23 +2,57 @@ using GLTF.Schema; using UnityEngine; using UnityGLTF.Extensions; +using UnityGLTF.Plugins; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityGLTF { - public static class MaterialExtensions + // When a plugin is registered with the default settings (the scriptable object in the project), + // it will be active "by default" when someone uses those default settings. + // e.g. it's used when someone uses the built-in editor methods for exporting objects. + // When using the API, one needs to manually register wanted plugins and configure them + // (can get the default settings and modify them). + + // Plugins can contain any number of extensions, but are encouraged to specify in the description + // which extensions are imported/exported with that plugin. + // Theoretically there could be multiple plugins operating on the same extension in different ways, in + // which case we currently can't warn about conflicts; they would all run. + // If plugins were required to list the extensions they operate on, we could warn about conflicts. + + // Plugins are ScriptableObjects which are added to the default GLTFSettings scriptable object. + // Their public serialized fields are exposed in the inspector, and they can be enabled/disabled. + // Plugins replace both GLTFSceneExporter.* static callbacks and GLTFSceneExporter.ExportOptions callbacks + // to allow for more control. + + // Example cases where separate plugins operate on the same data: + // - exporting UI as custom extension vs. baking UI to mesh + // - exporting Audio in a custom extension vs. using KHR_audio + // - exporting LODs as custom extension vs. using MSFT_lod + // - exporting particle systems as custom extension vs. baking to mesh + + // Plugins can either be added manually to ExportOptions.plugins / ImportContext.plugins + // or advertise themselves via a static callback which allows configuring their settings in the inspector. + // For each new instance of GLTFSceneExporter, new instances of plugins are created. + // For each new instance of GLTFSceneImporter, new instances of plugins are created. + + public class MaterialExtensionsPlugin: GltfExportPlugin { -#if UNITY_EDITOR - [InitializeOnLoadMethod] -#endif - [RuntimeInitializeOnLoadMethod] - static void InitExt() + public bool KHR_materials_ior = true; + public bool KHR_materials_transmission = true; + public bool KHR_materials_volume = true; + + public override GltfExportPluginContext CreateInstance(ExportContext context) { - GLTFSceneExporter.AfterMaterialExport += GLTFSceneExporterOnAfterMaterialExport; + return new MaterialExtensions(); } + public override string DisplayName => "Material Extensions"; + } + + public class MaterialExtensions: GltfExportPluginContext + { private static readonly int thicknessTexture = Shader.PropertyToID("thicknessTexture"); private static readonly int thicknessFactor = Shader.PropertyToID("thicknessFactor"); private static readonly int attenuationDistance = Shader.PropertyToID("attenuationDistance"); @@ -45,8 +79,7 @@ static void InitExt() private static readonly int clearcoatRoughnessTexture = Shader.PropertyToID("clearcoatRoughnessTexture"); private static readonly int clearcoatNormalTexture = Shader.PropertyToID("clearcoatNormalTexture"); - - private static void GLTFSceneExporterOnAfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfroot, Material material, GLTFMaterial materialnode) + public override void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfroot, Material material, GLTFMaterial materialnode) { if (!material) return; @@ -193,8 +226,6 @@ private static void GLTFSceneExporterOnAfterMaterialExport(GLTFSceneExporter exp cc.clearcoatRoughnessTexture = exporter.ExportTextureInfoWithTextureTransform(material, material.GetTexture(clearcoatRoughnessTexture), nameof(clearcoatRoughnessTexture)); if (material.HasProperty(clearcoatNormalTexture)) cc.clearcoatNormalTexture = exporter.ExportTextureInfoWithTextureTransform(material, material.GetTexture(clearcoatNormalTexture), nameof(clearcoatNormalTexture)); - - } } } diff --git a/Runtime/Scripts/GLTFSceneExporter.cs b/Runtime/Scripts/GLTFSceneExporter.cs index 3c294161c..58ec40c74 100644 --- a/Runtime/Scripts/GLTFSceneExporter.cs +++ b/Runtime/Scripts/GLTFSceneExporter.cs @@ -16,11 +16,11 @@ using Unity.Profiling; using UnityEngine; using UnityGLTF.Extensions; -using WrapMode = GLTF.Schema.WrapMode; +using UnityGLTF.Plugins; namespace UnityGLTF { - public class ExportOptions + public class ExportContext { public bool TreatEmptyRootAsScene = false; public bool MergeClipsWithMatchingNames = false; @@ -28,9 +28,9 @@ public class ExportOptions public ILogger logger; internal readonly GLTFSettings settings; - public ExportOptions() : this(GLTFSettings.GetOrCreateSettings()) { } + public ExportContext() : this(GLTFSettings.GetOrCreateSettings()) { } - public ExportOptions(GLTFSettings settings) + public ExportContext(GLTFSettings settings) { if (!settings) settings = GLTFSettings.GetOrCreateSettings(); if (settings.UseMainCameraVisibility) @@ -39,16 +39,71 @@ public ExportOptions(GLTFSettings settings) } public GLTFSceneExporter.RetrieveTexturePathDelegate TexturePathRetriever = (texture) => texture.name; + + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.AfterSceneExportDelegate AfterSceneExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.BeforeSceneExportDelegate BeforeSceneExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.AfterNodeExportDelegate AfterNodeExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.BeforeMaterialExportDelegate BeforeMaterialExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.AfterMaterialExportDelegate AfterMaterialExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.BeforeTextureExportDelegate BeforeTextureExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.AfterTextureExportDelegate AfterTextureExport; + [Obsolete("Register export plugins with GLTFSettings instead")] public GLTFSceneExporter.AfterPrimitiveExportDelegate AfterPrimitiveExport; + + internal GltfExportPluginContext GetImplicitPlugin() + { + return new ImplicitPlugin(this); + } + +#pragma warning disable CS0618 // Type or member is obsolete + internal class ImplicitPlugin : GltfExportPluginContext + { + private readonly ExportContext _exportContext; + + internal ImplicitPlugin(ExportContext context) + { + _exportContext = context; + } + + public override void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) => _exportContext.BeforeSceneExport?.Invoke(exporter, gltfRoot); + public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) => _exportContext.AfterSceneExport?.Invoke(exporter, gltfRoot); + public override void AfterNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, Node node) => _exportContext.AfterNodeExport?.Invoke(exporter, gltfRoot, transform, node); + public override bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) + { + // static callback, run after options callback + // we're iterating here because we want to stop calling any once we hit one that can export this material. + if (_exportContext.BeforeMaterialExport != null) + { + var list = _exportContext.BeforeMaterialExport.GetInvocationList(); + foreach (var entry in list) + { + var cb = (GLTFSceneExporter.BeforeMaterialExportDelegate) entry; + if (cb != null && cb.Invoke(exporter, gltfRoot, material, materialNode)) + { + return true; + } + } + } + return false; + } + public override void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) => _exportContext.AfterMaterialExport?.Invoke(exporter, gltfRoot, material, materialNode); + public override void BeforeTextureExport(GLTFSceneExporter exporter, ref GLTFSceneExporter.UniqueTexture texture, string textureSlot) => _exportContext.BeforeTextureExport?.Invoke(exporter, ref texture, textureSlot); + public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) => _exportContext.AfterTextureExport?.Invoke(exporter, texture, index, tex); + public override void AfterPrimitiveExport(GLTFSceneExporter exporter, Mesh mesh, MeshPrimitive primitive, int index) => _exportContext.AfterPrimitiveExport?.Invoke(exporter, mesh, primitive, index); + } +#pragma warning restore CS0618 // Type or member is obsolete } + + [Obsolete("Use ExportContext instead")] + public class ExportOptions: ExportContext {} public partial class GLTFSceneExporter { @@ -59,11 +114,43 @@ public partial class GLTFSceneExporter public delegate void BeforeSceneExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot); public delegate void AfterSceneExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot); public delegate void AfterNodeExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, Node node); + /// True: material export is complete. False: continue regular export. + public delegate bool BeforeMaterialExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode); + public delegate void AfterMaterialExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode); public delegate void BeforeTextureExportDelegate(GLTFSceneExporter exporter, ref UniqueTexture texture, string textureSlot); public delegate void AfterTextureExportDelegate(GLTFSceneExporter exporter, UniqueTexture texture, int index, GLTFTexture tex); public delegate void AfterPrimitiveExportDelegate(GLTFSceneExporter exporter, Mesh mesh, MeshPrimitive primitive, int index); + + private class LegacyCallbacksPlugin : GltfExportPluginContext + { + public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) => GLTFSceneExporter.AfterSceneExport?.Invoke(exporter, gltfRoot); + public override void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) => GLTFSceneExporter.BeforeSceneExport?.Invoke(exporter, gltfRoot); + public override void AfterNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, Node node) => GLTFSceneExporter.AfterNodeExport?.Invoke(exporter, gltfRoot, transform, node); + + public override bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) + { + // static callback, run after options callback + // we're iterating here because we want to stop calling any once we hit one that can export this material. + if (GLTFSceneExporter.BeforeMaterialExport != null) + { + var list = GLTFSceneExporter.BeforeMaterialExport.GetInvocationList(); + foreach (var entry in list) + { + var cb = (BeforeMaterialExportDelegate) entry; + if (cb != null && cb.Invoke(exporter, gltfRoot, material, materialNode)) + { + return true; + } + } + } + return false; + } + public override void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) => GLTFSceneExporter.AfterMaterialExport?.Invoke(exporter, gltfRoot, material, materialNode); + } + private static ILogger Debug = UnityEngine.Debug.unityLogger; + private List _plugins = new List(); public struct TextureMapType { @@ -278,7 +365,7 @@ public struct ExportFileResult private List _animatedNodes; private int _exportLayerMask; - private ExportOptions _exportOptions; + private ExportContext _exportContext; private Material _metalGlossChannelSwapMaterial; private Material _normalChannelMaterial; @@ -396,7 +483,7 @@ public override int GetHashCode() #region Settings - private GLTFSettings settings => _exportOptions.settings; + private GLTFSettings settings => _exportContext.settings; private bool ExportNames => settings.ExportNames; private bool RequireExtensions => settings.RequireExtensions; private bool ExportAnimations => settings.ExportAnimations; @@ -463,11 +550,11 @@ public override int GetHashCode() /// Root transform of object to export [Obsolete("Please switch to GLTFSceneExporter(Transform[] rootTransforms, ExportOptions options). This constructor is deprecated and will be removed in a future release.")] public GLTFSceneExporter(Transform[] rootTransforms, RetrieveTexturePathDelegate texturePathRetriever) - : this(rootTransforms, new ExportOptions { TexturePathRetriever = texturePathRetriever }) + : this(rootTransforms, new ExportContext { TexturePathRetriever = texturePathRetriever }) { } - public GLTFSceneExporter(Transform rootTransform, ExportOptions options) : this(new [] { rootTransform }, options) + public GLTFSceneExporter(Transform rootTransform, ExportContext context) : this(new [] { rootTransform }, context) { } @@ -475,16 +562,31 @@ public GLTFSceneExporter(Transform rootTransform, ExportOptions options) : this( /// Create a GLTFExporter that exports out a transform /// /// Root transform of object to export - /// Export Settings - public GLTFSceneExporter(Transform[] rootTransforms, ExportOptions options) + /// Export Settings + public GLTFSceneExporter(Transform[] rootTransforms, ExportContext context) { - _exportOptions = options; - if (options.logger != null) - Debug = options.logger; + _exportContext = context; + if (context.logger != null) + Debug = context.logger; else Debug = UnityEngine.Debug.unityLogger; + + // legacy: implicit plugin for all the static methods on GLTFSceneExporter + _plugins.Add(new LegacyCallbacksPlugin()); + // legacy: implicit plugin for all the methods on ExportContext + _plugins.Add(context.GetImplicitPlugin()); + + // create export plugin instances + foreach (var plugin in settings.ExportPlugins) + { + if (plugin != null && plugin.Enabled) + { + var instance = plugin.CreateInstance(context); + if (instance != null) _plugins.Add(instance); + } + } - _exportLayerMask = _exportOptions.ExportLayers; + _exportLayerMask = _exportContext.ExportLayers; var metalGlossChannelSwapShader = Resources.Load("MetalGlossChannelSwap", typeof(Shader)) as Shader; _metalGlossChannelSwapMaterial = new Material(metalGlossChannelSwapShader); @@ -607,8 +709,8 @@ public void SaveGLBToStream(Stream stream, string sceneName) exportGltfInitMarker.End(); beforeSceneExportMarker.Begin(); - _exportOptions.BeforeSceneExport?.Invoke(this, _root); - BeforeSceneExport?.Invoke(this, _root); + foreach (var plugin in _plugins) + plugin.BeforeSceneExport(this, _root); beforeSceneExportMarker.End(); _root.Scene = ExportScene(sceneName, _rootTransforms); @@ -626,11 +728,8 @@ public void SaveGLBToStream(Stream stream, string sceneName) } afterSceneExportMarker.Begin(); - if (_exportOptions.AfterSceneExport != null) - _exportOptions.AfterSceneExport(this, _root); - - if (AfterSceneExport != null) - AfterSceneExport.Invoke(this, _root); + foreach (var plugin in _plugins) + plugin.AfterSceneExport(this, _root); afterSceneExportMarker.End(); animationPointerResolver?.Resolve(this); @@ -711,8 +810,8 @@ public void SaveGLTFandBin(string path, string fileName, bool exportTextures = t exportGltfInitMarker.End(); beforeSceneExportMarker.Begin(); - _exportOptions.BeforeSceneExport?.Invoke(this, _root); - BeforeSceneExport?.Invoke(this, _root); + foreach (var plugin in _plugins) + plugin.BeforeSceneExport(this, _root); beforeSceneExportMarker.End(); if (_rootTransforms != null) @@ -729,10 +828,8 @@ public void SaveGLTFandBin(string path, string fileName, bool exportTextures = t } afterSceneExportMarker.Begin(); - if (_exportOptions.AfterSceneExport != null) - _exportOptions.AfterSceneExport(this, _root); - if (AfterSceneExport != null) - AfterSceneExport.Invoke(this, _root); + foreach (var plugin in _plugins) + plugin.AfterSceneExport(this, _root); afterSceneExportMarker.End(); animationPointerResolver?.Resolve(this); @@ -874,7 +971,7 @@ private SceneId ExportScene(string name, Transform[] rootObjTransforms) scene.Name = name; } - if(_exportOptions.TreatEmptyRootAsScene) + if(_exportContext.TreatEmptyRootAsScene) { // if we're exporting with a single object selected, that object can be the scene root, no need for an extra root node. if (rootObjTransforms.Length == 1 && rootObjTransforms[0].GetComponents().Length == 1) // single root with a single transform @@ -1019,8 +1116,8 @@ private NodeId ExportNode(Transform nodeTransform) // node export callback afterNodeExportMarker.Begin(); - _exportOptions.AfterNodeExport?.Invoke(this, _root, nodeTransform, node); - AfterNodeExport?.Invoke(this, _root, nodeTransform, node); + foreach (var plugin in _plugins) + plugin.AfterNodeExport(this, _root, nodeTransform, node); afterNodeExportMarker.End(); return id; diff --git a/Runtime/Scripts/GLTFSettings.cs b/Runtime/Scripts/GLTFSettings.cs index 09931348d..8785fcdc5 100644 --- a/Runtime/Scripts/GLTFSettings.cs +++ b/Runtime/Scripts/GLTFSettings.cs @@ -33,8 +33,7 @@ public override void OnActivate(string searchContext, VisualElement rootElement) base.OnActivate(searchContext, rootElement); CalculateCacheStats(); } - - + [SettingsProvider] public static SettingsProvider CreateGltfSettingsProvider() { @@ -51,8 +50,7 @@ private static void CalculateCacheStats() var files = new List(); exportCacheByteLength = ExportCache.CalculateCacheSize(files); } - - + private static GLTFSettings settings; private static SerializedObject m_SerializedObject; @@ -94,42 +92,60 @@ internal static void DrawGLTFSettingsGUI() } GUILayout.Space(10); + EditorGUILayout.LabelField("Import Plugins", EditorStyles.boldLabel); - OnPluginsGUI(); + OnPluginsGUI(settings.ImportPlugins); + + EditorGUILayout.LabelField("Export Plugins", EditorStyles.boldLabel); + OnPluginsGUI(settings.ExportPlugins); + + EditorGUILayout.LabelField("Supported Extensions (Import)", EditorStyles.boldLabel); + // All plugins in the extension factory are supported for import. + // TODO Some of them have extra package requirements (e.g. meshopt/draco), could be shown here + // TODO help buttons and docs/tooltip would be great + foreach (var ext in GLTFProperty.RegisteredExtensions) + { + EditorGUILayout.ToggleLeft(ext, true); + } OnAfterGUI?.Invoke(settings); } - internal static void OnPluginsGUI() + private static Dictionary editorCache = new Dictionary(); + internal static void OnPluginsGUI(IEnumerable plugins) { - foreach (var plugin in settings.ImportPlugins) + EditorGUI.indentLevel++; + foreach (var plugin in plugins) { if (!plugin) continue; var displayName = plugin.DisplayName ?? plugin.name; if (string.IsNullOrEmpty(displayName)) displayName = ObjectNames.NicifyVariableName(plugin.GetType().Name); + var key = plugin.GetType().FullName + "_SettingsExpanded"; + var expanded = SessionState.GetBool(key, false); using (new GUILayout.HorizontalScope()) { var label = new GUIContent(displayName); - plugin.Expanded = EditorGUILayout.BeginFoldoutHeaderGroup(plugin.Expanded, label); + var expanded2 = EditorGUILayout.Foldout(expanded, label); + if (expanded2 != expanded) + { + expanded = expanded2; + SessionState.SetBool(key, expanded2); + } plugin.Enabled = GUILayout.Toggle(plugin.Enabled, "", GUILayout.Width(20)); } - if (plugin.Expanded) + if (expanded) { - EditorGUI.indentLevel += 1; - plugin.OnGUI(); - EditorGUI.indentLevel -= 1; + EditorGUI.indentLevel++; + editorCache.TryGetValue(plugin.GetType(), out var editor); + Editor.CreateCachedEditor(plugin, null, ref editor); + editorCache[plugin.GetType()] = editor; + editor.OnInspectorGUI(); + EditorGUI.indentLevel--; } EditorGUILayout.EndFoldoutHeaderGroup(); } - - EditorGUILayout.LabelField("Supported Extensions (Import)", EditorStyles.boldLabel); - // All plugins in the extension factory are supported for import. - // TODO Some of them have extra package requirements (e.g. meshopt/draco), could be shown here - // TODO help buttons and docs/tooltip would be great - foreach (var ext in GLTFProperty.RegisteredExtensions) - { - EditorGUILayout.ToggleLeft(ext, true); - } + + EditorGUI.indentLevel--; } } @@ -161,9 +177,13 @@ public enum BlendShapeExportPropertyFlags All = ~0 } + // Plugins [SerializeField, HideInInspector] public List ImportPlugins; - + + [SerializeField, HideInInspector] + public List ExportPlugins; + [Header("Export Settings")] [SerializeField] private bool exportNames = true; @@ -273,6 +293,7 @@ public static GLTFSettings GetOrCreateSettings() settings = ScriptableObject.CreateInstance(); #endif } + #if UNITY_EDITOR RegisterPlugins(settings); #endif @@ -312,20 +333,29 @@ public static bool TryGetSettings(out GLTFSettings settings) #if UNITY_EDITOR private static void RegisterPlugins(GLTFSettings settings) { - if(settings.ImportPlugins == null) settings.ImportPlugins = new List(); + if (settings.ImportPlugins == null) settings.ImportPlugins = new List(); + if (settings.ExportPlugins == null) settings.ExportPlugins = new List(); - foreach (var pluginType in TypeCache.GetTypesDerivedFrom()) + void FindAndRegisterPlugins(List plugins) where T : GltfPlugin { - if (pluginType.IsAbstract) continue; - // If the plugin already exists we dont want to add it again - if (settings.ImportPlugins.Any(p => p != null && p.GetType() == pluginType)) - continue; - if (typeof(ScriptableObject).IsAssignableFrom(pluginType)) + foreach (var pluginType in TypeCache.GetTypesDerivedFrom()) { - var newInstance = ScriptableObject.CreateInstance(pluginType) as GltfImportPlugin; - settings.ImportPlugins.Add(newInstance); + if (pluginType.IsAbstract) continue; + if (plugins.Any(p => p != null && p.GetType() == pluginType)) + continue; + + if (typeof(ScriptableObject).IsAssignableFrom(pluginType)) + { + var newInstance = CreateInstance(pluginType) as T; + plugins.Add(newInstance); + Debug.Log("added plugin " + newInstance); + EditorUtility.SetDirty(settings); + } } } + + FindAndRegisterPlugins(settings.ImportPlugins); + FindAndRegisterPlugins(settings.ExportPlugins); } #endif } diff --git a/Runtime/Scripts/Plugins/GltfExportPlugin.cs b/Runtime/Scripts/Plugins/GltfExportPlugin.cs new file mode 100644 index 000000000..f112386b6 --- /dev/null +++ b/Runtime/Scripts/Plugins/GltfExportPlugin.cs @@ -0,0 +1,26 @@ +using GLTF.Schema; +using UnityEngine; + +namespace UnityGLTF.Plugins +{ + public abstract class GltfExportPlugin: GltfPlugin + { + /// + /// Return the Plugin Instance that receives the import callbacks + /// + public abstract GltfExportPluginContext CreateInstance(ExportContext context); + } + + public abstract class GltfExportPluginContext + { + public virtual void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) {} + public virtual void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) {} + public virtual void AfterNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, Node node) {} + public virtual bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) => false; + public virtual void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) {} + public virtual void BeforeTextureExport(GLTFSceneExporter exporter, ref GLTFSceneExporter.UniqueTexture texture, string textureSlot) {} + public virtual void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) {} + public virtual void AfterPrimitiveExport(GLTFSceneExporter exporter, Mesh mesh, MeshPrimitive primitive, int index) {} + + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/GltfExportPlugin.cs.meta b/Runtime/Scripts/Plugins/GltfExportPlugin.cs.meta new file mode 100644 index 000000000..04e03ae7a --- /dev/null +++ b/Runtime/Scripts/Plugins/GltfExportPlugin.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 201693f491d641c3a7bf53d98828d59c +timeCreated: 1703879501 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/IGltfImporterPlugin.cs b/Runtime/Scripts/Plugins/GltfImportPlugin.cs similarity index 86% rename from Runtime/Scripts/Plugins/IGltfImporterPlugin.cs rename to Runtime/Scripts/Plugins/GltfImportPlugin.cs index 2a3aa5921..6f534a8b2 100644 --- a/Runtime/Scripts/Plugins/IGltfImporterPlugin.cs +++ b/Runtime/Scripts/Plugins/GltfImportPlugin.cs @@ -4,12 +4,8 @@ namespace UnityGLTF.Plugins { - public abstract class GltfImportPlugin : ScriptableObject + public abstract class GltfImportPlugin : GltfPlugin { - internal bool Expanded = true; - public abstract string DisplayName { get; } - public bool Enabled { get; set; } = true; - public abstract void OnGUI(); /// /// Return the Plugin Instance that receives the import callbacks /// diff --git a/Runtime/Scripts/Plugins/IGltfImporterPlugin.cs.meta b/Runtime/Scripts/Plugins/GltfImportPlugin.cs.meta similarity index 100% rename from Runtime/Scripts/Plugins/IGltfImporterPlugin.cs.meta rename to Runtime/Scripts/Plugins/GltfImportPlugin.cs.meta diff --git a/Runtime/Scripts/Plugins/GltfPlugin.cs b/Runtime/Scripts/Plugins/GltfPlugin.cs new file mode 100644 index 000000000..bdabedfe0 --- /dev/null +++ b/Runtime/Scripts/Plugins/GltfPlugin.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace UnityGLTF.Plugins +{ + public abstract class GltfPlugin: ScriptableObject + { + public abstract string DisplayName { get; } + public bool Enabled { get; set; } = true; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/GltfPlugin.cs.meta b/Runtime/Scripts/Plugins/GltfPlugin.cs.meta new file mode 100644 index 000000000..426962e5a --- /dev/null +++ b/Runtime/Scripts/Plugins/GltfPlugin.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: faaa3712025f4258a16c9afa9e8d1fc6 +timeCreated: 1703880672 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/ImportContext.cs b/Runtime/Scripts/Plugins/ImportContext.cs index 1d9fd409b..dde79f3a5 100644 --- a/Runtime/Scripts/Plugins/ImportContext.cs +++ b/Runtime/Scripts/Plugins/ImportContext.cs @@ -35,9 +35,4 @@ internal GLTFImportContext(IReadOnlyList plugins) Plugins = plugins; } } - - public interface IGLTFImportRemap - { - - } } diff --git a/Runtime/Scripts/SceneExporter/ExporterAnimation.cs b/Runtime/Scripts/SceneExporter/ExporterAnimation.cs index 3722fd979..0b220cd15 100644 --- a/Runtime/Scripts/SceneExporter/ExporterAnimation.cs +++ b/Runtime/Scripts/SceneExporter/ExporterAnimation.cs @@ -135,7 +135,7 @@ private GLTFAnimation GetOrCreateAnimation(AnimationClip clip, string searchForD return existingAnim; } - if (_exportOptions.MergeClipsWithMatchingNames) + if (_exportContext.MergeClipsWithMatchingNames) { // Check if we already exported an animation with exactly that name. If yes, we want to append to the previous one instead of making a new one. // This allows to merge multiple animations into one if required (e.g. a character and an instrument that should play at the same time but have individual clips). diff --git a/Runtime/Scripts/SceneExporter/ExporterMaterials.cs b/Runtime/Scripts/SceneExporter/ExporterMaterials.cs index a41e11ac4..198bdb2a4 100644 --- a/Runtime/Scripts/SceneExporter/ExporterMaterials.cs +++ b/Runtime/Scripts/SceneExporter/ExporterMaterials.cs @@ -12,18 +12,19 @@ namespace UnityGLTF { public partial class GLTFSceneExporter { - /// True: material export is complete. False: continue regular export. - public delegate bool BeforeMaterialExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode); - public delegate void AfterMaterialExportDelegate(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode); - // Static callbacks + [Obsolete("Use ExportPlugins on GLTFSettings instead")] public static event BeforeSceneExportDelegate BeforeSceneExport; + [Obsolete("Use ExportPlugins on GLTFSettings instead")] public static event AfterSceneExportDelegate AfterSceneExport; + [Obsolete("Use ExportPlugins on GLTFSettings instead")] public static event AfterNodeExportDelegate AfterNodeExport; /// True: material export is complete. False: continue regular export. + [Obsolete("Use ExportPlugins on GLTFSettings instead")] public static event BeforeMaterialExportDelegate BeforeMaterialExport; + [Obsolete("Use ExportPlugins on GLTFSettings instead")] public static event AfterMaterialExportDelegate AfterMaterialExport; - + // mock for Default material private static Material _defaultMaterial = null; public static Material DefaultMaterial @@ -77,11 +78,10 @@ public MaterialId ExportMaterial(Material materialObj) material.Name = materialObj.name; } - // before material export: only continue with regular export if that didn't succeed. - if (_exportOptions.BeforeMaterialExport != null) + foreach (var plugin in _plugins) { beforeMaterialExportMarker.Begin(); - if (_exportOptions.BeforeMaterialExport.Invoke(this, _root, materialObj, material)) + if (plugin.BeforeMaterialExport(this, _root, materialObj, material)) { beforeMaterialExportMarker.End(); return CreateAndAddMaterialId(materialObj, material); @@ -92,28 +92,6 @@ public MaterialId ExportMaterial(Material materialObj) } } - - // static callback, run after options callback - // we're iterating here because we want to stop calling any once we hit one that can export this material. - if (BeforeMaterialExport != null) - { - var list = BeforeMaterialExport.GetInvocationList(); - foreach (var entry in list) - { - beforeMaterialExportMarker.Begin(); - var cb = (BeforeMaterialExportDelegate) entry; - if (cb != null && cb.Invoke(this, _root, materialObj, material)) - { - beforeMaterialExportMarker.End(); - return CreateAndAddMaterialId(materialObj, material); - } - else - { - beforeMaterialExportMarker.End(); - } - } - } - exportMaterialMarker.Begin(); var isBirp = #if UNITY_2019_3_OR_NEWER @@ -356,13 +334,13 @@ private MaterialId CreateAndAddMaterialId(Material materialObj, GLTFMaterial mat _root.Materials.Add(material); // after material export - afterMaterialExportMarker.Begin(); if (materialObj) { - _exportOptions.AfterMaterialExport?.Invoke(this, _root, materialObj, material); - AfterMaterialExport?.Invoke(this, _root, materialObj, material); + afterMaterialExportMarker.Begin(); + foreach (var plugin in _plugins) + plugin.AfterMaterialExport(this, _root, materialObj, material); + afterMaterialExportMarker.End(); } - afterMaterialExportMarker.End(); return id; } diff --git a/Runtime/Scripts/SceneExporter/ExporterMeshes.cs b/Runtime/Scripts/SceneExporter/ExporterMeshes.cs index d08c73d01..a97001836 100644 --- a/Runtime/Scripts/SceneExporter/ExporterMeshes.cs +++ b/Runtime/Scripts/SceneExporter/ExporterMeshes.cs @@ -343,7 +343,8 @@ private MeshPrimitive[] ExportPrimitive(UniquePrimitive primKey, GLTFMesh mesh) // remove any prims that have empty triangles if (EmptyPrimitive(prim)) continue; // invoke pre export event - _exportOptions.AfterPrimitiveExport?.Invoke(this, meshObj, prim, i); + foreach (var plugin in _plugins) + plugin.AfterPrimitiveExport(this, meshObj, prim, i); nonEmptyPrims.Add(prim); } prims = nonEmptyPrims.ToArray(); diff --git a/Runtime/Scripts/SceneExporter/ExporterTextures.cs b/Runtime/Scripts/SceneExporter/ExporterTextures.cs index 29c0883ca..c102d50b6 100644 --- a/Runtime/Scripts/SceneExporter/ExporterTextures.cs +++ b/Runtime/Scripts/SceneExporter/ExporterTextures.cs @@ -179,7 +179,8 @@ public TextureId ExportTexture(Texture textureObj, string textureSlot, TextureEx new UniqueTexture(textureObj, exportSettings) : new UniqueTexture(textureObj, textureSlot, this); - _exportOptions.BeforeTextureExport?.Invoke(this, ref uniqueTexture, textureSlot); + foreach (var plugin in _plugins) + plugin.BeforeTextureExport(this, ref uniqueTexture, textureSlot); TextureId id = GetTextureId(_root, uniqueTexture); if (id != null) @@ -229,7 +230,8 @@ public TextureId ExportTexture(Texture textureObj, string textureSlot, TextureEx DeclareExtensionUsage(EXT_texture_exr.EXTENSION_NAME); } - _exportOptions.AfterTextureExport?.Invoke(this, uniqueTexture, id.Id, texture); + foreach (var plugin in _plugins) + plugin.AfterTextureExport(this, uniqueTexture, id.Id, texture); return id; } @@ -243,7 +245,7 @@ public TextureId ExportTexture(Texture textureObj, string textureSlot, TextureEx /// The relative texture output path on disk, including extension private string GetImageOutputPath(Texture texture, TextureExportSettings textureMapType, string textureSlot, out bool ableToExportFromDisk) { - var imagePath = _exportOptions.TexturePathRetriever(texture); + var imagePath = _exportContext.TexturePathRetriever(texture); if (string.IsNullOrEmpty(imagePath)) { imagePath = texture.name; diff --git a/Runtime/Scripts/Timeline/GLTFRecorder.cs b/Runtime/Scripts/Timeline/GLTFRecorder.cs index f64be413a..33315ac1e 100644 --- a/Runtime/Scripts/Timeline/GLTFRecorder.cs +++ b/Runtime/Scripts/Timeline/GLTFRecorder.cs @@ -350,7 +350,7 @@ public void EndRecording(Stream stream, string sceneName = "scene", GLTFSettings var logHandler = new StringBuilderLogHandler(); - var exporter = new GLTFSceneExporter(new Transform[] { root }, new ExportOptions(settings) + var exporter = new GLTFSceneExporter(new Transform[] { root }, new ExportContext(settings) { AfterSceneExport = PostExport, logger = new Logger(logHandler),