"Custom" file browser

Hey guys,

i have archive files of a game which contain 3D models and such and i want to write an addon for blender that makes it possible to import these models. Now i could write a tool (outside of blender) that simply extracts the whole archive, but this takes a while as the archive is rather big. Instead i would like to be able to navigate through the archive structure inside of blender, then choosing a specific model and extracting and importing only that chosen model into blender.

The idea is to show a file browser dialog in which you will select an archive file. You should then be presented what’s inside the archive (the folders and files) and be able to go into subfolders of the archive and selecting a file (just like you can with a zip file in e.g. windows explorer).

Now the question is how i can realise this in the blender interface. It would be ideal if i could set the contents of the built-in file browser myself (and react to folder selections etc.) but i couldn’t find anything regarding that. Alternatively i could see it working with a setup like the Material/Particle/Scene lists are made (is that done with a CollectionProperty?).

Any suggestions on how to to this the proper way would be great :slight_smile:

greetz Sphere

I assume you would need to add support for zip-files in the file browser’s C code. You can’t do this with Python.

Thanks for the answer.

The files i wanna open aren’t zip files but some specific archives for that game (".raf"-files to be exact). But the file structure is known so reading them won’t be a problem.

Alright, so if i can’t add functionality to or use the layout of the filebrowser in python then i have to use another (not quite as pretty) approach.

I’m now thinking about using an UIList (just like in the “UI List Simple” template in the text editor) which i would display on the bottom left of the file browser. If an raf-archive is selected i would start using that UIList as my own, “custom archive browser”. Would you recommend that approach or is there any better solution you could think of?

So i got finally back to work a bit on this. Like i mentioned, i’m using an UIList to have a “custom browser”. Using the panel on the bottom left of the file browser as described above was working fine, but it was rather unintuitive. I wanted to have a separate dialog for my custom browser, therefore i am calling a new operator “RAFBrowser” when opening a file with the filebrowser (operator ImportFilearchives):

bl_info = {
    "name": "Riot Archive File (RAF)",
    "blender": (2, 71, 0),
    "location": "File > Import",
    "description": "Import LoL data of an Riot Archive File",
    "category": "Import-Export"}


import bpy
from io_scene_lolraf import raf_utils
from bpy.props import (StringProperty, BoolProperty, CollectionProperty,
                       IntProperty)


class ImportFilearchives(bpy.types.Operator):
    """Import whole filearchives directory."""
    bl_idname = "import_scene.rafs"
    bl_label = 'Import LoL filearchives'
    
    directory = StringProperty(name="'filearchives' folder", 
                               subtype="DIR_PATH", options={'HIDDEN'})
    filter_folder = BoolProperty(default=True, options={'HIDDEN'})
    filter_glob = StringProperty(default="", options={'HIDDEN'})
    
    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def execute(self, context):
        # TODO: Validate filepath
        bpy.ops.ui.raf_browser('INVOKE_DEFAULT',filepath=self.directory)
        return {'FINISHED'}
    

class RAFEntry(bpy.types.PropertyGroup):
    name = bpy.props.StringProperty()
    selected = bpy.props.BoolProperty(name="")


archive = None
class RAFBrowser(bpy.types.Operator):
    bl_idname = "ui.raf_browser"
    bl_label = "RAF-browser"
    bl_options = {'INTERNAL'}
    
    filepath = StringProperty()
    current_dir = CollectionProperty(type=RAFEntry)
    selected_index = IntProperty(default=0)
    
    def invoke(self, context, event):
        global archive
        archive = raf_utils.RAFArchive(self.filepath)
        return context.window_manager.invoke_props_dialog(self)
    
    def draw(self, context):
        if self.selected_index != -1:
            print("new selected_index: " + str(self.selected_index))
            global archive
            # TODO: change current directory of archive
            self.current_dir.clear()
            for dir in archive.current_dir():
                entry = self.current_dir.add()
                entry.name = dir
            self.selected_index = -1
        self.layout.template_list("RAFDirList", "", self, "current_dir", self, "selected_index")
    
    def execute(self, context):
        print("execute")
        return {'FINISHED'}


class RAFDirList(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        operator = data
        raf_entry = item
        
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.prop(raf_entry, "name", text="", emboss=False, icon_value=icon)
            layout.prop(raf_entry, "selected")
        elif self.layout_type in {'GRID'}:
            layout.alignment = 'CENTER'
            layout.label(text="", icon_value=icon)
        

def menu_func_import(self, context):
    self.layout.operator(ImportFilearchives.bl_idname, text="LoL Filearchives")


def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_file_import.append(menu_func_import)


def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)


if __name__ == "__main__":
    import imp
    imp.reload(raf_utils)
    bpy.utils.register_module(__name__)



Using the “invoke_props_dialog”-function it looks fairly pretty and how i would like it, but unfortunately the “draw”-function of the RAFBrowser is now called only once, which means that there are no checks whether the selected_index changed, so i can’t browse in the archive by selecting folders.

Is there a way to make this work? Or is there something that more or less looks like using “invoke_props_dialog” but has permanent updates on the “draw”-function?

Ideasman published a patch to change how dialogs / popups work, not sure if draw() is called more than once with this one.