1
$\begingroup$

Could anyone help me make sense of the import file template that Blender 3.2 gives for Python scripting? It seems a lot of this code is unnecessary for the function of opening a dialog box and selecting images. Where would I start inputting my code to utilize the files I selected and what here is unnecessary noise?

import bpy


def read_some_data(context, filepath, use_some_setting):
    print("running read_some_data...")
    f = open(filepath, 'r', encoding='utf-8')
    data = f.read()
    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
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 = ".tif"

    filter_glob: StringProperty(
        default="*.tif",
        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',
    )

    def execute(self, context):
        return read_some_data(context, self.filepath, self.use_setting)


# 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')
```
$\endgroup$
1
  • $\begingroup$ Except for read_some_data, the BoolProperty and EnumProperty which are currently not really doing anything useful, the other parts are necessary to get an import dialog and a menu entry in the UI. Perhaps your question is answered by this post if you are looking to get all filenames? $\endgroup$ Commented Jun 28, 2022 at 19:35

1 Answer 1

2
$\begingroup$

The ImportHelper class is not well documented, so I wrote this example to help explain it:

#-----------------------------------------------------------------------------
# Here's what the ImportHelper class does as a mix-in:
# It adds a StringProperty named 'filepath'
# It adds an invoke function
# It adds a "check function.
#
# The invoke function calls context.window_manager.fileselect_add
# This class is the same as the above class, except it makes these things
# explicit
# https://docs.blender.org/api/current/bpy.types.WindowManager.html#bpy.types.WindowManager.fileselect_add
#
from bpy.types import Operator
from pathlib import Path

class TLA_OT_stlhandlerv2(Operator):
    """ Import an STL and do things with it"""
    bl_idname = "import.stlhandlerv2"
    bl_label = "Process STL"
    bl_options = {"REGISTER", "UNDO"}

    # This is usually obtained from the ImportHelper mixin
    # It is used by fileselect_add if it is present.
    filepath: StringProperty(
        name="File Path",
        description="Filepath used for importing the file",
        maxlen=1024,
        subtype='FILE_PATH',
    )

    # This is used by fileselect_add if it is present.
    # It is not well documented.
    filter_glob: StringProperty(
        default='*.stl',
        options={'HIDDEN'}
    )

    # This is used by fileselect_add if it is present.
    directory: StringProperty(
        options={'HIDDEN'}
    )

    # This is used by fileselect_add if it is present.
    files: CollectionProperty(type=bpy.types.PropertyGroup)

    @classmethod
    def poll(cls, context):
        return context.mode == "OBJECT"

    def execute(self, context):
        directory_path = Path(self.filepath).parent
        for file in self.files:
            bpy.ops.import_mesh.stl(filepath=str(directory_path / file.name))
            print(f'Do Your thing here')
        return {'FINISHED'}

    # This is usually obtained from the ImportHelper mixin
    def invoke(self, context, _event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

This example happens to use a dialog box to collect files that it then processes using the operator that imports an STL file. It implements this workflow:

When the handler is invoked, the fileselect_add function is called as a modal operator. As a side effect, it has access to filepath, filter_glob, and directory and uses them to set up the the dialog box before it calls it.

When fileselect_path completes, it sets those variables, in case they have changed, and it fills out the files variable with the names of all of the files that were marked as selected in the dialog.

Note: Because the variable names are hard wired in the helper functions you must use the same spelling in your operator class.

Once fileselect_path completes, your execute function is called. It can use the values to do whatever it wants. In my example, it invokes the stl importer.

The other bits in the template file are just a way of demonstrating how to add your operator to a menu to invoke it.

Addition

To make this answer clearer, here is the code for an operator and panel. The panel is added as the "TLA" tab in the Video Sequence Editor's side panel and contains a button labeled "Add Images". Pressing the button will bring up the file dialog, but with no filtering (I've removed the glob variable). It assumes that Channel 1 is empty and appends all of the files from the dialog to that image strip.

import bpy
from bpy.types import Operator
from bpy.types import Panel
from bpy.props import StringProperty
from bpy.props import CollectionProperty
from bpy_extras.io_utils import ImportHelper
from pathlib import Path

class TLA_OT_addimages(Operator, ImportHelper):
    """ add images to an image strip in the VSE"""
    bl_idname = "vse.addimages"
    bl_label = "Add Images"
    bl_options = {"REGISTER", "UNDO"}
    directory: StringProperty(
        options={'HIDDEN'}
    )
    # This is used by fileselect_add if it is present.
    files: CollectionProperty(type=bpy.types.PropertyGroup)
    @classmethod
    def poll(cls, context):
        return True
    def execute(self, context):
        self.report({'INFO'},
            f"execute()")
        directory_path = Path(self.filepath).parent
        file_list = []
        for file in self.files:
            file_list.append({"name":file.name})
        bpy.ops.sequencer.image_strip_add(
            directory=str(directory_path),
            files=file_list,
            relative_path=True,
            channel=1,
            fit_method='FIT'
        )
        return {'FINISHED'}

class TLA_PT_sidebar(Panel):
    """Display test button"""
    bl_label = "Add images"
    bl_space_type = "SEQUENCE_EDITOR"
    bl_region_type = "UI"
    bl_category = "TLA"
    def draw(self, context):
        col = self.layout.column(align=True)
        prop = col.operator("vse.addimages")

 
classes = [
    TLA_OT_addimages,
    TLA_PT_sidebar,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)


def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)

because the operator is invoked from the UI of the sequence editor it does not need a context overrride.

$\endgroup$
7
  • $\begingroup$ the name of my class must contain OT correct? $\endgroup$
    – se2000
    Commented Jun 29, 2022 at 18:11
  • $\begingroup$ Why is ImportHelper not a parameter for your class? What benefit is there for the template to include it/does it do anything different whether or not its a parameter? $\endgroup$
    – se2000
    Commented Jun 29, 2022 at 18:38
  • $\begingroup$ Yes, the name must be PREFIX_OT_SUFFIX. ImportHelper is not a parameter (parent class) in my class only because I wanted to document explicitly what ImportHelper does implicitly. The benefit for including it is that it includes the invoke function, so you don't have to write that yourself. $\endgroup$ Commented Jun 29, 2022 at 19:06
  • $\begingroup$ If my understanding is correct (which it may not be) I just need to run bpy.ops.workspace.append_activate(idname="Video Editing") bpy.context.area.type = 'SEQUENCE_EDITOR' to invoke in the context of the VSE? Or is there a better way to go about it? For the dictionary of self.files what is the benefit of using a dictionary rather than iterating through a list? What would I make the keys and value? Sorry for all the questions I am very new to Blender scripting $\endgroup$
    – se2000
    Commented Jun 29, 2022 at 20:09
  • 1
    $\begingroup$ @se2000 I've done some further checking. The argument is supposed to be a list of OperatorFileListElement I've tried not duplicating the entry and the call seems to work just fine. I now don't think it needs to be double and there's a bug in the add code for the VSE. $\endgroup$ Commented Jul 1, 2022 at 0:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .