Reputation: 1838
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.
Upvotes: 2
Views: 2015
Reputation: 512
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'}
Upvotes: 2
Reputation: 1838
It turns out there are several things you need to do to make this work, it's not yet covered in the documentation.
First of all, it helps to know what the base class 'ImportHelper' does when using it in an import script. On initialisation, the script calls 'context.window_manager.fileselect_add(self)' which adds several objects to your class. This string in the source code gave it away:
"The string properties 'filepath', 'filename', 'directory' and a 'files' " "collection are assigned when present in the operator"
The 'files' object is what we need but that's only present in if you also have the correct property present in your class, for example:
files: CollectionProperty(
type=bpy.types.OperatorFileListElement,
options={'HIDDEN', 'SKIP_SAVE'},
)
then, if that is present, you can access this in your class's 'execute' method to iterate through all the files in your selection:
for meshfile in self.files:
filepath = meshfile.name
print(filepath)
Also, remember to include the correct properties, I include these in my importer:
from bpy.props import (
BoolProperty,
CollectionProperty,
StringProperty,
)
Upvotes: 2