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.
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:
Import os
to use os.path.join
:
import os
Import CollectionProperty
:
from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty
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',
)
###########################################
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'}