browse file in dialog menu

Scripting in Blender with Python, and working on the API

Moderators: jesterKing, stiv

Juan Tirador
Posts: 2
Joined: Mon Feb 11, 2013 10:28 pm

browse file in dialog menu

Postby Juan Tirador » Tue Feb 12, 2013 12:12 am

Hi folks!

I'm currently writing an import script and I have the following problem:
I have to select two files: one for the mesh and one for the skin.

My current solution (lame!) kind of looks like this:

Code: Select all

class MyImporter(bpy.types.Operator):
    bl_idname = "import.test"
    bl_label = "TEST"

    # the two files we want to select
    filepath = bpy.props.StringProperty(subtype='FILE_PATH')
    skinpath = bpy.props.StringProperty(subtype='FILE_PATH')
   
    def execute(self, context):
        # import...
        print("importing")
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


The problem is, that with "fileselect_add()" I only select "filepath", having a small field on the left, where I can enter the path for "skinpath". But if I klick the button for browsing that file (so I wouldn't have to enter it manually, which is not a solution!) it says "cannot activate file selector, one already open"

I thought the solution would be to use "invoke_props_dialog()" instead of "fileselect_add()". Then, when I invoke my import script, a dialog opens (as expected) where I can enter the filepath and the skinpath.
But the "browse file" buttons right next to the corresponding input fields just do nothing!
Do I have to bind to them another operator which uses "fileselect_add()" or maybe "bpy.ops.buttons.file_browse()"? And if so, how can I do that?

Having spent hours of searching through the net I didn't find any (useful) documentation on that matter. There is one related post:
Blender 2.6 Browse File.
But it didn't really tell me anything new... Except that I learned how to put a button into a panel, which opens a file browser (as explained there in the end). But I have no idea, how to return the selected filepath, so I can actually do something with it...

I would be very grateful for some hints (preferably on how to get those buttons working in the "invoke_props_dialog()") !

Thanks in advance!
Juan

CoDEmanX
Posts: 894
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Postby CoDEmanX » Tue Feb 12, 2013 1:29 pm

well, it really depends on how you use this... do the mesh and the skin file share the same name? If so, you only need to select the mesh and blender can pick up the skin file itself without user interaction.

If the have different names, you could use the files collection property of the fileselect operator to retrieve all selected files. You would basically allow the user to select as many files as he/she wants. Then you can get the first mesh and first skinfile from collection and import these.

here's a quick example:

Code: Select all

import bpy



# ImportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator

class ImportFilesCollection(bpy.types.PropertyGroup):
    name = StringProperty(
            name="File Path",
            description="Filepath used for importing the file",
            maxlen=1024,
            subtype='FILE_PATH',
            )
bpy.utils.register_class(ImportFilesCollection)


class ImportSomeData(Operator, ImportHelper):
    """This appears in the tooltip of the operator and in the generated docs"""
    bl_idname = "import_test.some_data"  # important since its how bpy.ops.import_test.some_data is constructed
    bl_label = "Import Some Data"

    # ImportHelper mixin class uses this
    filename_ext = ".obj"

    filter_glob = StringProperty(
            default="*.obj",
            options={'HIDDEN'},
            )
           
    files = bpy.props.CollectionProperty(type=ImportFilesCollection)

    def execute(self, context):
        print(len(self.files))
        for i, f in enumerate(self.files, 1):
            print("File %i: %s" % (i, f.name))
        return {'FINISHED'}


# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
    self.layout.operator(ImportSomeData.bl_idname, text="Text Import Operator")


def register():
    bpy.utils.register_class(ImportSomeData)
    bpy.types.INFO_MT_file_import.append(menu_func_import)


def unregister():
    bpy.utils.unregister_class(ImportSomeData)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.import_test.some_data('INVOKE_DEFAULT')
I'm sitting, waiting, wishing, building Blender in superstition...

Juan Tirador
Posts: 2
Joined: Mon Feb 11, 2013 10:28 pm

Postby Juan Tirador » Tue Feb 12, 2013 5:43 pm

Thanks for the reply!

Both of your suggestions work partially, but (as far as I see) they don't, if either mesh and skin have different names or lie in different directories (which both happens to be the case for me)-;

I'm not quite familiar with blender's internals but the solution that seems to me to be by far the easiest and most straight forward one, is just to select the two files separately in the props_dialog.

Do you have any idea, on how to get those buttons to work? Might it even be a bug, that nothing happens upon klicking them?

CoDEmanX
Posts: 894
Joined: Sun Apr 05, 2009 7:42 pm
Location: Germany

Postby CoDEmanX » Tue Feb 12, 2013 10:36 pm

in your sitution it's indeed way more complicated...

It might be more appropriate to add either two import operators or add an option to select the type. One would eventually use it like this:

1. Run import operator
2. Default type is set to "Mesh"
3. Select mesh and import
4. Run import operator again
5. Change type to "Skin"
6. Select skin and import

Changing the filter_glob while fileselector modal op is running is tricky, but possible whatsoever:

Code: Select all

import bpy
from os import path


# ImportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator

class ImportFilesCollection(bpy.types.PropertyGroup):
    name = StringProperty(
            name="File Path",
            description="Filepath used for importing the file",
            maxlen=1024,
            subtype='FILE_PATH',
            )
bpy.utils.register_class(ImportFilesCollection)


class ImportSomeData(Operator, ImportHelper):
    """This appears in the tooltip of the operator and in the generated docs"""
    bl_idname = "import_test.some_data"  # important since its how bpy.ops.import_test.some_data is constructed
    bl_label = "Import Some Data"

    # ImportHelper mixin class uses this
    filename_ext = ".blend"
   
    second_file = StringProperty()
   
    mode = EnumProperty(items=(('1','Mesh',''),('2','Skin','')))

    filter_glob = StringProperty(
            default="*.blend",
            options={'HIDDEN'},
            )
           
    files = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup)

    def execute(self, context):
        #print(len(self.files))
        for i, f in enumerate(self.files, 1):
            print("File %i: %s" % (i, f.name))
        print("Second file:", self.second_file)
        return {'FINISHED'}
   
    def check(self, context):
       
        change_ext = False
       
        if self.mode == '1':
            self.second_file = self.filepath
           
            if self.filter_glob != '*.blend':
                self.filter_glob = '*.blend'
                self.filename_ext = '.blend'
               
                context.space_data.params.filter_glob = '*.blend'
                bpy.ops.file.refresh()
                print("refresh")
               
                change_ext = True
           
        else:
            if self.filter_glob != '*.obj':
                self.filter_glob = '*.obj'
                self.filename_ext = '.obj'
           
                context.space_data.params.filter_glob = '*.obj'
                bpy.ops.file.refresh()
                print("refresh")
               
                change_ext = True
               
        return (change_ext or ImportHelper.check(self, context))

    def draw(self, context):
        layout = self.layout
       
        layout.prop(self, "mode", expand=True)
       
        layout.label(path.basename(self.filepath))
        layout.label(path.basename(self.second_file))
       
        layout.label(self.mode)

# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
    self.layout.operator(ImportSomeData.bl_idname, text="Text Import Operator")


def register():
    bpy.utils.register_class(ImportSomeData)
    bpy.types.INFO_MT_file_import.append(menu_func_import)


def unregister():
    bpy.utils.unregister_class(ImportSomeData)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)


if __name__ == "__main__":
    register()

    # test call
    #bpy.ops.import_test.some_data('INVOKE_DEFAULT')
    # Note: if you wanna this from Text editor, an additional check is required in check(), add:
    #if self == context.active_operator:
    # above the code, which accesses context.space_data
    # otherwise it will fail as the space_data refers to "Run script" area


btw:
in the example i posted, i've created a class derived from PropertyGroup, but in fact, it isn't needed at all. Blender creates a property "name" automatically, so you can simply do:

files = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup)
I'm sitting, waiting, wishing, building Blender in superstition...


Return to “Python”

Who is online

Users browsing this forum: No registered users and 2 guests