# Asset Types & Traits *Resolving a definite type for an asset is difficult. Instead, the following proposes a way to define type related characteristics (traits) of an asset, which can then be mapped to precise behavior.* ## What is the Type of an Asset? The type of an asset is a vague concept. Some considerations: - Is it the ID type? If so, we wouldn't be able to tell apart a compositor node tree from a geometry node tree. Or a mesh object from a curve object. So just ID type often isn't granular enough. Nor does that cover assets that are not IDs (files, USD assets, presets, etc.). - How much should it matter to the user if an image is already a Blender data-block or just an image on the hard drive? Or if an object is already native Blender data or will go through an importer after dragging in. What matters most is the object itself, the format it happens to be stored in is irrelevant *to a degree*. - An image could be a reference image, a bump map, a stencil texture, ... The fact that an asset is an image may not matter much to the user, what the image represents and thus what it can be used for does. So the proposal is to avoid the concept of a single static type. Instead, differentiate assets using type related characteristics, so called *traits*. ## Asset Traits Asset traits are keywords that specify certain type characteristics of an asset. They are loose (in the sense of having no knowledge about each other) but well defined. ![](https://i.imgur.com/SOmDYnj.png) With every trait added, the asset's type characteristics get defined more precisely. So traits are additive and increase specificity. Or put differently: Each trait removes ambiguity. Traits look a lot like the already supported asset tags. And they are similar, but traits are more focused on type-relevant characteristics of the asset, have well-defined meaning, and are managed by software. Tags are managed by the user (more specifically, an asset creator) to help browsing assets, and can contain arbitrary bits of information with or without well-defined meaning. :::info **Design Choices** - Redundancy allowed: There may be some redundancy in the traits of an asset. For example `Stencil` pretty much implies that it's also an image and a texture. This makes it easy for code to check high-level characteristics, while keeping the flexibility to do more granular checks as needed. - Implicit hierarchy: There's also often a semantic hierarchy between the traits. For example the traits of a mesh-object could also be specified in a path-like manner: `"ID/Object/Mesh"`. There doesn't seem to be a need for making the hierarchy explicit though, so it doesn't seem worth the overhead of defining a sensible path system. A loose set of strings seems simple and flexible. Plus, assets may have multiple independent traits, so just a single path-like trait wouldn't be sufficient anyway. - Style conventions: Traits are written using capitalized words and spaces as needed. This way they can be nicely exposed to the user if wanted, without additional "translation" from a coder-y style like `ObjectData`. For example, we may choose to display some traits as read-only tags to the user, since this information is useful for their browsing experience too. ::: ### Trait Glossary Since traits are supposed to be well-defined, we should keep a glossary of the traits Blender uses natively. Here are some example definitions: `ID` : The asset represents and needs to be imported as native ID data-block (using link/append). `Object` : The asset represents and should be imported as Object. Note: This may also be set for assets that are not a native format and use an importer, e.g. an Object defined by a GLTF file. Native Blender objects have both the `Object`- and the `ID`-trait set. `Modifier` : The asset may be imported as modifier. For example the traits `Node Tree`, `Geometry Nodes`, and `Mesh Modifier` would together indicate a geometry node-group that can not only be used as regular node group, but also be dropped into the modifier stack of a mesh object. ### Custom Traits Traits don't have to be defined by Blender natively. An add-on may introduce new traits (which it would have to manage itself). For example an add-on adding USD asset support, or support for using Inkscape brushes in Blender. The add-ons can provide own traits to indicate these assets. *Well-defined* just means there is a reasonably clear definition, which is not necessarily provided by Blender itself. ### Where do Traits Come From? The stork brings them :baby: Seriously though, how exactly traits are defined unfortunately depends on the type of asset too. Whatever operation turns data into an asset (as per the asset system's understanding) has to set the initial traits. For example, marking a brush as asset could do the following: - Generic *Mark as Asset* code sets the `ID` and `Brush` traits. - From there a new `IDTypeInfo::refine_asset_traits()` could add further traits, like `Sculpt Mode`, to indicate a sculpt brush. :::warning **TODO** How do we keep traits updated? E.g. a texture may switch from being a stencil to a regular texture and vice versa. Updating traits on save may be a good start, but since traits drive operations like preview rendering, they also need to be updated before any such operation. Could be just a call to `IDTypeInfo::refine_asset_traits()`? ::: ### Forward Compatibility While this is a forward compatible system in itself, there is the issue that we can't possibly predict all the possible traits that might be useful in future. For example, will we need image format details as traits in the future? Versioning code could of course add new traits as needed, but then that code may need the fully loaded asset data, not just the lightweight representation of an asset we load for the asset browser/system. Note that this is a general problem with assets, even if we store type information differently. You just don't know what information you'll need from a the representation of an asset in future. ### OpenAssetIO OpenAssetIO also uses the concept of traits. It's similar to what is proposed here, although it goes a bit further to support communicating more general metadata this way too (which Blender handles differently). Point is, we can easily map Blender's asset traits to the one of OpenAssetIO. There may have to be a small translation layer though, since our trait terminology would be quite Blender-focused. An `ID` trait could be translated to a `blender-ID` OpenAssetIO trait. In general prefixing Blender specific traits with `blender-` seems like a good idea. Further OpenAssetIO traits can be added as needed then. E.g. the Blender `Image` trait may be expanded into a `image` and `raster` trait for OpenAssetIO (indicating an image stored in a pixel format). ## Type Specific Behavior *TL;DR: Operations like importing, preview rendering, drag & drop or managing asset metadata should behave differently depending on the type of an asset. How can this be supported reliably?* Dragging an object into the scene should trigger a different operation than activating a brush. Rendering an object preview may want to use a different preview-scene setup than a material preview. How can we make sure Blender behaves correctly (or at least predictably) for each asset? ![Operations requiring different behavior based on the asset type](https://i.imgur.com/cXOAl9a.png) <!-- These operations have to be supported and are type specific: - **Import/apply:** An asset is either added to a scene (imported), or it's used to modify existing data (applied). Different methods of importing/applying are needed for objects, brushes, files, etc. - **Drag & drop:** While dragging an asset into the scene, visual feedback should be provided. An object may want to draw a bounding box, a material may want to highlight the faces the material would be assigned to. - **Preview Rendering:** An object may want to use a different scene and different render settings to render a default preview than a material. Animation pose snippets may even want to render a multi-frame preview. Note that different "providers" may also want to render these differently: Blender's default previews, an asset market, a production environment may all want to provide their own default preview rendering, optimized for their use-case. - **Determining asset type icon:** Together with the preview, the asset browser displays an icon to indicate the type of an asset. Querying this is type specific, e.g.: Rather than a generic node tree icon, show the node tree type (shading, geometry nodes, compositing nodes, etc.). Type specific operations may appear in (at least) two scenarios: - **Creating/updating assets:** When creating an asset, or an asset that is "life" in some Blender session, certain operations manage data - **Using/providing assets:** When an asset is --> These operations fall into two categories: * **Produce** (create/update): Operations that are needed when creating an asset, or to update something related to the asset. For example, rendering a preview, or keeping the metadata updated as the asset is changed. * **Consume** (represent/use): Operations that are needed to display the asset in any way to the user, without access to the fully loaded asset. For example, displaying a default preview if the asset doesn't have its own, or drawing a bounding box while dragging an object over the 3D View. Taking the operations displayed in the picture above, they can be categorized as follows: | Produce | Consume | | -------- | -------- | |Mark as Asset, <br> Preview Rendering, <br> Manage Asset Metadata, <br> Assign to Default Catalog <br> | Drag & Drop, <br> Import/Apply, <br> Query Type Icon, <br> Query Default Preview <br> | ### From Traits to Behavior The mentioned operations can be implemented in some kind of callback (for example a "naked" callback or an operator). What's needed is a way to map the traits of an asset to these callbacks. Code could request the asset system to resolve a set of traits to a set of callbacks. For example (dummy code): ```java interface AssetProducerOperations { void render_preview(); void update_metadata(); AssetCatalog get_default_catalog(); } AssetProducerOperations producer_ops = asset_system.make_producer_from_traits(asset.traits); ``` ```java interface AssetConsumerOperations { void draw_drag_feedback(); void import_or_apply(); IconID get_icon(); PreviewImage get_preview(); } AssetConsumerOperations consume_ops = asset_system.make_consumer_from_traits(asset.traits); ``` :::warning This so far is only a vague & early idea. Is this predictable enough? What if multiple callbacks fight for the same set of traits? Should the specificity of traits be used as tie breaker? And/or some concept of asset "owner" (e.g. so asset market X can add their own preview rendering for materials in its library). ::: :::warning **TODO** How can code register callbacks for a given set of traits? C++ or Python. :::