8
$\begingroup$

file icon does nothing

The dialog above is created with the code below. When I click on the file icon (see red arrow), nothing happens. Is there a proper way to get these to just work or at least point to a custom operator which invokes context.window_manager.fileselect_add(self) or something similar?

It really seems like the functionality I want should be built into the StringProperty when using FILE_PATH as a subtype... I must be missing something obvious.

What I am trying to do is execute a single operation that requires selecting more than one file path. I can invoke 2 fileselect_add's one after the other, but it becomes confusing because right when you click to accept the path the new path window opens immediately. It is just too confusing as to which one is being selected as there is really no way to customize the fileselect_add window to any usefull degree (as far as I am aware) to make it more obvious what the user is selecting.

Dialog code:

class mydialog(bpy.types.Operator):
        bl_idname = "tools.mydialog"
        bl_label = "My Dialog"

        inFilePath = StringProperty(name="input file", subtype="FILE_PATH")
        outFilePrefix = StringProperty(name="out prefix", subtype="FILE_PATH")

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)

    def execute(self, context):
        print(self.inFilePath)
        print(self.outFilePrefix)
        return {'FINISHED'}
$\endgroup$
  • 1
    $\begingroup$ Not sure why the file button doesn't work (your operator may be blocking context changes) but I believe you will find this idea won't work. Selecting a file will take the user to the file open window, which will dismiss your dialog. You may need to create a panel in the tool or properties panels for this to work. $\endgroup$ – sambler Dec 10 '13 at 23:54
  • $\begingroup$ There are certain limitations, e.g. popups can't redraw (so no dynamic layout like in panels), buttons work only once, and the modal file selector can't be invoked from certain context (e.g. from inside a running file select operator). $\endgroup$ – CoDEmanX Dec 11 '13 at 1:47
  • $\begingroup$ @CoDEmanX I'm stumped by this same issue as OP. How does a generic popup launched by context.window_manager.invoke_props_dialog qualify as a file select operator, so modal file selector can't be invoked from the button? $\endgroup$ – Tom Pace Feb 10 '14 at 16:58
  • $\begingroup$ Blender is designed to work non-modal as much as possible. Popups are used rarely, actually only if a certain operation is known to take a long time and thus is expansive to re-run using F6/Redo panel with new settings. The user is asked before the operation in such cases. I haven't looked at the C code, but I guess a popup is similar to a blocking modal op, and a file selector is the same. They can't both run at the same time. You should add file path properties to a panel, but it can't be in a modal file selector's sidebar. $\endgroup$ – CoDEmanX Feb 10 '14 at 17:27
7
$\begingroup$

You can use a CollectionProperty if you really need to let the user select multiple files (in different folders).

The following code invokes a file selector, that let's you add the current filepath as well as the selected files to a list. It allows for several input/output paths. It won't add duplicates, and you can also remove entries.

(The example operator is derives from the File Export template, but Add Files doesn't make much sense for an export operation - use it for imports instead!).

File Selector Multiple Paths

import bpy
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, IntProperty, CollectionProperty
from bpy.types import Operator
from os.path import dirname, join

class AddFile(Operator):
    bl_idname = "export_test.add_file"
    bl_label = "Add File"

    filepath = StringProperty()

    @classmethod
    def poll(cls, context):
        op = context.active_operator
        return op.filepath not in (f.name for f in op.file_list)

    def execute(self, context):
        item = context.active_operator.file_list.add()
        item.name = self.filepath
        return {'FINISHED'}

class AddFiles(Operator):
    bl_idname = "export_test.add_files"
    bl_label = "Add Files"

    filepaths = CollectionProperty(type=bpy.types.PropertyGroup)

    def execute(self, context):
        file_list = context.active_operator.file_list

        for fp in self.filepaths:
            if fp.name not in (f.name for f in file_list):
                item = file_list.add()
                item.name = fp.name
        return {'FINISHED'}

class RemoveFile(Operator):
    bl_idname = "export_test.remove_file"
    bl_label = "Remove File"

    index = IntProperty()

    @classmethod
    def poll(cls, context):
        return len(context.active_operator.file_list) > 0

    def execute(self, context):
        try:
            context.active_operator.file_list.remove(self.index)
        except IndexError:
            pass
        return {'FINISHED'}

class ExportSomeData(Operator, ExportHelper):
    """This appears in the tooltip of the operator and in the generated docs"""
    bl_idname = "export_test.some_data"
    bl_label = "Export Some Data"

    # ExportHelper mixin class uses this
    filename_ext = ".txt"

    filter_glob = StringProperty(
            default="*.txt",
            options={'HIDDEN'},
            )

    # special prop, blender fills it with all selected files if present
    files = CollectionProperty(type=bpy.types.PropertyGroup)

    file_list = CollectionProperty(type=bpy.types.PropertyGroup)
    file_list_index = IntProperty()

    def draw(self, context):
        layout = self.layout

        row = layout.row(True)
        row.label(bpy.path.basename(self.filepath))

        row.operator(AddFile.bl_idname, icon='ZOOMIN').filepath = self.filepath

        props = row.operator(AddFiles.bl_idname, icon='ZOOMIN')
        folder = dirname(self.filepath)
        for f in self.files:
            item = props.filepaths.add()
            item.name = join(folder, f.name)

        row.operator(RemoveFile.bl_idname, icon='ZOOMOUT', text="").index = self.file_list_index

        layout.template_list("UI_UL_list", "AddFiles", self, "file_list", self, "file_list_index")

    def execute(self, context):
        for f in self.file_list:
            print(f.name)
        self.report({'WARNING'}, "\n".join(f.name for f in self.file_list))
        return {'FINISHED'}

def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()

    # test call
    bpy.ops.export_test.some_data('INVOKE_DEFAULT')
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.