2
$\begingroup$

If I have a basic save/export operator like this:

class SaveData(bpy.types.Operator):
    bl_idname = "my.save"
    bl_label = "Save"
    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        do_save(ensure_ext(self.filepath, ".mytype"))
        return {"FINISHED"}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {"RUNNING_MODAL"}

What is the right/idiomatic way to prevent that that from saving a file named .mytype if the user just hits Save without supplying a filename? This bad behavior appears to be common behavior among many Blender plugins (whether they give a Python traceback or save a .ext file just depends on whether they happen to call ensure_ext() or something similar)

What I have tried:

  • The check method is not clearly documented, but it appears to be for checking parameters before invoke, not after.
  • The execute method could check for empty self.filepath but I've found zero examples of that. This would occur after the file selector has closed, so it would require popping another modal to warn the user that nothing happened.
  • The native Save/Save As for .blend files aggressively puts .blend into the filepath if you try to clear it, so I guess technically you can't save with no filename, but it will happily write a file named .blend. There also doesn't seem to be any way to turn on that behavior for other file selectors.
  • The FILE_OT_execute (which is the operator for the Save or Load button in the file selector) has a promising property named need_active which looks (from reading the C source) like it makes the button do nothing in case no file is selected. But I don't understand how I would set that property because that is used internally by fileselect_add(). This behavior would match other Windows programs I've tested.
$\endgroup$

1 Answer 1

3
$\begingroup$

ImportHelper and ExportHelper mixin classes.

If you look at either the import or export operator templates in the text editor will see they use the helper mixin classes ImportHelper and ExportHelper

Here is the code for export helper from bpy_extras.io_utils find it in the scripts/modules folder of your blender installation.

class ExportHelper:
    filepath: StringProperty(
        name="File Path",
        description="Filepath used for exporting the file",
        maxlen=1024,
        subtype='FILE_PATH',
    )
    check_existing: BoolProperty(
        name="Check Existing",
        description="Check and warn on overwriting existing files",
        default=True,
        options={'HIDDEN'},
    )

    # subclasses can override with decorator
    # True == use ext, False == no ext, None == do nothing.
    check_extension = True

    def invoke(self, context, _event):
        import os
        if not self.filepath:
            blend_filepath = context.blend_data.filepath
            if not blend_filepath:
                blend_filepath = "untitled"
            else:
                blend_filepath = os.path.splitext(blend_filepath)[0]

            self.filepath = blend_filepath + self.filename_ext

        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def check(self, _context):
        import os
        change_ext = False
        change_axis = _check_axis_conversion(self)

        check_extension = self.check_extension

        if check_extension is not None:
            filepath = self.filepath
            if os.path.basename(filepath):
                filepath = bpy.path.ensure_ext(
                    filepath,
                    self.filename_ext
                    if check_extension
                    else "",
                )

                if filepath != self.filepath:
                    self.filepath = filepath
                    change_ext = True

        return (change_ext or change_axis)

which as you can see sets the name to "untitled." if not set.

Check out the templates to see other properties that are used to extend this

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

filter_glob: StringProperty(
    default="*.txt",
    options={'HIDDEN'},
    maxlen=255, 

Also a files collection bpy/file browser: get selected file names where directory is used to designate the folder the files reside in.

Set custom location for import/export helper

$\endgroup$

You must log in to answer this question.

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