7
$\begingroup$

I want my export operator to open the normal file selector but instead of asking for a specific file I want it to ask for a directory. The reason for this is that both my model and the textures should be stored in a directory tree and I want to user to select the root. For example the model file "somemodel.ext" links to the textures "tex1.png" and "tex2.png" the resulting file structure should look like this:

RootFolder
+--textures
   +--somemodel
      + tex1.png
      + tex2.png
+--models
   + somemodel.ext

and I want to user to select the RootFolder in the export fileselect.

$\endgroup$
3
  • $\begingroup$ Using savePath = bpy.path.abspath('//' + folderName + '/') is working well for me. The textures I'm baking are going into the specified folder which is in the same directory as the .blend. I'll post a more complete code example if no one with more experience shows up with a better solution by tomorrow. $\endgroup$ Aug 5, 2014 at 22:06
  • $\begingroup$ I know how to get a folder path but I want the filepicker (the thing that opens when you do Save As... for example) to notice that. And I surely don't want to export the files to a fixed location. But don't mind to post your code, anyway. If you want to discuss this come into the chat chat.stackexchange.com/rooms/8888/the-renderfarm $\endgroup$ Aug 5, 2014 at 22:12
  • $\begingroup$ there are a couple Cycles bake Addons that have file selector dialogues. You could dig up one of those for reference. $\endgroup$ Aug 6, 2014 at 8:58

2 Answers 2

8
$\begingroup$

The most compact solution that I found uses the method WindowsManager.fileselect_add() in invoke(), and doesn't use the Helpers. The documentation of fileselect_add says:

Opens a file selector with an operator. The string properties ‘filepath’, ‘filename’, ‘directory’ and a ‘files’ collection are assigned when present in the operator

Actually, you need to specify only a directory StringProperty in your operator. In so doing, the possibility to select the name of the file will disappear.

Also, to show only folders by default you can add a property filter_folder = bpy.props.BoolProperty(default=True, options={'HIDDEN'}).

Since v2.80, declaring string/bool properties requires using the colon : sign instead of equal =.

Full example, tested on Blender 3.6.0:

import bpy
from bpy.props import StringProperty, BoolProperty
from bpy.types import Operator

class SelectDirExample(Operator):

    """Create render for all chracters"""
    bl_idname = "example.select_dir"
    bl_label = "Dir Selection Example Operator"
    bl_options = {'REGISTER'}

    # Define this to tell 'fileselect_add' that we want a directoy
    directory: StringProperty(
        name="Outdir Path",
        description="Where I will save my stuff"
        # subtype='DIR_PATH' is not needed to specify the selection mode.
        # But this will be anyway a directory path.
        )

    # Filters folders
    filter_folder: BoolProperty(
        default=True,
        options={"HIDDEN"}
        )

    def execute(self, context):

        print("Selected dir: '" + self.directory + "'")

        return {'FINISHED'}

    def invoke(self, context, event):
        # Open browser, take reference to 'self' read the path to selected
        # file, put path in predetermined self fields.
        # See: https://docs.blender.org/api/current/bpy.types.WindowManager.html#bpy.types.WindowManager.fileselect_add
        context.window_manager.fileselect_add(self)
        # Tells Blender to hang on for the slow user input
        return {'RUNNING_MODAL'}


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


def unregister():
    bpy.utils.unregister_class(SelectDirExample)


#
# Invoke register if started from editor
if __name__ == "__main__":
    register()
    
    # OPTIONAL - test run
    bpy.ops.example.select_dir('INVOKE_DEFAULT')

Full example, tested on Blender 2.79:

import bpy
from bpy.props import StringProperty, BoolProperty
from bpy.types import Operator

class SelectDirExample(bpy.types.Operator):

    """Create render for all chracters"""
    bl_idname = "example.select_dir"
    bl_label = "Dir Selection Example Operator"
    bl_options = {'REGISTER'}

    # Define this to tell 'fileselect_add' that we want a directoy
    directory = StringProperty(
        name="Outdir Path",
        description="Where I will save my stuff"
        # subtype='DIR_PATH' is not needed to specify the selection mode.
        # But this will be anyway a directory path.
        )

    # Filters folders
    filter_folder = BoolProperty(
        default=True,
        options={"HIDDEN"}
        )

    def execute(self, context):

        print("Selected dir: '" + self.directory + "'")

        return {'FINISHED'}

    def invoke(self, context, event):
        # Open browser, take reference to 'self' read the path to selected
        # file, put path in predetermined self fields.
        # See: https://docs.blender.org/api/current/bpy.types.WindowManager.html#bpy.types.WindowManager.fileselect_add
        context.window_manager.fileselect_add(self)
        # Tells Blender to hang on for the slow user input
        return {'RUNNING_MODAL'}


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


def unregister():
    bpy.utils.unregister_class(SelectDirExample)


#
# Invoke register if started from editor
if __name__ == "__main__":
    register()
    
    # OPTIONAL - test run
    bpy.ops.example.select_dir('INVOKE_DEFAULT')

You can test the manual file selection by hitting SPACE on a 3D view and searching for Dir Selection Example Operator.

Differently, when executed from Python code you can specify the directory on your own: bpy.ops.demo.select_dir_example(directory='/tmp')

$\endgroup$
4
  • 1
    $\begingroup$ I consider this the better answer now. Btw to show only folders by default you can add a property filter_folder = bpy.props.BoolProperty(default=True, options={'HIDDEN'}). For further reference on options you can set via properties consult the source code. $\endgroup$ Dec 19, 2018 at 19:52
  • 1
    $\begingroup$ This doesn't appear to work in 2.82 $\endgroup$
    – Lucas
    Jul 30, 2021 at 0:57
  • 1
    $\begingroup$ In Blender 2.8 and newer use the annotation syntax to define properties. Replace the equal sign (=) with a colon (:) like: directory: bpy.props.StringProperty(name="Directory", options={"HIDDEN"}) resp. filter_folder: bpy.props.BoolProperty(default=True, options={"HIDDEN"}) $\endgroup$
    – Sighthound
    Apr 25, 2022 at 8:39
  • 1
    $\begingroup$ Edited the answer to include the above comments 👍 $\endgroup$
    – L0Lock
    Aug 3, 2023 at 16:27
7
$\begingroup$

I presume the main question here is selecting a root directory with the FileSelector.

bpy_extras.io_utils.ExportHelper includes FileSelectParams, which when set give the desired functionality.

The first attribute to be set is: use_filter_folder = True This will filter anything that is not a folder to the user. This also has the added advantage that the path your script gets back is folder.

You also need to set a file extension type, even though it is not used. It will cause an error on execution. Possible bug? filename_ext = "."

This is based on an for general file import - How do I get the filepath when I import a file?

Full sample

import bpy
from bpy_extras.io_utils import ExportHelper
    
import os.path

class ExportOperator(bpy.types.Operator, ExportHelper):
    """Operator for setting a directory path."""

    #: Name of function for calling the nif export operator.
    bl_idname = "export_scene.folder"

    #: How the nif import operator is labelled in the user interface.
    bl_label = "Export to folder"

    filename_ext = "."
    use_filter_folder = True

    def execute(self, context):
        userpath = self.properties.filepath
        if not os.path.isdir(userpath):
            msg = "Please select a directory not a file\n" + userpath
            self.report({'WARNING'}, msg)

        #Insert the desired logic here to write to the directory.

        return{'FINISHED'}

def menu_func_import(self, context):
    self.layout.operator(
        ExportOperator.bl_idname, text="Export Scene to folder")

def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_file_import.append(menu_func_import)

def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)

if __name__ == "__main__":
    register()
$\endgroup$
4
  • $\begingroup$ thanks for pointing the possible bug out. Though appreciating the effort, this script doesn't LIMIT the user to only select directories. I could of course just ignore the filename the user gives it (something like "model1") but then I would have to deal with all those asking what the filename is, etc. $\endgroup$ Aug 6, 2014 at 17:31
  • $\begingroup$ If the user does decide to enter in a path that is not a directory, then just sanitise with os.path.isdir(file_path). You can only be so defensive. $\endgroup$
    – neomonkeus
    Aug 6, 2014 at 18:20
  • $\begingroup$ You can actually, instead of using ExportHelper, use your own filepath attribute defined as filepath = StringProperty(type='DIR_PATH'). $\endgroup$ Aug 9, 2015 at 2:28
  • $\begingroup$ I think it should be StringProperty(subtype='DIR_PATH') $\endgroup$
    – Noam Peled
    Dec 12, 2017 at 20:18

You must log in to answer this question.

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