Error when Calling bpy.ops.import_scene.fbx

Hello!

I created a class to extend the functionality of the .fbx file importer provided by Blender.

@orientation_helper(axis_forward='-Z', axis_up='Y')
class ImportFBX(Operator, ImportHelper):
    """Load a FBX file"""
    bl_idname = "import_scene.test_fbx"
    bl_label = "Import FBX"
    bl_options = {'UNDO', 'PRESET'}

    directory: StringProperty()

    filename_ext = ".fbx"
    filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})

    files: CollectionProperty(
        name="File Path",
        type=bpy.types.OperatorFileListElement,
    )

    ui_tab: EnumProperty(
        items=(('MAIN', "Main", "Main basic settings"),
               ('ARMATURE', "Armatures", "Armature-related settings"),
               ),
        name="ui_tab",
        description="Import options categories",
    )

    use_manual_orientation: BoolProperty(
        name="Manual Orientation",
        description="Specify orientation and scale, instead of using embedded data in FBX file",
        default=False,
    )
    global_scale: FloatProperty(
        name="Scale",
        min=0.001, max=1000.0,
        default=1.0,
    )
    bake_space_transform: BoolProperty(
        name="Apply Transform",
        description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
        "target space is not aligned with Blender's space "
        "(WARNING! experimental option, use at own risk, known to be broken with armatures/animations)",
        default=False,
    )

    use_custom_normals: BoolProperty(
        name="Custom Normals",
        description="Import custom normals, if available (otherwise Blender will recompute them)",
        default=True,
    )
    colors_type: EnumProperty(
        name="Vertex Colors",
        items=(('NONE', "None", "Do not import color attributes"),
               ('SRGB', "sRGB", "Expect file colors in sRGB color space"),
               ('LINEAR', "Linear", "Expect file colors in linear color space"),
               ),
        description="Import vertex color attributes",
        default='SRGB',
    )

    use_image_search: BoolProperty(
        name="Image Search",
        description="Search subdirs for any associated images (WARNING: may be slow)",
        default=True,
    )

    use_alpha_decals: BoolProperty(
        name="Alpha Decals",
        description="Treat materials with alpha as decals (no shadow casting)",
        default=False,
    )
    decal_offset: FloatProperty(
        name="Decal Offset",
        description="Displace geometry of alpha meshes",
        min=0.0, max=1.0,
        default=0.0,
    )

    use_anim: BoolProperty(
        name="Import Animation",
        description="Import FBX animation",
        default=True,
    )
    anim_offset: FloatProperty(
        name="Animation Offset",
        description="Offset to apply to animation during import, in frames",
        default=1.0,
    )

    use_subsurf: BoolProperty(
        name="Subdivision Data",
        description="Import FBX subdivision information as subdivision surface modifiers",
        default=False,
    )

    use_custom_props: BoolProperty(
        name="Custom Properties",
        description="Import user properties as custom properties",
        default=True,
    )
    use_custom_props_enum_as_string: BoolProperty(
        name="Import Enums As Strings",
        description="Store enumeration values as strings",
        default=True,
    )

    ignore_leaf_bones: BoolProperty(
        name="Ignore Leaf Bones",
        description="Ignore the last bone at the end of each chain (used to mark the length of the previous bone)",
        default=False,
    )
    force_connect_children: BoolProperty(
        name="Force Connect Children",
        description="Force connection of children bones to their parent, even if their computed head/tail "
        "positions do not match (can be useful with pure-joints-type armatures)",
        default=False,
    )
    automatic_bone_orientation: BoolProperty(
        name="Automatic Bone Orientation",
        description="Try to align the major bone axis with the bone children",
        default=False,
    )
    primary_bone_axis: EnumProperty(
        name="Primary Bone Axis",
        items=(('X', "X Axis", ""),
               ('Y', "Y Axis", ""),
               ('Z', "Z Axis", ""),
               ('-X', "-X Axis", ""),
               ('-Y', "-Y Axis", ""),
               ('-Z', "-Z Axis", ""),
               ),
        default='Y',
    )
    secondary_bone_axis: EnumProperty(
        name="Secondary Bone Axis",
        items=(('X', "X Axis", ""),
               ('Y', "Y Axis", ""),
               ('Z', "Z Axis", ""),
               ('-X', "-X Axis", ""),
               ('-Y', "-Y Axis", ""),
               ('-Z', "-Z Axis", ""),
               ),
        default='X',
    )

    use_prepost_rot: BoolProperty(
        name="Use Pre/Post Rotation",
        description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)",
        default=True,
    )

    def draw(self, context):
        pass

    def execute(self, context):
        keywords = self.as_keywords()
        bpy.ops.import_scene.fbx(**keywords)
        return {'FINISHED'}

I removed all the extra stuff from my code to highlight the problem I am experiencing: an error is thrown when calling bpy.ops.import_scene.fbx.

TypeError: Converting py args to operator properties:  IMPORT_SCENE_OT_fbx.files expected a each sequence member to be a dict for an RNA collection, not OperatorFileListElement

This error doesn’t make any sense to me. If you look at the documentation for bpy.ops.import_scene.fbx, you see this point about the files parameter:

  • files (bpy_prop_collection of OperatorFileListElement, (optional)) – File Path

OperatorFileListElement seems to be the correct type, so why am I getting this error?

And advice or help would be most appreciated. Thank you.

It looks like you’re passing in the generic type of OperatorFileListElement, rather than a collection of specific filepath data.

For the type, I believe a CollectionProperty is looking for a PropertyGroup class you have created.

I think this example might be helpful.

I don’t really get what you are describing. I get what you mean when you say that I am passing the the generic type of OperatorFileListElement and I should be passing a CollectionProperty. But I don’t understand how I am suppose to do this.
I am assuming you mean the files property when you refer to the CollectionProperty I should be passing. What should I do with self.files to do this?
Thank you for the help.

I’m adapting this from the StackExchange answer to fit your case, but I think what you want is something like:

class FileList(bpy.types.PropertyGroup):
    path: bpy.props.StringProperty


@orientation_helper(axis_forward='-Z', axis_up='Y')
class ImportFBX(Operator, ImportHelper):
    """Load a FBX file"""
    bl_idname = "import_scene.test_fbx"
    bl_label = "Import FBX"
    bl_options = {'UNDO', 'PRESET'}

    collection = bpy.props.CollectionProperty(type=FileList)
    # or possibly: collection = bpy.context.scene.collection?

    files: CollectionProperty(
        name="File Path",
        type=collection,
    )
...


def register():
    bpy.utils.register_class(FileList)

    bpy.types.Scene.collection = bpy.props.CollectionProperty(type=FileList)

    item = bpy.context.scene.collection.add()
    item.path = '~/Documents/FBX'
    item = bpy.context.scene.collection.add()
    item.path = '~/Downloads/3D_Models'

    bpy.utils.register_class(ImportFBX)


if __name__ == "__main__":
    register()

First, create a PropertyGroup class to hold your data, in this case it will be strings, representing filepaths, so we add a StringProperty that will hold the path

You register it, and assign a scene property so that you can access it later, you can also add whatever data you need here. (or in another function, however you need to fill your filepath data)

Then, in your operator, you reference it and pass it to the property.

I haven’t actually run this code, I should write out a minimal example just to test it, but the snippet I adapted came from Campbell, one of the main bpy developers, so it should be good.

I wrote a minimal standalone script that I think will be easier to understand, and I tested it to make sure that it works:

import bpy

class FileList(bpy.types.PropertyGroup):
    name = bpy.props.StringProperty()

class TOOL_PT_ExamplePanel(bpy.types.Panel):
    bl_label = "ExamplePanel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'

    def draw(self, context):
        layout = self.layout
        op = layout.operator("object.example_op", text="Test")
        for file in context.scene.filelist:
            row = layout.row()
            row.label(text=file.name)


class TOOL_OT_ExampleOperator(bpy.types.Operator): 
    bl_idname = "object.example_op"
    bl_label = "Example Operator"
    bl_options = {'REGISTER'}   

    filelist = bpy.props.CollectionProperty(type=FileList)

    def execute(self, context):
        for file in context.scene.filelist:
            print(file.name)
        return {'FINISHED'}

def register():
    bpy.utils.register_class(FileList)
    
    bpy.types.Scene.filelist = bpy.props.CollectionProperty(type=FileList)

    it = bpy.context.scene.filelist.add()
    it.name = "/name/to/FBX_1"
    it = bpy.context.scene.filelist.add()
    it.name = "/name/to/FBX_2"   

    bpy.utils.register_class(TOOL_OT_ExampleOperator)
    bpy.utils.register_class(TOOL_PT_ExamplePanel)


if __name__ == "__main__":
    register()

It seems to be pretty finicky:

  • when I tried the initial example it failed because it didn’t want a collection to be named ‘collection’
  • then I got a few errors that said ‘See previous error’ - even though they were the first errors of the session
  • then I tried to add separate name and path properties and that caused it to fail
  • then I tried to access self.filelist in the Operator execute function, which also caused it to fail
  • then I tried to make self.filelist = bpy.context.scene.filelist, but it didn’t like that either

And, that takes us to the script you see above, registering a name property, referencing the class inside the Operator, but referencing the context.scene property inside of draw and execute functions.

It should be easier to get a handle on how they want you to set up and pass data with this script, which will hopefully make it easier to implement in yours.