Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

ScriptedImporter question

Discussion in 'Editor & General Support' started by Gildor2, Aug 11, 2017.

  1. Gildor2

    Gildor2

    Joined:
    Feb 3, 2014
    Posts:
    4
    Hi,

    I'm making a scene importer for Unity for glTF format using new experimental ScriptedImporter API. This format has "main" .gltf file with scene description, and possibly one or more .buffer files containing binary data. I faced the problem that I can't access secondary files during import. The problem is that Unity copies original file (main file) to Assets directory and then passes it for import. Then OnImportAsset() function is called with ctx containing the path of copied glTF file. Secondary files containing geometry information are not copied and located at path which was selected with "Import New Asset" menu command. When my importer tries to access a secondary file using ctx.assetPath or ScriptedImporter.assetPath, it fails because the file wasn't copied.

    Is there any way of getting the path of original imported asset from ScriptedImporter? I've checked AlembicImporter and UsdImporter code from Github, but it seems these plugins doesn't access any files except the "main" one, i.e. they are not trying to load any texture etc.

    Probably I'm doing something wrong? Or I should consider using older approach for importing resources, when I'm binding importer to menu command, showing import GUI etc? Could anyone help me please?
     
  2. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    Hello,

    You are running into a conceptual hardship that Unity's Asset import pipeline has to deal with: one asset produced from multiple source files.

    Our import pipeline (even the native importers have this restriction) explicitly state's that it only supports a 1-1 relationship between a resulting asset and it's source file from which it is extracted. There are a few issues that dictated this constraint. A major one is dependency tracking: the asset pipeline has no support for reading/storing dependency information (this asset needs files x, y and z) which is be needed to trigger re-import events (say file y gets edited, that would have to trigger a re-import).

    I realise this is not very helpful to you in your situation, so here are some options for you to unstuck your project:

    Opt. 1: Two step import process:
    - 1: have a menu entry that allows selecting x files that make up an asset, zip them / bundle them into 1 file and give that file to be imported (save it in the asset directory)
    - 2: declare an importer that handles that type of bundle file, have it extract the bundle to a temp dir and access the files in the temp dir as you wish.
    - Optional: add a way to update the content of the bundle when the original files change. By re-writing the aggregate file, a re-import event will get triggered automatically.

    Opt. 2: manage the dependencies your self.
    - add a importer for .gltf and an other one for .buffer files.
    - The buffer importer imports a dummy asset (with current release, OnImport must generate an asset, but that has been removed for next build of Unity). But this import checks for the presence of it associated .gltf asset in the aset database. if it finds it, it triggers an explicit re-import of the .gltf asset.
    - the .gltf importer does it's job normally and generates an error if the buffer sources files can't be found.

    opt. 3: proxy file
    - similar to opt 1, but instead of aggregating the source files into one file, create a txt file that stores the path's of the various files that make up the asset.
    - bad thing about this is that Unity wont be able to tell when the target files have changed and if you share the project across different machines, the path's in the file need to be valid.

    Opt 4: no scripted importer / post import event
    - don't go with scripted importers and add triggers for after import event's and

    If I where doing it, I'd go with opt 1, unless the source files are HUGE, as it is in line with where the asset pipeline is going: (1) get the source file (2) process the source file. It always pays to be conceptually aligned with the framework your building on.
    Opt. 4 and Opt.2 are messy in that they generate extra assets and will take a bit of fiddling to get to work properly.
    Opt 3. is the worst one but has the advantage of being very simple to implement and has the smallest tech stack.

    Cheers,
    -J
    (Unity DCC team)
     
  3. Gildor2

    Gildor2

    Joined:
    Feb 3, 2014
    Posts:
    4
    Hi Jason,

    Thank you for long and detailed answer. I have a question about ScriptedImporter. Why file which is being imported is copied into Assets directory, and only then passed to the importer? Taking into account your "option #3" it is not required, i.e. imported asset will work without having a full source file inside Assets directory. If I'm importing a mesh with my importer, I'd prefer to have a generated .mesh file located in that directory, but not glTF. Even more, I can distribute that .mesh after import without asking users to have my importer plugin in their projects.

    My current solution is to use "old style" import (not sure if it has some special name): added a menu "import glTF" item and callback. Callback parses source files, generates nodes (game objects) and meshes, imports materials and textures. I don't know yet if these assets will be copyable between Unity projects or not, but I hope so.

    Having 1:1 mapping is a weak point of Unity's scripted importer pipeline. I think that monitoring for asset changes could be delegated by user (this may be described as plugin's limitation). So user will simply issue "reimport" command. by himself. However current implementation doesn't allow to do that at all. If I'll have a scene with materials and textures located separately, it is absolutely impossible to import that with use of scripted importer. Alternatively, monitoring for changes could be done with plugin. If engine can't store multiple file names and dates, these dates/filenames could be converted to hash and stored instead of a single timestamp.

    If I'd go with ScriptedImporter workflow for my project, I'd ask to do the following changes to the editor:

    1. Make ScriptedImporter pipeline working without having a source file copied into Assets directory. I.e. copying can be done, but not 100% required.
    2. If copying is still performed, allow ScriptedImporter and/or context class to get access to the name of original file, so plugin can locate all secondary files.
    3. Probably delegate checks for source asset updates to ScriptedImporter implementation, i.e. this importer not only will be able to import assets, but also can verify which files are needed. This will also allow you to implement zipping if all source files by Unity, if needed (see your "option #1").

    Regarding your possible solutions.

    1. Actually you're offering me to write importer for a different file format (*zipped gltf"). Doing zipping by hands, even with executing a single menu command, won't make pipeline easier than doing a "regular" import and issuing reimport by hands.
    2. Also not very good. Not easy to end user, plus for example if I'm importing a file with many binary files and textures - I'll need to import each by hands. Too easily to make a mistake here. Plus, possible conflicts with other plugins, if someone will need to import a scene in different format, but will have textures too, or if engine will have file extension collision (actually glTF has not "buffer", but "bin" extension - it's too generic to register it).
    3. Sounds like a "manifest" file. Probably may be an option for us, because we're doing glTF EXPORTER as well, but we won't be able to import glTF files exported with use of other software.

    Thanks,
    Konstantin
    4D Pipeline
     
  4. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    Hey,

    "
    Thank you for long and detailed answer. I have a question about ScriptedImporter. Why file which is being imported is copied into Assets directory, and only then passed to the importer? Taking into account your "option #3" it is not required, i.e. imported asset will work without having a full source file inside Assets directory. If I'm importing a mesh with my importer, I'd prefer to have a generated .mesh file located in that directory, but not glTF. Even more, I can distribute that .mesh after import without asking users to have my importer plugin in their projects.
    "
    What you are describing here is a converter not an importer: one time event of converting a x file into a unity mesh and then sharing THAT asset. That is not the goal of unity importers (scripted or not).

    As for your "old style" importer, it's actually a converter as the asset pipeline has no knowledge of the original files used to generate what gets stored in asset directory from the resulting operation.

    Here is a use case that you should consider to better understand Importers vs. Converters: Source control. Unity projects are setup to be source control friendly. There are a lot of subdirectories in a unity project, but only Assets and ProjectSettings typically end up in source control. The asset importers job is to take the content of the Assets directory and transform & save into the Library directory, which does not get stored in source control. The files in the library subdirectory are platform specific (an importer can generate wildly different files depending on targeting Wii or Windows PC or iPhone ). But the source asset file (what is found in the Assets subdirectory) is universal to ALL platforms.

    If you want some process to take external files, convert them into a mesh asset, and store that mesh into the asset directory so that is can be source controlled, then asset importers are not what you need: you need your "old way".

    But if you do want to source control the original files, then they must ALL get stored in the Asset subdirectory. In which case you will write an asset importer that relies on all the files being INSIDE the assets directory.

    Cheers,
    -J
    (DCC Team)
     
  5. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I am running into this issue here as well, so I would love to see some discussion on it. I'm completely open about it though as I have no pre-judgment as to which is the best way to approach it

    I will detail my situation as best I can, but before I do that I want to cover a more generic situation, which is importing models with materials and textures.

    You mention a 1:1 relationship. A 1:1 relationship would imply that a user needs to import a mesh, create materials for that mesh, and import textures to use on those materials all as separate steps, which doesn't sound very user friendly to me. I haven't done much importing using Unity's native system, but surely it simplifies this for the artist when importing something like say an FBX file right? If I import an FBX file into Unity, I would expect Unity to import the mesh, create the materials, and import any textures that are referenced in the FBX. This is no longer a 1:1 relationship.

    Now our system is significantly more complicated, as I am trying to hook in a VFX Film asset system into Unity. From the artist perspective there isn't even a concept of directories or files, just asset contexts, names, and versions. These are certainly analogous to a traditional file system, but it is abstracted. Instead of browsing to a file on disk, an artist would browse an asset database to find a given show, scene, and shot, then select an asset in that shot.

    For us an asset is conceptually already a bundle, or as we call them, a package. It is essentially a definition file that points to other files on disk for the mesh, textures, etc. So when importing this to Unity would we import this package only, and then use that to generate all the ancillary materials and textures? Would the textures be imported as additional files or somehow contained in the bundle as it lives inside Unity? What I mean by that is, would the prefab contain the texture files as sub assets, or would the texture files be imported as additional top level assets by copying them into the project directory? If the latter, things get sort of tricky I think because you need to link them into the materials so you essentially need Unity to start processing your custom importer, then stop to import the textures, then go back to your importer so you can now hook them up. Seems like that wouldn't be doable.
     
  6. markvi

    markvi

    Joined:
    Oct 31, 2016
    Posts:
    118
    You're right, to a point. An FBX is imported as a prefab, however, and a prefab can contain multiple assets, including meshes, materials, game objects, etc. Starting with 2017.2, the default behaviour is to embed materials in the prefab (see https://docs.google.com/document/d/1k-uWwslUx1ZPcmT609PZiq7hwCpRuFc-iMEq9mQgsRU). Textures and materials are no longer automatically extracted - instead there's a button on the importer that you must use to manually extract any embedded textures (and optionally materials as well).

    What Gildor2 is asking for is a many:1 relationship, which Unity can only support using the workarounds Jason listed.

    It sounds like you're asking for something different again: a way to map a location outside of your project into your project. We're in the process of adding support for this by abstracting the filesystem. We needed to do this for packages - they're shared across multiple projects, but "appear" in your project as though they're local. This is called the Unity VFS, and it's still internal right now. Ultimately this will be opened up and you can implement your own arbitrary VFS mappings in C#.

    The other part of the puzzle is an import graph - making it possible to procedurally construct assets from disparate parts, and reuse that logic. There's no way to do that now - importers are mostly written in C++, and are black boxes. We're committed to C# and scripted importers - and procedural import workflows are part of that roadmap.
     
  7. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    As a first step we're fine with the assets being copied into the local project, though a more abstract system in the future certainly sounds great!

    What I'm struggling with understanding right now though is how to handle the image files used as textures in the materials. I have a basic ScriptedImporter working that imports the geometry and creates the appropriate materials for each sub mesh (we are using a UDIM workflow, so for right now I am just splitting these out into submeshes). The next step is to bring in the image files as textures to hook up to the materials, and this is where I am kind of stuck. Using your ZIP file (#1) workflow above, I can extract everything to a temp working directory which would give me the JPG files used for textures, but I need to get them into the project somehow. Would these be new top level assets? IE just copy them to the project folder and let Unity import them on its own as top level assets? Or would they somehow be imported by my ScriptedImporter as SubAssets in the prefab?

    If the former, top level assets imported by unity, then I am unsure how to coordinate the import so that they are addressable by my ScriptedImporter to link them into the materials. If I copy them over, presumably Unity won't actually import them until my ScriptedImporter is done running, but by then it is too late.

    If the latter, as sub assets, then it seems more controllable, but I'm not quite sure how to go about dynamically importing an image file as a sub asset of my main prefab.
     
  8. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I looked over the document you linked, and that is pretty much what I want to do with our Model assets. Import them as a single prefab that contains the meshes, materials, and textures all as sub assets.

    Can you give me any information, or point me to some documentation, on how I can go about importing the texture files as sub assets in the prefab?
     
  9. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I managed to get this all working, and it is quite nice :)

    I am handling the textures as sub-assets of the prefab which keeps everything clean, and makes hooking them up to the materials easy. That said I don't know if what I am doing is really correct. It is rather slow and has some other drawbacks (like no feedback to the user for a slow operation) so I'm definitely open to suggestions if there is a better way to do it.

    Essentially though what I do is for each JPG file I have in my bundle, I open it as a Bitmap using System.Drawing, then rotate it because, Windows, and iterate through each pixel to get its color. I then create a new Texture2D for Unity and populate it with those pixels, then add that object as a subasset.
     
  10. markvi

    markvi

    Joined:
    Oct 31, 2016
    Posts:
    118
    That sounds fine. The only optimization I can think of would be using Texture2D.LoadRawTextureData (if you're not already) to avoid looping through pixels (assuming there's a system call you can use to convert your pixels to the layout and data format that matches your Texture2D).
     
  11. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    So I have this all working for the most part except one big problem.

    The process works pretty much as you suggested. It is a two step process, though the intention is to make it seem as one step to the artist. They browse our asset database through a custom EditorWindow to select an asset. We then have a process that grabs all the subassets required (alembic model and textures), and zip this up into a custom bundle. The custom bundle is then copied to the Assets folder, where the ScriptedImporter (step 2) will take it and import it.

    The problem I am seeing though is that once the custom bundle is copied to the project assets folder... nothing happens until such time as you alt-tab away from Unity and back again, and then Unity suddenly sees it. This is obviously not ideal :(

    Is there maybe some UnityEditor function I should call after placing the bundle into the assets folder to tell it to rescan?
     
  12. markvi

    markvi

    Joined:
    Oct 31, 2016
    Posts:
    118
  13. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Thanks that was the ticket!

    Now with everything I tried a larger asset and ran into another problem that I think is related to bundling all the subassets up as one item in the project. My "bundle" is a Zip file with no compression for speed purposes. In the case of this test the bundle is around 500mb. In this is a few Alembic files and about 1,500 (yes you read that right) JPG 1k textures. *glares at people over freaking using UDIMs*.

    When I process this, and importer it into Unity I create a new material for each UDIM. This results in 89 materials created as sub-assets, along with all the textures.

    I'm seeing a nasty error from Unity about a Serialized File size of 5.29GB!!! When I dig up the file I see it is a hashed file made in the Library/metadata

    Now I fully accept that this asset is a worst case test and severely overbuilt, but why is it turning into a 5GB file?!
     
  14. markvi

    markvi

    Joined:
    Oct 31, 2016
    Posts:
    118
    Importers create assets. These in turn live in Library and are in Unity's internal serialized format. Each file in Assets/ is assigned a GUID (visible in its .meta file) and all objects it generates are contained in the same asset, which is stored in Library/metadata/xx/xxxxxxxxxxxxxxxx (where xxx... is the GUID). So you're effectively creating an uncompressed file containing 1500 JPGs + Alembics. Serialized texture assets contain raw pixels as well as mipmaps, so that's where the 5GB comes from.

    You're trying to use Unity in a way that it wasn't intended to be used. I'd extract the textures into their own directory and let Unity handle importing them. That way you'll get one asset per texture, instead of one ginormous 5GB asset. This is how the FBX importer works - prior to 2017.2, textures were extracted automatically on every import to a directory named <modelname>.fbm. In 2017.2, we no longer extract textures automatically - users need to explicitly extract embedded media via a button on the importer. Everything else is a sub-asset (including materials).
     
    fherbst likes this.
  15. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    But from 500mb to 5gb? That seems like an awfully large increase in size. So much so that I suspect something odd going on.

    Splitting it up seems like it is going to cause other issues, not the least of which is import order. How do I ensure Unity has already imported the texture files before my custom importer runs so that I can then hook them up to the materials?

    It also violates the 1:1 goal you originally mentioned :)
     
  16. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Ok so I found the AssetDatabase.ImportAsset() method so using that I am able to coordinate with Unity to have the texture files imported as stand-alone assets and then it looks like I should be able to grab them back out of the AssetDatabase by name after that, though I haven't gotten to that point quite yet. That's on my plate for today along with figuring out how to handle submeshes.
     
  17. rubeng

    rubeng

    Joined:
    Apr 20, 2013
    Posts:
    60
    Hi, I am not testing the new ScriptedImporter api yet, but I use something that was mentioned as a problem before

    Our import pipeline (even the native importers have this restriction) explicitly state's that it only supports a 1-1 relationship between a resulting asset and it's source file from which it is extracted. There are a few issues that dictated this constraint.
    What we did in our project is add configuration files to some folders that contain sprites so that we can maintain all the sprite/texture import data in a controlled and automated way.

    What happens is that on import, we navigate through the folders upwards merging the config from the configuration files, and then using that data to setup the sprite importer.

    This allow us to update or add sprites to our project without having to worry about setting up the importers, and making sure that our guidelines are followed (no mipmap, pixels per meter, etc) depending on where in the file structure the sprites are found

    config at the base of one of our x1 sprite folders

    {
    "pixelsPerUnit" : 100,
    "maxTextureSize" : 2048,
    "textureFormat" : "AutomaticTruecolor"
    }

    and somewhere deeper inside the folder hierarchy we would have a config file like
    {
    "packTag" : "ExoPackFightSignx1"
    }

    this would make that any sprite found deeper than this config file would be imported as defined.

    The problem with this of course is that this creates a dependency, between the sprite asset and the series of config files in its hierarchy.

    Is there any proper way to support this? or will this be supported with the new ScriptedImporter api?

    This is the code of the asset importer just in case someone finds it useful

    https://gist.github.com/rgarat/84efd68b24447fb4122bf7951f2ce3a6

    Thanks
     
  18. JDB-Artist

    JDB-Artist

    Joined:
    Dec 5, 2012
    Posts:
    41
    Sorry for reviving an old thread, but since I just implemented a ScriptedImport, how would I give the user an equivalent to a button "Extract Materials" as found on standard importers?
     
  19. markvi

    markvi

    Joined:
    Oct 31, 2016
    Posts:
    118
    To implement the "Extract Materials" button, you call PrefabUtility.ExtractMaterialsFromAsset, which is unfortunately internal. You can call it via reflection. Its signature is:

    Code (CSharp):
    1. internal static void ExtractMaterialsFromAsset(Object[] targets, string destinationPath)
    targets
    is an array of objects to extract, and
    destinationPath
    is the directory in which to create the extracted materials (must be a directory that exists).

    Also maybe relevant: we recently implemented material mapping (not extraction, but mapping existing Unity materials to material references in an imported .abc file) in the Alembic importer: code here.