Search Unity

Feedback Trying to add new data to FBX imports is absolutely miserable

Discussion in 'Asset Importing & Exporting' started by Yoraiz0r, Jan 15, 2023.

  1. Yoraiz0r

    Yoraiz0r

    Joined:
    Apr 26, 2015
    Posts:
    81
    (Using Unity 2021.3.11f1) Today I tried making an AssetPostProcessor for certain FBX files, so that I can give them new data.
    My one goal was to give certain skinned meshes access to their pre-skinned vertex positions, put into UV2, which I cannot do outside of unity (as software like blender expects uv to be a vector2, and does not have the capacity unity has to make it a vector3/4).
    Code (CSharp):
    1. private void ModifyMesh(Mesh mesh)
    2.     {
    3.         var vertices = mesh.vertices;
    4.         mesh.SetUVs(2, vertices.ToList());
    5.     }
    6.  
    I've done it in runtime before (example code above), but doing it in runtime is both slower than I'd like, wasteful on memory, and not as clean to discern where information is coming from, so I determined I should Post Process my FBX imports.
    I didn't want to make this happen to all my meshes, as that would be unnecessarily slow.
    I didn't want it to be controlled by specific file names as I find those incredibly restricting.
    And I didn't want to use asset tags as suggested by this blog post, because the assets already contain user properties that say how they should be handled.

    I saw that OnPostprocessGameObjectWithUserProperties exists, and I confirmed that it does detect my FBX's custom properties. It mentions having full control over the mesh so I figured that's great... as an experiment - grab a reference to the MeshFilter from a test-cube gameobject, the sharedmesh from that one, and call the method mentioned earlier...except this introduced a warning that shows up every time I reimport:
    Importer(FBXImporter) generated inconsistent result for asset(guid:guidhere)"namehere".

    No matter what I try, this warning does not go away. Any type of change I try to make in the method, to the mesh, is met with this annoyance.

    I tried exploring other avenues of changing the mesh - perhaps in earlier methods. The information is incredibly scarse and I don't see a single way to obtain the reference to the mesh in any part of the asset post processing pipeline short of getting a component and fetching a shared mesh from that.

    Additionally, there is no way to obtain the user properties short of waiting for the exact first method I mentioned, so even if I wanted to use my user properties, I have to wait until that one moment, which seems to have issues.

    The research did point out to me that Asset Importers have a userData field, so I could make checkboxes within unity itself and use those from the start of the import process, which sounds incredibly appealing. It is a string so separating it into fields can be a hassle but it also means it is flexible...great. I'll try to extend the model importer editor to have a checkbox for whether my new UVs should be made, writing into userData, and then just check for that to modify my mesh....

    Except the ModelImporterEditor is internal, trying to make a new editor for it from scratch would be miserable due to the loss of the UI provided by the original, and trying to make a wrapping editor for it extending from AssetImporterEditor is futile because it makes a bunch of incoherent errors despite successful compilations. The most notable one being
    The previous instance of MyProject.Editor.ModelImporterEditor has not been disposed correctly. Make sure you are calling base.OnDisable() in your AssetImporterEditor implementation.
    Can't even add a tab to the existing 'Model/Rig/Animation/Materials'.

    So extending the model importer editor is no good, making my own model importer editor is a huge hassle that won't be maintainable if unity ever updates its own, and is no good, trying to use my already existing user properties already present on the model is no good... and I'm not done yet.

    I figured if I can't add new fields to the existing ModelImporterEditor, I can at least make my own desperate window just so I can add userData to the fbx which I'd like to edit. I went ahead and made my own EditorWindow, which checks the selection for an AssetImporter , and if it finds one, it shows me a checkbox to turn my flag on / off. That worked great...except I have one checkbox and the minimum size for an editor window is 100x100. I found the .minSize field, and set it to 10x10, and that worked wonders...when undocked. I then tried to dock it to the bottom of my inspector...and it was suddenly back to being giant. Some more frustration later and apparently the minsize for docked windows is static and internal, so I can't ever fix that in the current existing version of Unity.

    After a few hours of trying everything I can think of, I don't have a single working clean, nor intuitive, nor even functional solution. It's incredibly difficult to tell whether the problems are of my own making or actual bugs from unity's end. I'd love any type of help I can have in order to make this asset post processing work without stupid warnings, and without heavy compromises like tags, specific path conventions, or permanent window layout space sacrifice.

    tl;dr:
    1. changing mesh data in
    AssetPostprocessor.OnPostprocessGameObjectWithUserProperties 
    throws an inconsistent guid warning on every reimport attempt.
    2. User properties cannot be obtained earlier in the asset post processing pipeline
    3.
    AssetImporter.userData 
    exists, but there is no easy way to show it nor any other information on Unity's default asset importer editors, as they are uneditable & unwrappable by any conventional means.
    4. Making an external a separate editor window to set the userData means sacrificing permanent layout space consumption of far too many pixels for the simple cause of a single checkbox.
    5. The default ModelImporter has 4 tabs, but does not let users add more, despite the many possible and positive use cases.

    Today ended in defeat, but I hope this feedback can at least help drive future versions of unity to be far less painful than what I had to go through, and still have to keep searching through, just to...add a checkbox... and have it run 2 lines of code on a mesh.
     
    saskenergy likes this.
  2. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Hi there,
    What a lengthy post with so many details! I'm sorry you had to go through all of this and fall short on every solution.
    Let me try to answer each point and figure out solutions.

    It should be possible to get rid of the inconsistent warning, and is probably your best shot at fixing your workflow (I personally think it's the right way to go given my answers to the other questions!)
    To fix this issue, it's important to understand how the asset re-import mechanism is working in Unity.

    When an asset is imported, it can declare explicit dependencies through the AssetImportContext upon other assets, files, global settings, etc... On top of that, it's always depending upon its own content, its meta file content, the importer version, its path, and any AssetPostprocessor the importer would use.
    All these dependencies are being hashed to create a unique identifier (the ArtifactID) that the AssetDatabase then uses to store your import result (Prefab, Meshes, Materials, Textures, etc... namely, Asset Objects) in the Library folder.

    On AssetDatabase.Refresh (project opening, Editor get focus, manual call, etc...) Unity look at which Source Asset (the files in the Asset or Packages folder) on disk changes and compute a bunch of global dependencies (what platform are we on, what is the version of each importer, what are the values of the CustomDependencies, what AssetPostprocessor exist, and what are their version, etc...)
    At that point, it's possible to know which existing ArtifactID will result in a different ArtifactID given the new data, and that provides us with a list of what needs to be re-imported.
    For each of these Source Asset that needs re-import, we first look into the Library to see if the result is already there, if it is, then nice, we don't have to do the import, and we just restore the previous result. If it's not there, then we call the AssetImporter and generate the new Asset Objects.

    On the other side, you can always force a Source Asset to be re-imported by right-clicking on it and selecting ReImport. In that case, any existing result in Library is ignored and the Importer is called to process your asset.
    At the end of the import, the ArtifactID is calculated to figure out where the Asset Objects need to be saved, and if something is already present here, the AssetDatabase will compare the result. If that's different, it means that the same ArtifactID hash generated different results, and thus it won't be possible to know which one is the right one, and restoring previous imports will cause issues.

    How to prevent that?
    1- Making sure that everything that can impact the import result is declared as a dependency.
    For example, if you're using a global setting in your AssetPostprocessor, then you need to use a CustomDependency to hash this setting outside of the import process so you can depend on it during your import.
    If you're using the content of another file, then you need to make sure that you depend on its Source Asset.
    2- Making sure dependencies are correctly updated.
    The AssetPostprocessor class has a virtual GetVersion method that you should override and change this number every time you're changing your code in your postprocessor method, so the hash changes and the fbx assets will re-import using the new code, to generate new results.
    3- Make sure your code is determinist. Using random numbers while generating assets is a no-no, if two imports yield different results because your method is not able to generate the same result twice, then the ArtifactID won't ever be able to resolve properly, and your final product will probably have inconsistency one build after another...

    If you want to share your AssetPostprocessor script, I could have a look at it and help pinpoint what may make it non-deterministic and generate that warning.

    The order of the AssetPostprocessor methods is sometimes not what we would like for sure...
    One thing that may help is that the AssetPostprocessor instance is the same through the whole import of an asset, and not shared with other assets. That means it's definitely possible to keep data from one method in a class member variable to reuse it in the next method.
    So you should be able to check your user settings during OnPostprocessGameObjectWithUserProperties and change the Meshes data during OnPostpostprocessModel without issues (hopefully by figuring out what was the issue in your first point and making it just work).

    UserData is, well, a workaround at best in my opinion. A lot of people are using it to store any kind of data regardless of what's already stored or not in it...
    I think we should have changed it a long time ago to a dictionary and provided proper UI support for it in the Importer Inspectors, but we never did and that would be some big changes in the UI that we don't really have the time to look into right now (I'm keeping a note that it would be a nice improvement for existing importers though...)

    Unfortunately, the min sized window when docked is by design, I'm not sure the UI team would revisit that any time soon.
    As stated in the previous answer, I think we should do better on the importer side and solve that issue instead.

    I think I've look into adding a new tab for it some years ago and exposing callback to let users put whatever they want in the new tab, but then it doesn't really help for other importers, and making it work for everything was really hard to achieve, and while we could just fix it for the ModelImporter, we will then drown into bugs that you can't do it properly with other importers, which is not nice.
    I think handling that should be part of the better userData story.

    I hope all this information helps, don't hesitate to share your script here, or open a bug with your project and post the IN- number here so I can have a look.

    Have a good day.
     
    Yoraiz0r likes this.
  3. Yoraiz0r

    Yoraiz0r

    Joined:
    Apr 26, 2015
    Posts:
    81
    Hi, first - thank you so much for taking the time to respond to this post.
    I've since made more iterations and attempts at it, as well as having reported the inconsistent warning with the IN-28865, and the inability to adjust anything regarding the ModelImporterEditor on IN-28870.

    Here's the smallest script I've deduced can be used for the asset post processor, to replicate the issue.
    I also confirmed that as you mentioned, reimporting all assets 'solves' this issue, but its incredibly long a process and I'd prefer manually collecting and allowing a 'reimport all things that would be affected' (a far smaller subset) than the former, or at least to suppress the warning for a day.
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. namespace Editor
    6. {
    7.     public class CustomAssetPostProcessor : AssetPostprocessor
    8.     {
    9.         private void OnPostprocessGameObjectWithUserProperties(GameObject go, string[] propNames, object[] values)
    10.         {
    11.             var meshFilter = go.GetComponent<MeshFilter>();
    12.             var mesh = meshFilter.sharedMesh;
    13.             mesh.SetUVs(2, mesh.vertices.ToList());
    14.         }
    15.     }
    16. }
    The example of the custom dependency mentioned in (1) mentions that I cannot set those while the asset is importing, but I only get to know that on the post process, which sounds too late. What do I do in this case? the only 'dependency' is the fact that my model has custom user properties in this case, I'm not even checking which one to allow branching in this minimal example.
    For (2) - updating the version, this is actually great to know, thank you. I'll give it a go later when I have the chance. It does sound ever so slightly tedious but I fully understand the need.
    For (3) - I fully understand - and believe - that my import process is deterministic.
    If updating the version is all I need to secure a warning less import, that sounds fantastic.
    (Also, example project in IN-28865 with simple cube asset that has custom user properties, in case it matters)

    I did feel that notion as well. I've looked at other developers' usage of it (however little I could find) and it seems that there are no standards whatsoever, some serialize with jsonutility, some parse it by splitting commas, its all over the place. Dictionaries - or a system to directly add our own fields / check if they exist in meta files, would be splendid.I borderline considered doing manual text scanning & editing of the meta file before finding the userdata, but I feared it won't survive a reimport or the likes because there is no way for me to 'protect' fields in it from whatever Unity may do (that I don't know).

    That's sad to hear, but yes - solving the root issue is better than solving the symptoms individually, please.

    The problems isn't the tabs per se - but rather that there is absolutely no way to use already existing code of the model importer editor without rewriting it all manually and praying no other internal code is relied upon. It brings up all sorts of questions like...
    1. Why aren't there importer editor wrappers in unity by default? surely adding power tooling around importers would have been better than the limited 'presets' years down the line. letting developers add buttons to set all their settings or include extra information sounds like something that would have been more common...
    2. Why is the code around those editors flagged internal instead of public? we can't even refer to them directly. as it stands, none can go make tabbed ui editors right now using unity's implementation.
    3. Why can't importer editors be created inside other importer editors? (will make more sense with example snippet below)
    4. sans all other options, why is there no clean "add your own settings / ui to editors here and it will be found and used"?

    For my desperate attempt at wrapping the ModelImporterEditor, I started with this: (also found in IN-28870)
    Code (CSharp):
    1. using System;
    2. using UnityEditor;
    3. using UnityEditor.AssetImporters;
    4. using UnityEngine;
    5.  
    6. namespace Editor
    7. {
    8.     [CustomEditor(typeof(ModelImporter))]
    9.     public class CustomModelImporterEditor : AssetImporterEditor
    10.     {
    11.         AssetImporterEditor _originalEditor;
    12.  
    13.         public override void OnEnable()
    14.         {
    15.             _originalEditor = CreateEditor(targets, Type.GetType("UnityEditor.ModelImporterEditor, UnityEditor")) as AssetImporterEditor;
    16.             base.OnEnable();
    17.         }
    18.      
    19.         public override void OnDisable()
    20.         {
    21.             DestroyImmediate(_originalEditor);
    22.             base.OnDisable();
    23.         }
    24.  
    25.         public override void OnInspectorGUI()
    26.         {
    27.             serializedObject.Update();
    28.  
    29.             _originalEditor.OnInspectorGUI();
    30.  
    31.             if (GUILayout.Button("Do something"))
    32.                 Debug.Log(("I did a thing"));
    33.  
    34.             serializedObject.ApplyModifiedProperties();
    35.          
    36.             ApplyRevertGUI();
    37.         }
    38.     }
    39. }
    Sadly, the mere act of having this editor wrap another, makes "previous editor was not properly disposed" at any selection attempt between models. This would make it fatally annoying to work cleanly. The usage of a wrapped editor also makes the ApplyRevertGUI show twice, as well as the list of the assetpostprocessors - apparently they've been oddly added to the end of ApplyRevertGUI instead of being separate from the importer editor system?

    I suspect I can mark my wrapper to not need ApplyRevertGUI, and remove that method call, since I can trust it is called from ModelImporterEditor, but this still won't solve the errors. The AssetImporterEditor demands I call base.OnEnable / base.OnDisable, but calling them is what causes the issues, seemingly.

    As a side note, switching the extension from AssetImporterEditor to just Editor, makes the errors disappear, but I obviously lose the apply revert functionality, which is a deal breaker.

    Also - if I may have more of your time please...
    As I continued my experiments in the form of using AssetPostProcessor, another thought popped into my head, the idea of appending a settings object to the asset, and then relying on it. However, reasonably, it seems that appended objects in assets cannot be edited - the inspector is grayed out. I also don't like the idea of plentiful objects in assets I never plan to mess with, so I understand - but is there a way to enable such editing of appended objects in assets anyways?

    I assume this is the reason animation clips cannot be edited in fbx importers directly, but only through the importer window. the values actually belong to the importer and the generated assets are uneditable. Still, it makes me wish that there was a way to make edits without having to completely disconnect the clips from their original asset...
     
  4. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    That script would definitely not be a source of this warning. I'll wait for our QA team to process your project and have a look at it later this week to figure out what's going on.

    Yes, that would be expected, Reimport All basically trashes whatever is in the Library folder and start again from scratch, so it doesn't compare anymore and will have just fresh new results for each import.

    If your only dependence is the properties in the fbx file, then it's already a dependency because all Source Asset contents are a dependency of their own import by default. So you shouldn't have anything to do.

    I guess that comes down to old maintenance and backward compatibility... the overall importer mechanism and UI is very old code, prior to 2010, and while we tried to update it and make it more modular to give better support for ScriptedImporters for example, it is tied to a lot of limitations that the InspectorWindow can have, and changing all of that take a lot of time and efforts that we often prefer spending on improving other parts of the engine, because unfortunately, the ModelImporter UI doesn't bother that many people compared to other issues we have.
    It doesn't mean we don't do anything and ignore the problem though. We are actively working on solutions, replacements, and improvements in the area, but it takes time because of all the implications it has on existing projects, tools, packages, etc...

    Yes, we've had some users reporting that already in the past, and I've made some experiments myself and couldn't get to any place where that could work well. The implication it has on the InspectorWindow and wrapping multiple Editors in it is already brittle with simple Editors, and just crumbles when it goes to AssetImporterEditors. Making it public does help a little but it has so many implications and other limitations around it that it's not really an alternative either.

    Asset Object can't be edited. Because the import is deterministic (or supposed to be), and results are stored in Library and thus not shared when you share your project, we cannot allow changes to the import result after the import. So anything created at import time should be considered read-only so it can be recreated the same on another machine or the next time your Source Asset changes.
    I know some users and packages use companion files to handle their asset post-processor settings. For example, they are storing settings in a ScriptableObject saved in a .asset file with the same name as the Source Asset and declare a dependency to it in the AssetPostprocessor, this way the asset will reimport every time the settings are added/removed/changed.
    It's also compatible with multiple people making their own settings because anyone can add as many ScriptableObject as they want in the same .asset file using AssetDatabase.AddObjectToAsset.

    I'm answering that after the previous answer because I think it's better to use a companion file rather than playing with the meta file at that point.
    Unity serializes the .meta file in a very particular way, so adding any custom data into it is definitely not an option and won't survive any setting changes in the importer.
    Going the companion file road is way better in that sense because the serialization here behaves as expected and everything will show as expected in the inspector too.
     
    Yoraiz0r likes this.
  5. Yoraiz0r

    Yoraiz0r

    Joined:
    Apr 26, 2015
    Posts:
    81
    Going the companion file road is also worse in many senses since now managing moving a single resource around requires you find all its companions and move them along. The inspectors are also separate so you can't have a full view in a single place... even if I do choose to go the companion route, how do I determine/find the companions to my file, without wrecking the import process performance? can you provide an example please?

    I'm somewhat stumped - while I can understand a scripted importer depending on other files to make its own content, I'm unsure how to create this situation for an fbx file's post processor... let alone conditionally.

    If I make an asset depend on the artifact of another, does this automatically setup the import order so that the artifact has to always be made first?

    My current imagination of this is that on importing my model, in the post process, I would have to find scriptableobjects / 'companion assets' , set them as a dependency in the post process, using DependOnArtifact? Then I simply author them separately and ensure they're found? what would be the quickest way to ensure they're found / don't get screwed up by import order? is there some GetDependedAssets method I can rely on?

    Is there a way said scriptableobject can declare itself as a dependency of the model instead? does that make sense at all? do I need to do anything to ensure those scriptableobjects always get imported earlier than models such as with a scripted importer?

    I assume at minimum trying to figure out a right click fbx file -> create attached companion asset would be useful...

    This of course doesn't solve the aforementioned user experience issue, having to create and witness the assets separately will still be wasteful on time... Is there any way to override what the inspector sees in general? I could try to add something along the lines of "if inspecting a modelimportereditor, also do stuff like..." (show the scriptableobject contents/checkbox /create button?)

    Thank you so much for your time and explanations again!
     
  6. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Here we go:
    Code (CSharp):
    1. using System.IO;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. // Your configuration file
    6. public class CustomConfiguration : ScriptableObject
    7. {
    8.     public bool enableSomeProcess;
    9.  
    10.     // This menu item will show by right-clicking the inspector header on each imported Model.
    11.     // It could be moved elsewhere though.
    12.     [MenuItem("CONTEXT/ModelImporter/Create Custom Configuration")]
    13.     public static void CreateCompanionSetting(MenuCommand command)
    14.     {
    15.         var assetPath = AssetDatabase.GetAssetPath(command.context);
    16.         var settingsPath = $"{Path.GetDirectoryName(assetPath)}/{Path.GetFileNameWithoutExtension(assetPath)}.asset";
    17.  
    18.         // First we try to load any existing configuration to avoid overriding it or creating duplicates.
    19.         // It could be done in a MenuItem validation method.
    20.         var config = AssetDatabase.LoadAssetAtPath<CustomConfiguration>(settingsPath);
    21.         if (config == null)
    22.         {
    23.             config = ScriptableObject.CreateInstance<CustomConfiguration>();
    24.             if (AssetDatabase.LoadMainAssetAtPath(settingsPath) == null)
    25.             {
    26.                 // If not configuration file already exists, then we create a new file
    27.                 AssetDatabase.CreateAsset(config, settingsPath);
    28.             }
    29.             else
    30.             {
    31.                 // If it exists, then we just add this new setting to it.
    32.                 AssetDatabase.AddObjectToAsset(config, settingsPath);
    33.             }
    34.         }
    35.        
    36.     }
    37. }
    38.  
    39. public class ModelImporterCustomImport : AssetPostprocessor
    40. {
    41.     public override uint GetVersion()
    42.     {
    43.         return 1;
    44.     }
    45.  
    46.     private void OnPostprocessModel(GameObject go)
    47.     {
    48.         // First lets find the setting path name
    49.         var assetPath = context.assetPath;
    50.         var settingsPath = $"{Path.GetDirectoryName(assetPath)}/{Path.GetFileNameWithoutExtension(assetPath)}.asset";
    51.         // always add a dependency to it so if it's added later or changed, the asset will re-import.
    52.         context.DependsOnSourceAsset(settingsPath);
    53.         // Try to load the config. It may fail to load if it's not ready in the AssetDatabase.
    54.         // But the dependency will make sure to re-import the fbx file once its ready if that's the case.
    55.         // So the end result will always work using the file if it exists.
    56.         var config = AssetDatabase.LoadAssetAtPath<CustomConfiguration>(settingsPath);
    57.         if (config != null && config.enableSomeProcess)
    58.         {
    59.             // Do what you want here
    60.         }
    61.     }
    62. }
    Kind of... If your asset gets re-imported before your dependency for some reason, the dependency will be invalidated when it's its turn to be imported, so your asset will be re-imported a second time with the correct version of the dependency. The idea is that whatever happens, the last import should always be able to access anything that it declared a dependency on if it exists.

    No, that's also an idea I've investigated some time ago, but the InspectorWindow wasn't in a state that allowed that easily... I think we got closer to that with some recent changes though, I'll reach out to the owners of that area and discuss the idea with them to see if this could eventually happen at some point.
     
    Yoraiz0r likes this.
  7. Yoraiz0r

    Yoraiz0r

    Joined:
    Apr 26, 2015
    Posts:
    81
    Thank you again for your time and effort. I took the time to try it out and some more things, here are my results.

    First, I tried out the script exactly as you wrote it, with the only change being the inclusion of 'do what you want here'.
    Sadly... even with adding that,bumping the version of the importer up to 2, and going in my project to try it out (using the context menu to generate the companion asset, setting the bool to true)... nothing happens.

    The change in the companion asset - even after a project save, does not trigger a reimport of the model. Reimporting the model shows that it does find the companion asset, and relies on its content...but the inconsistent warning still shows up...

    I tried switching the DependOnSourceAsset to DependsOnArtifact, but this had no change. I still have to manually reimport, and I still get the inconsistent warning.

    Interestingly, removing the companion asset does trigger a reimport and removes the extra asset's contents. It would seem like the fields within the companion asset are not respected for the purpose of hashing / warning, but the existence of the asset does?

    Having to create a whole object per special feature is far less than ideal and bloats the project... quite significantly, the companion assets alone mean 2 assets per thing that needs the special feature. It also has some maintenance cost on searching, committing, and moving assets around...

    For show of effort, I've opted to try and make an extremely easily visible result. I also added the MenuItem validation, changed the manual extension editing to use Path.ChangeExtension, and lastly made the context menu asset creation reimport the original asset, as the existence of the companion asset is meant to change its output.

    This example 'works' but only in the sense that it changes the original asset, if the companion asset exists or not. Even if you add fields to said companion asset, the original asset's result won't change / will generate the warning. I hope someone else finds it useful...
    Code (CSharp):
    1. using System.IO;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public class CustomConfiguration : ScriptableObject
    6. {
    7.     private const string MenuItemPath = @"CONTEXT/ModelImporter/Create Custom Configuration";
    8.    
    9.     [MenuItem(MenuItemPath)]
    10.     public static void CreateCompanionSetting(MenuCommand command)
    11.     {
    12.         var originalAssetPath = AssetDatabase.GetAssetPath(command.context);
    13.         var configPath = Path.ChangeExtension(originalAssetPath, "asset");
    14.         if (AssetDatabase.LoadAssetAtPath<CustomConfiguration>(configPath))
    15.             return;
    16.        
    17.         var config = CreateInstance<CustomConfiguration>();
    18.         if (!AssetDatabase.LoadMainAssetAtPath(configPath))
    19.             AssetDatabase.CreateAsset(config, configPath);
    20.         else
    21.             AssetDatabase.AddObjectToAsset(config, configPath);
    22.  
    23.         AssetDatabase.ImportAsset(originalAssetPath);
    24.  
    25.     }
    26.  
    27.     [MenuItem(MenuItemPath, true)]
    28.     private static bool ValidateCreateCompanionSetting(MenuCommand command)
    29.     {
    30.         var assetPath = AssetDatabase.GetAssetPath(command.context);
    31.         var settingsPath = Path.ChangeExtension(assetPath, ".asset");
    32.         return !AssetDatabase.LoadAssetAtPath<CustomConfiguration>(settingsPath);
    33.     }
    34. }
    35.  
    36.  
    37. public class ModelImporterCustomImport : AssetPostprocessor
    38. {
    39.     public override uint GetVersion() => 5;
    40.  
    41.     private void OnPostprocessModel(GameObject go)
    42.     {
    43.         var settingsPath = Path.ChangeExtension(assetPath, "asset");
    44.         context.DependsOnSourceAsset(settingsPath);
    45.        
    46.         var config = AssetDatabase.LoadAssetAtPath<CustomConfiguration>(settingsPath);
    47.         if (!config)
    48.             return;
    49.  
    50.         var material = new Material(Shader.Find("Specular")) { name = "Very Visible Example Material" };
    51.         context.AddObjectToAsset(material.name, material);
    52.     }
    53. }
    Now, this does bring up some more questions...
    I know that AssetImportContext.DependsOnArtifact exists... but attempting to use it to detect changes in the scriptableobject's bool state seems defunct.
    Is it because scriptableobjects have 'nothing to import'?
    If I make my own scriptedimporter type and use that as my artifact, would that work instead?
    Would I still have to make a dummy subobject import result?...

    I'm trying to figure out how to make fields changing on the companion asset actually affect the original asset's import state, without causing the warning, and without being limited to 1 variation per entire extra object. (mainly because the code to manage them is not easy to mass produce/maintain, relying on multiple methods, keyed attributes, and a tiny context menu...)

    Also, I tried to see if I can actually add extra visuals to any inspector, against my better judgement.
    Apparently the Addressables package uses 'Editor.finishedDefaultHeaderGUI' to render its asset checkbox. Expanding on this, you can do whatever you want at the top of the inspector, regardless of what you're inspecting.


    The above can be achieved with this code
    Code (CSharp):
    1.  
    2. [InitializeOnLoad]
    3. public static class SeeQualityOfLifeButtonsInInspectorHeader
    4. {
    5.     static SeeQualityOfLifeButtonsInInspectorHeader() => Editor.finishedDefaultHeaderGUI += ShowQualityOfLifeButtons;
    6.  
    7.     private static void ShowQualityOfLifeButtons(Editor editor)
    8.     {
    9.         if (!editor.target || editor.target is not ModelImporter)
    10.             return;
    11.  
    12.         GUILayout.BeginHorizontal();
    13.         if (GUILayout.Button("Create Companion Asset A"))
    14.             Debug.Log($"Do whatever you want here");
    15.         if (GUILayout.Button("Create Companion Asset B"))
    16.             Debug.Log($"Do whatever you want here");
    17.         GUILayout.FlexibleSpace();
    18.         GUILayout.EndHorizontal();
    19.     }
    20. }
    I don't love it, but it is far better than the prior manual hassles. With this, you can actually display - and edit - all the settings you want while still staring at the model importer, having one clean big picture.

    Is there any way to actually bind specific settings as dependencies?
    I see that registering a custom dependency cannot be done while importing the asset, which makes sense...
    I tried source asset, but the fields changing do not trigger a reimport for the asset that depends on them.
    I tried artifact, but there's seemingly no difference.
    Did I do something wrong? please help?

    And, going back to the userData each asset has... can those contribute to importing without the warnings? is there any way to register them as the dependency?
     
  8. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Hey there,

    I've tried the project you sent us (IN-28865) and didn't get any warning with and without your script, either with the full project you sent or while whipping the Library folder to start anew.
    I've tried your project on our most recent alpha build, on the latest 2021.3, and on 2023.1.11f1 which seems to be the version you reported the bug with.
    I can't say for sure what's going on with either your machine or your local project/unity install, but there is something wrong with it.
    What you can do is try on the latest 2021.3 LTS and delete your Library folder before opening your project. It'll take some time, but it should work. If not, I definitely have no idea what's happening.

    All your questions regarding my script failing seem related to the same issue, something is broken regarding the AssetDatabase and the dependencies on your project/unity install, and thus nothing related to the dependency system is working.

    Regardless of whether it's working or not on your side, the difference between DependsOnArtifact and DependsOnSourceAsset is that the first one will trigger a re-import if the imported artifact changes (the import result, which contains also all the dependencies declared, the Source Asset, and the generated Asset Objects).
    The second one will trigger a re-import only if the Source Asset (the file on disk or its meta file) is changed. So it will basically ignore any other dependency this asset may have during import.

    That's a nice found hack. Given most of our UI is slowly but surely moving to UIToolkit, I wouldn't rely too much on such tricks, but I also don't really have any good alternative to offer at the moment...

    There is a way that I've experimented in the past, not using CustomDependendecies but rather using GetOutputArtifactFilePath and GetArtifactFilePath.
    I don't have the time for now to create some examples for it, but the idea is to have a custom scripted importer for your companion file instead of being a ScriptableObject, and during the import, create an artifactfile for each of the settings.
    Then when using one of the settings, get the artifact file you're interested in based on the path of the companion file, and use the value from this file instead of reading the companion file directly.
    This way you'll depend on the content of the Artifact File instead of the whole companion file and only re-import when it changes.
    This is however way more complicated to handle, but a nice way to only depend exactly on one setting and not the whole companion file content.

    userData are being saved in the .meta file, so they re already part of the dependency for any imported asset.
     
  9. bastien_humeau

    bastien_humeau

    Unity Technologies

    Joined:
    Jun 14, 2017
    Posts:
    191
    Thinking about it, on any asset in Unity, you can right-click and select "Show in Import Activity window".
    This will show you all the dependencies your asset has, what triggered its last import, how long did it take, etc...
    In the options dropdown at the top, you can also select "Show previous import" which will show you all the previous versions of your asset saved in the Library folder.
    This is a very useful tool to help you understand what's happening with a specific asset and why it doesn't re-import when you think it should.