2

I would like to wrap my properties inside collapsible panels when using the ExportHelper or ImportHelper class but I can not figure it out how to draw them properly:

enter image description here

This is my code so far (reduced version of the Operator File Export template):

import bpy

ExportHelper is a helper class, defines filename and

invoke() function which calls the file selector.

from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator

class ExportSomeData(Operator, ExportHelper): """This appears in the tooltip of the operator and in the generated docs""" bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed bl_label = "Export Some Data"

# ExportHelper mixin class uses this
filename_ext = ".txt"

def execute(self, context):
    return {'FINISHED'}


class SubPanel(bpy.types.Panel): bl_label = 'SubPanel' bl_idname = 'Sub_PT_Layout' bl_parent_id = 'export_test.some_data' bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS'

def draw(self, context):
    layout = self.layout
    layout.label(text = 'test')

    use_setting: BoolProperty(
        name="Example Boolean",
        description="Example Tooltip",
        default=True,
    )


def register(): bpy.utils.register_class(ExportSomeData) bpy.utils.register_class(SubPanel)

def unregister(): bpy.utils.unregister_class(SubPanel) bpy.utils.unregister_class(ExportSomeData)

if name == "main": register()

# test call
bpy.ops.export_test.some_data('INVOKE_DEFAULT')

Unfortunately I get the following error:

RuntimeError: Error: Registering panel class: parent 'export_test.some_data' for 'Sub_PT_Layout' not found

Q: How can I add a panel to the properties area of the file browser when using the ExportHelper or ImportHelper class?

brockmann
  • 12,613
  • 4
  • 50
  • 93
Lala_Ghost
  • 457
  • 4
  • 17
  • As the error mentions your parent ID name is incorrect. You need to use the class name not the bl_idname also you are trying to parent a panel to an operator instead of another panel. I believe the panel you are looking for is bl_parent_id = 'FILE_PT_operator' however your test call will only include your defined operator not the full panel. – Ratt Mar 30 '21 at 07:16

1 Answers1

5

You would have to add a draw() method along with a pass statement to the operator in order to skip drawing and declare a Panel class, get the actual operator from that class in order to draw it's properties. Demo based on the Operator Export template that comes with Blender:

enter image description here

import bpy

ExportHelper is a helper class, defines filename and

invoke() function which calls the file selector.

from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator, Panel

class ExportSomeData(Operator, ExportHelper): """This appears in the tooltip of the operator and in the generated docs""" bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed bl_label = "Export Some Data"

# ExportHelper 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',
)

def execute(self, context):
    return {'FINISHED'}

def draw(self, context):
    pass


class CUSTOM_PT_export_settings(Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Settings Panel" bl_options = {'DEFAULT_CLOSED'}

@classmethod
def poll(cls, context):
    sfile = context.space_data
    operator = sfile.active_operator
    return operator.bl_idname == "EXPORT_TEST_OT_some_data"

def draw(self, context):
    layout = self.layout
    layout.use_property_split = True
    layout.use_property_decorate = False  # No animation.

    sfile = context.space_data
    operator = sfile.active_operator

    layout.prop(operator, 'type')
    layout.prop(operator, 'use_setting')


Only needed if you want to add into a dynamic menu

def menu_func_export(self, context): self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")

def register(): bpy.utils.register_class(ExportSomeData) bpy.utils.register_class(CUSTOM_PT_export_settings) bpy.types.TOPBAR_MT_file_export.append(menu_func_export)

def unregister(): bpy.utils.unregister_class(CUSTOM_PT_export_settings) bpy.utils.unregister_class(ExportSomeData) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)

if name == "main": register()

# test call
bpy.ops.export_test.some_data('INVOKE_DEFAULT')

Make sure to add a poll() method to the panel, testing for the actual bl_idname of the operator otherwise the panel will appear for all other export operators as well.

@classmethod
def poll(cls, context):
    sfile = context.space_data
    operator = sfile.active_operator
    return operator.bl_idname == "EXPORT_TEST_OT_some_data"

Note: Once the export operator is registered you can use the python console and call its idname() method getting the proper bl_idname:

>>> bpy.ops.export_test.some_data.idname()
'EXPORT_TEST_OT_some_data'

Just for the sake, adding sub panels is possible as well:

enter image description here

import bpy

ExportHelper is a helper class, defines filename and

invoke() function which calls the file selector.

from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator, Panel

class ExportSomeData(Operator, ExportHelper): """This appears in the tooltip of the operator and in the generated docs""" bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed bl_label = "Export Some Data" bl_options = {'PRESET', 'UNDO'}

# ExportHelper 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',
)

def execute(self, context):
    return {'FINISHED'}

def draw(self, context):
    self.layout.label(text="Hello World")
    #self.layout.prop(self, 'type')
    #pass


class EXAMPLE_PT_panel_1(Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Settings Panel 1" #bl_parent_id = "FILE_PT_operator" # Optional

@classmethod
def poll(cls, context):
    sfile = context.space_data
    operator = sfile.active_operator
    return operator.bl_idname == "EXPORT_TEST_OT_some_data"

def draw(self, context):
    layout = self.layout

    sfile = context.space_data
    operator = sfile.active_operator

    layout.label(text="This is the first Panel")
    layout.prop(operator, 'type')
    layout.prop(operator, 'use_setting')


class EXAMPLE_PT_panel_2(Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Settings Panel 2" bl_parent_id = "EXAMPLE_PT_panel_1" bl_options = {'DEFAULT_CLOSED'}

@classmethod
def poll(cls, context):
    sfile = context.space_data
    operator = sfile.active_operator
    return operator.bl_idname == "EXPORT_TEST_OT_some_data"

def draw(self, context):
    layout = self.layout

    sfile = context.space_data
    operator = sfile.active_operator

    layout.label(text="This is the second Panel")
    layout.prop(operator, 'type')


Only needed if you want to add into a dynamic menu

def menu_func_export(self, context): self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")

def register(): bpy.utils.register_class(ExportSomeData) bpy.utils.register_class(EXAMPLE_PT_panel_1) bpy.utils.register_class(EXAMPLE_PT_panel_2) bpy.types.TOPBAR_MT_file_export.append(menu_func_export)

def unregister(): bpy.utils.unregister_class(EXAMPLE_PT_panel_2) bpy.utils.unregister_class(EXAMPLE_PT_panel_1) bpy.utils.unregister_class(ExportSomeData) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)

if name == "main": register()

# test call
bpy.ops.export_test.some_data('INVOKE_DEFAULT')

Related: How do a create a foldout UI panel?


If you'd like to add a checkbox to one of the panel headers, declare a BoolProperty in your operator (alternatively use the scope of the window manager):

class ExportSomeData(Operator, ExportHelper):
    ...
    bake = BoolProperty(default=False)
    ...

And call Layout.prop() in the dedicated draw_header() method of the panel:

def draw_header(self, context):
    sfile = context.space_data
    operator = sfile.active_operator
self.layout.prop(operator, "bake", text="")

Related: How to add a checkbox to a panel header?

brockmann
  • 12,613
  • 4
  • 50
  • 93