Search code examples
pythonpluginsimportblendermultipleselection

Importing multiple files in blender import plugin


I'm writing an import plugin for blender 2.8x and I'd like to make use of the multiple file selection feature. Unfortunately, I can't find any provision for this in 'ImportHelper' (the class I derive from) and web searches haven't yielded anything that seems to work and I can't seem to find anything in the documentation either.


Solution

  • As explained in Luther's answer, you need to add a files : CollectionProperty attribute to your class to enable multifile support.

    However, in addition you also need a directory: StringProperty attribute. Then you need to build the full filepath using os.path.join.

    See below for the code from a complete working import addon that supports multifile import. It is a modification of the "Operator File Import" template (operator_file_import.py). The code was tested on Blender 3.4.1.

    operator_file_import.multiple.py :

    import os
    import bpy
    
    def read_some_data(context, filepath, use_some_setting):
        print("Importing:", filepath)
        f = open(filepath, 'r', encoding='utf-8')
        try:
          data = f.read() # Fails on some latin-1 encoded text files, but not relevant to this importer example.
        except:
          pass
        f.close()
    
        # would normally load the data here
        #print(data)
    
        return {'FINISHED'}
    
    
    # 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, CollectionProperty
    from bpy.types import Operator
    
    
    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 = ".txt"
    
        filter_glob: StringProperty(
            default="*.txt",
            options={'HIDDEN'},
            maxlen=255,  # Max internal buffer length, longer would be clamped.
        )
    
        # List of operator properties, the attributes will be assigned
        # to the class instance from the operator settings before calling.
        use_setting: BoolProperty(
            name="Example Boolean",
            description="Example Tooltip",
            default=True,
        )
    
        type: EnumProperty(
            name="Example Enum",
            description="Choose between two items",
            items=(
                ('OPT_A', "First Option", "Description one"),
                ('OPT_B', "Second Option", "Description two"),
            ),
            default='OPT_A',
        )
    
        ###########################################
        # necessary to support multi-file import
        files: CollectionProperty(
            type=bpy.types.OperatorFileListElement,
            options={'HIDDEN', 'SKIP_SAVE'},
        )
    
        directory: StringProperty(
            subtype='DIR_PATH',
        )
        ###########################################
    
        def execute(self, context):
            for current_file in self.files:
                filepath = os.path.join(self.directory, current_file.name)
                read_some_data(context, filepath, self.use_setting)
            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")
    
    
    # Register and add to the "file selector" menu (required to use F3 search "Text Import Operator" for quick access).
    def register():
        bpy.utils.register_class(ImportSomeData)
        bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
    
    
    def unregister():
        bpy.utils.unregister_class(ImportSomeData)
        bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
    
    
    if __name__ == "__main__":
        register()
    
        # test call
        bpy.ops.import_test.some_data('INVOKE_DEFAULT')
    

    These are the relevant changes to the basic import template:

    1. Import os to use os.path.join:

      import os
      
    2. Import CollectionProperty:

      from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty
      
    3. Add the files and directory properties:

      ###########################################
      # necessary to support multi-file import
      files: CollectionProperty(
          type=bpy.types.OperatorFileListElement,
          options={'HIDDEN', 'SKIP_SAVE'},
      )
      
      directory: StringProperty(
          subtype='DIR_PATH',
      )
      ###########################################
      
    4. Loop through self.files and build the full paths using os.path.join:

      def execute(self, context):
          for current_file in self.files:
              filepath = os.path.join(self.directory, current_file.name)
              read_some_data(context, filepath, self.use_setting)
          return {'FINISHED'}