3
$\begingroup$

I am currently trying to make an Asset Library for our team. Since the current Asset Browser does not support Collections, my plan is to create one Blend file which will have all the assets in a combined mesh form, but I will also be preserving the original file - with separate meshes, textures and rigs - in a zip file dedicated to each asset. These zip files will share the same name and path as the assets in the blend file.

Now I am trying to add a button to the Asset Browser side panel using Python, which will detect the location of the active/ selected asset in the Asset Browser, detect its path, and copy the zip file of the same name from the source folder to the Downloads folder, so that my team does not have to manually search for the required file from a vast library.

Is there any way we can do that with the current limited functionality and python support for the Asset Browser?

For clarifications : All users will be using Windows, and the current latest version of Blender (3.1.2) for this.

$\endgroup$

2 Answers 2

5
$\begingroup$

You have to understand that the asset browser is a file browser under the hood. So the available elements in the given contexts are the same as the file browser.

Link to the context elements available to the asset browser

The context in this case has a handy selected_asset_files attribute you can use. It is stated it is a sequence of bpy.types.FileSelectEntry, a class which has a handy relative_path attribute. It will output the filepath relatively to the root of the current library.

The root of the current library can be inferred by either

  • Using the current filepath if we're looking at the Current File library
  • Using a User Library filepath if we're looking at a User Library.

The active library name can be fetched with context.area.spaces.active.params.asset_library_ref.

Link to the docs

If the current library name is LOCAL, it means we're looking at the current file. I have not tested what happens if a user library is named LOCAL, but I assume it would somehow bug out ?

Then, from the asset library name, you can fetch the asset library filepath with context.preferences.filepaths.asset_libraries which returns the container of all user libraries.

Link to the docs

Which gives us :

import bpy
from pathlib import Path


class PrintAssetPaths(bpy.types.Operator):
    bl_idname = "asset.print_filepaths"
    bl_label = "Print Filepaths"

    @classmethod
    def poll(cls, context):
        return context.selected_asset_files

    def execute(self, context):
        current_library_name = context.area.spaces.active.params.asset_library_ref
        if current_library_name == "LOCAL":  # Current file
            library_path = Path(bpy.data.filepath)  # Will be "." if file has never been saved
        else:
            library_path = Path(context.preferences.filepaths.asset_libraries.get(current_library_name).path)

        for asset_file in context.selected_asset_files:
            asset_fullpath = library_path / asset_file.relative_path
            if current_library_name == "LOCAL":
                # For some reason the relative path stops at the ID container in local file
                asset_fullpath /= asset_file.local_id.name
            print(asset_fullpath)  # Includes the path to the asset inside the .blend file
            asset_filepath = asset_fullpath.parent.parent
            print(asset_filepath)  # This is the file explorer path
            
        return {"FINISHED"}


def display_button(self, context):
    self.layout.operator(PrintAssetPaths.bl_idname)


def register():
    bpy.utils.register_class(PrintAssetPaths)
    bpy.types.ASSETBROWSER_MT_editor_menus.append(display_button)


def unregister():
    bpy.types.ASSETBROWSER_MT_editor_menus.remove(display_button)
    bpy.utils.unregister_class(PrintAssetPaths)


if __name__ == "__main__":
    register()

This will add a button to the header of the asset browser area and print out the filepaths of all selected assets when clicked.

enter image description here

$\endgroup$
7
  • 1
    $\begingroup$ Perfect! With a few minor tweaks and additions, the code worked exactly as I wanted it.. thanks a lot for the prompt and precise reply! $\endgroup$
    – Adisage
    Commented Apr 21, 2022 at 17:10
  • 1
    $\begingroup$ @Adisage happy to help ! Don't hesitate to also post your solution as an answer, it might help others in the process. :) cheers $\endgroup$
    – Gorgious
    Commented Apr 21, 2022 at 21:36
  • $\begingroup$ Good idea! Done ... $\endgroup$
    – Adisage
    Commented Apr 24, 2022 at 6:27
  • $\begingroup$ Note that context.area.spaces.active.params.asset_library_ref is no longer valid if the user set his category to "all" in blender 3.5 $\endgroup$
    – Fox
    Commented Jun 1, 2023 at 14:32
  • $\begingroup$ @Fox oh, right. Too bad ! I guess it's not available to the python API then ? $\endgroup$
    – Gorgious
    Commented Jun 1, 2023 at 17:01
1
$\begingroup$

Such a precise and elegant solution by Gorgious... I would have taken tons of lines of code to do the exact same thing (I am not a coder, as you might've guessed...)

Posting my final code with a few minor tweaks and additions, in case someone else is looking for the same :

import bpy
import shutil
import os
from pathlib import Path


class PrintAssetPaths(bpy.types.Operator):
    bl_idname = "asset.print_filepaths"
    bl_label = "Download Asset"

    @classmethod
    def poll(cls, context):
        return context.selected_asset_files

    def execute(self, context):
        current_library_name = context.area.spaces.active.params.asset_library_ref
        library_path = Path(context.preferences.filepaths.asset_libraries.get(current_library_name).path)

        for asset_file in context.selected_asset_files:
            asset_fullpath = library_path / asset_file.relative_path
            asset_filepath = asset_fullpath.parent.parent.parent
            asset_name = asset_file.name
            asset_zip = str(asset_filepath) + "\\" + str(asset_name) + ".zip"
            path_to_copy = str(Path.home() / "Downloads")
           
            shutil.copy(asset_zip, path_to_copy)
           
            print("File copied successfully!")
        return {"FINISHED"}


def display_button(self, context):
    self.layout.operator(PrintAssetPaths.bl_idname)


def register():
    bpy.utils.register_class(PrintAssetPaths)
    bpy.types.ASSETBROWSER_MT_editor_menus.append(display_button)


def unregister():
    bpy.types.ASSETBROWSER_MT_editor_menus.remove(display_button)
    bpy.utils.unregister_class(PrintAssetPaths)


if __name__ == "__main__":
    register()

If there's any more suggestions to make this better, kindly let me know...

Hope this helps! :)

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .