Open Library Path from Outliner

Hello,

I’have lately been using more linked objects/collections than before, and while one usually knows the directory or can go to/find it pretty easy from memory, its a different story when looking up files from e.g. a network drive or re-opening older files.

I kind of felt the need to more easily acces the source path of the library from within the outliner>File Mode, or other parts in the interface. I came up with and added this operator to the context menu in the Outliner:

It just opens a explorer window and selects the file in question - but does not open the .blend.

import bpy
import os
import subprocess

from bpy.types import (
    Collection,
    Panel,
    Operator,
    PropertyGroup)

from bpy.props import (StringProperty, EnumProperty, IntProperty,
                       FloatProperty, BoolProperty, PointerProperty)


debugging = False

        
def explorer_on_file(url):

    #opens a windows explorer window """
    subprocess.Popen(f'explorer /select,"{url}"')
      

        
class Open_Directory(Operator):
    bl_label = "Open Directory"
    bl_idname = "open.directory" 
    bl_description = "Opens a new explorer window of the source directory" 
    
    path: bpy.props.StringProperty(name="Path Argument", default="")
    
    bl_options = {'REGISTER', 'UNDO'} 
    
    def execute(self, context):
    
        # Validate if path exists
        if not os.path.exists(self.path):
        
            self.report({'WARNING'}, 'Path/File does not exist anymore')
            print(f'path {self.path} does not exist')
            
            return {'FINISHED'}
            
        # check if path is not None and then Open   
        if self.path != '':
            # opens file and selects it
            explorer_on_file(self.path)
            
            print ("Opening directory: " + self.path) if debugging else None
            self.report({'INFO'}, f' Opened: {self.path}')
            
        return {'FINISHED'}         

def draw_open_directory(self, context):

    global debugging
    selected_item = context.selected_ids[0] if context.selected_ids else None
    
    #print(str(selected_item))
    #print(selected_item.name)
    
    def is_linked_library(item):
    
        # Check if the string representation contains the text "Library" ### Rough Workaround
        return "Library" in str(item)[:20], bpy.path.abspath(getattr(item, 'filepath', None))
    
    if selected_item:
        is_linked = is_linked_library(selected_item)[0]
        directory_path = is_linked_library(selected_item)[1]
        
        print(f"Is linked library: {is_linked}") if debugging else None
        print (directory_path) if debugging else None
        
        if is_linked == True:

            op = self.layout.operator("open.directory", icon='FILE_FOLDER', text='Open Source').path = directory_path

def register():
    bpy.utils.register_class(Open_Directory)
    bpy.types.OUTLINER_MT_context_menu.prepend(draw_open_directory)
    

def unregister():
    bpy.utils.unregister_class(Open_Directory)
    bpy.types.OUTLINER_MT_context_menu.remove(draw_open_directory)
    
if __name__ == "__main__":
    register()

Here is my question, i’m a novice scripter and wasn’t able to somehow get the type of the active item in the outliner, however:

context.selected_ids[0]

Outputs a struct that contains the type as a string, so i’ve used:

# Check if the string representation contains the text "Library" ### Rough Workaround
        return "Library" in str(item)[:20]

as a validation to check if the operator should be displayed or not. This works, but is more than likely not the correct way to handle this. What would be a better way to tackle this? Is there already a more accessible solution available?

If i want to take this further than a startup script, would this code be something that needed to be implemented as C++, or could it remain as a python script? If this is not some random edge case i’d get my feet wet and love to help add it to the main branch.

I’ve also made the same functions available from the Object>Instancing Panel together with a simple label that shows the filepath, but cut out the code from here since it differs a bit, also checks if its a linked object or collection etc.

3 Likes

my viewport version:

obj = bpy.context.active_object
if obj.data.library:
    if os.path.exists(obj.data.library.filepath):
        print('opening.... ', obj.data.library.filepath)
        import subprocess

check… if obj.data.library is true

1 Like

Thank you, this does work with View Layer Objects/Collections but not for the Outliner in 'Blender File’ Mode and is pretty much along what i’m doing, however i’m looking to set one element here as active:
example1
example2

so its in a different context, hence the temporary context override:

if bpy.context.area.type!='OUTLINER':
    #If needed find the outliner area
    for area in bpy.context.screen.areas:
        if area.type == 'OUTLINER':
            areaOverride=area
            
with bpy.context.temp_override(area=areaOverride):
    bpy.context.space_data.display_mode = 'LIBRARIES'

obj.data.library is always grabbing the view layer object. To acces an object from in the File-View Mode i think that we need to use context.selected_ids:

selected_item = context.selected_ids[0] if context.selected_ids else None

This works, however I thought that it would be possible to check with

context.selected_ids[0].data.library

after the temp override, but somehow the context is lost as it returns an error that says context is not defined.

Pretty much, all i want to do is to run the operator that lets me relocate the library source from a different button/location other than the outliner. with:

bpy.ops.outliner.lib_operation(type='RELOCATE')

In order to work it needs an active element, which i want to set beforehand. The active element needs to be a path - i think.

1 Like