Skip to main content
added 361 characters in body
Source Link
import bpy
from bpy.types import Operator, PropertyGroup
from bpy_extras.io_utils import ExportHelper #, ImportHelper
import os

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder")
    
    def draw_folder_selection(self, layout, property):
        row = layout.row(align=True)
        row.prop(self, property)
        ops = row.operator('select.directory_call_operator', text="", icon='FILEBROWSER')
        ops.operator_call = 'object.select_dir_example'
        # StringProperties need extra "" and \\ need to be doubled with my_string.replace("\\","\\\\")
        # E.g. my_string : StringProperty
        # f'my_string="{self.my_string}"'
        ops.operator_properties = f'input1={self.input1}, input2={self.input2}'
        ops.operator_folder_property_name = "folder"
        ops.old_filepathold_directory = self.folder

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

        layout.prop(self, 'input1')
        layout.prop(self, 'input2')
        self.draw_folder_selection(layout, 'folder')
        
    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        return {'FINISHED'}

# Use 'ImportHelper' when something is imported from this directory
class Select_OT_directory_call_operator(Operator, ExportHelper):
    bl_idname = "select.directory_call_operator"
    bl_label = "Select Directory"
    bl_description = " "
    bl_options = {'REGISTER', 'INTERNAL'}

    filename_ext = ""
    filename = ""
    filepath = ""
    filter_glob: bpy.props.StringProperty(
        default="",
        options={'HIDDEN'},
        maxlen=255,  # Max internal buffer length, longer would be clamped.
    )
    use_filter_folder = True
    
    filepathdirectory: bpy.props.StringProperty(name="Folder Path", maxlen=0maxlen=1024, default=" "default="")

    operator_call : bpy.props.StringProperty(options={'HIDDEN'}, description="operator to call")
    operator_properties : bpy.props.StringProperty(options={'HIDDEN'}, description="operator properties diffrent from folder")
    operator_folder_property_name : bpy.props.StringProperty(options={'HIDDEN'}, description="folder property of the operator to apply the new path to")
    old_filepathold_directory : bpy.props.StringProperty(options={'HIDDEN'}, description="old filepath of the operator used for cancel")
    
    def call_operator(self, filepath: str):
        filepath = filepath.replace("\\","\\\\") # needed because a str is placed into a str
        exec(f'bpy.ops.{self.operator_call}("INVOKE_DEFAULT", {self.operator_properties}, {self.operator_folder_property_name}="{filepath}")')
    
    def invoke(self, context, event):
        super().invoke(context, event)
        self.filepath = ""
        return {'RUNNING_MODAL'}
    
    def execute(self, context):
        user_path = self.properties.filepathdirectory
        if (not os.path.isdir(user_path)):
            msg = f"Please select a directory not a file\n{user_path}"
            self.report({'ERROR'}, msg)
            self.cancel(context)
            return {'CANCELLED'}
        self.call_operator(user_path)
        return {'FINISHED'}
    
    def cancel(self, context):
        self.call_operator(self.old_filepathold_directory)

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

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

if __name__ == "__main__":
    register()

EDIT 2 add invoke to delete filename on startup and use directory instead of filepath because this will only return the selected directory even if a file is selected

import bpy
from bpy.types import Operator, PropertyGroup
from bpy_extras.io_utils import ExportHelper #, ImportHelper
import os

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder")
    
    def draw_folder_selection(self, layout, property):
        row = layout.row(align=True)
        row.prop(self, property)
        ops = row.operator('select.directory_call_operator', text="", icon='FILEBROWSER')
        ops.operator_call = 'object.select_dir_example'
        # StringProperties need extra "" and \\ need to be doubled with my_string.replace("\\","\\\\")
        # E.g. my_string : StringProperty
        # f'my_string="{self.my_string}"'
        ops.operator_properties = f'input1={self.input1}, input2={self.input2}'
        ops.operator_folder_property_name = "folder"
        ops.old_filepath = self.folder

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

        layout.prop(self, 'input1')
        layout.prop(self, 'input2')
        self.draw_folder_selection(layout, 'folder')
        
    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        return {'FINISHED'}

# Use 'ImportHelper' when something is imported from this directory
class Select_OT_directory_call_operator(Operator, ExportHelper):
    bl_idname = "select.directory_call_operator"
    bl_label = "Select Directory"
    bl_description = " "
    bl_options = {'REGISTER', 'INTERNAL'}

    filename_ext = ""
    filter_glob: bpy.props.StringProperty(
        default="",
        options={'HIDDEN'},
        maxlen=255,  # Max internal buffer length, longer would be clamped.
    )
    use_filter_folder = True
    
    filepath: bpy.props.StringProperty(name="Folder Path", maxlen=0, default=" ")

    operator_call : bpy.props.StringProperty(options={'HIDDEN'}, description="operator to call")
    operator_properties : bpy.props.StringProperty(options={'HIDDEN'}, description="operator properties diffrent from folder")
    operator_folder_property_name : bpy.props.StringProperty(options={'HIDDEN'}, description="folder property of the operator to apply the new path to")
    old_filepath : bpy.props.StringProperty(options={'HIDDEN'}, description="old filepath of the operator used for cancel")
    
    def call_operator(self, filepath: str):
        filepath = filepath.replace("\\","\\\\") # needed because a str is placed into a str
        exec(f'bpy.ops.{self.operator_call}("INVOKE_DEFAULT", {self.operator_properties}, {self.operator_folder_property_name}="{filepath}")')

    def execute(self, context):
        user_path = self.properties.filepath
        if (not os.path.isdir(user_path)):
            msg = f"Please select a directory not a file\n{user_path}"
            self.report({'ERROR'}, msg)
            self.cancel(context)
            return {'CANCELLED'}
        self.call_operator(user_path)
        return {'FINISHED'}
    
    def cancel(self, context):
        self.call_operator(self.old_filepath)

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

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

if __name__ == "__main__":
    register()
import bpy
from bpy.types import Operator, PropertyGroup
from bpy_extras.io_utils import ExportHelper #, ImportHelper
import os

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder")
    
    def draw_folder_selection(self, layout, property):
        row = layout.row(align=True)
        row.prop(self, property)
        ops = row.operator('select.directory_call_operator', text="", icon='FILEBROWSER')
        ops.operator_call = 'object.select_dir_example'
        # StringProperties need extra "" and \\ need to be doubled with my_string.replace("\\","\\\\")
        # E.g. my_string : StringProperty
        # f'my_string="{self.my_string}"'
        ops.operator_properties = f'input1={self.input1}, input2={self.input2}'
        ops.operator_folder_property_name = "folder"
        ops.old_directory = self.folder

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

        layout.prop(self, 'input1')
        layout.prop(self, 'input2')
        self.draw_folder_selection(layout, 'folder')
        
    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        return {'FINISHED'}

# Use 'ImportHelper' when something is imported from this directory
class Select_OT_directory_call_operator(Operator, ExportHelper):
    bl_idname = "select.directory_call_operator"
    bl_label = "Select Directory"
    bl_description = " "
    bl_options = {'INTERNAL'}

    filename_ext = ""
    filename = ""
    filepath = ""
    filter_glob: bpy.props.StringProperty(
        default="",
        options={'HIDDEN'},
        maxlen=255,  # Max internal buffer length, longer would be clamped.
    )
    use_filter_folder = True
    
    directory: bpy.props.StringProperty(name="Folder Path", maxlen=1024, default="")

    operator_call : bpy.props.StringProperty(options={'HIDDEN'}, description="operator to call")
    operator_properties : bpy.props.StringProperty(options={'HIDDEN'}, description="operator properties diffrent from folder")
    operator_folder_property_name : bpy.props.StringProperty(options={'HIDDEN'}, description="folder property of the operator to apply the new path to")
    old_directory : bpy.props.StringProperty(options={'HIDDEN'}, description="old filepath of the operator used for cancel")
    
    def call_operator(self, filepath: str):
        filepath = filepath.replace("\\","\\\\") # needed because a str is placed into a str
        exec(f'bpy.ops.{self.operator_call}("INVOKE_DEFAULT", {self.operator_properties}, {self.operator_folder_property_name}="{filepath}")')
    
    def invoke(self, context, event):
        super().invoke(context, event)
        self.filepath = ""
        return {'RUNNING_MODAL'}
    
    def execute(self, context):
        user_path = self.properties.directory
        if (not os.path.isdir(user_path)):
            msg = f"Please select a directory not a file\n{user_path}"
            self.report({'ERROR'}, msg)
            self.cancel(context)
            return {'CANCELLED'}
        self.call_operator(user_path)
        return {'FINISHED'}
    
    def cancel(self, context):
        self.call_operator(self.old_directory)

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

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

if __name__ == "__main__":
    register()

EDIT 2 add invoke to delete filename on startup and use directory instead of filepath because this will only return the selected directory even if a file is selected

Added second Solution
Source Link

Solution 2 is preferred

Solution
I don't think an easy solution like this is possible because you are using a dialog which is a floating window and the folder selection will overwrite the current floating window. Also take a look at the info area/info-log of Blender, the Operator isn't executed/registered. Which means it hasn't returned {'FINISHED'} and execute was never called. Therefore, to implement something like this, you would need to call operator SelectDirExample again.

EDIT: Solution 2 This is a solution that uses an custom filebrowser operator.

import bpy
from bpy.types import Operator, PropertyGroup
from bpy_extras.io_utils import ExportHelper #, ImportHelper
import os

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder")
    
    def draw_folder_selection(self, layout, property):
        row = layout.row(align=True)
        row.prop(self, property)
        ops = row.operator('select.directory_call_operator', text="", icon='FILEBROWSER')
        ops.operator_call = 'object.select_dir_example'
        # StringProperties need extra "" and \\ need to be doubled with my_string.replace("\\","\\\\")
        # E.g. my_string : StringProperty
        # f'my_string="{self.my_string}"'
        ops.operator_properties = f'input1={self.input1}, input2={self.input2}'
        ops.operator_folder_property_name = "folder"
        ops.old_filepath = self.folder

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

        layout.prop(self, 'input1')
        layout.prop(self, 'input2')
        self.draw_folder_selection(layout, 'folder')
        
    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        return {'FINISHED'}

# Use 'ImportHelper' when something is imported from this directory
class Select_OT_directory_call_operator(Operator, ExportHelper):
    bl_idname = "select.directory_call_operator"
    bl_label = "Select Directory"
    bl_description = " "
    bl_options = {'REGISTER', 'INTERNAL'}

    filename_ext = ""
    filter_glob: bpy.props.StringProperty(
        default="",
        options={'HIDDEN'},
        maxlen=255,  # Max internal buffer length, longer would be clamped.
    )
    use_filter_folder = True
    
    filepath: bpy.props.StringProperty(name="Folder Path", maxlen=0, default=" ")

    operator_call : bpy.props.StringProperty(options={'HIDDEN'}, description="operator to call")
    operator_properties : bpy.props.StringProperty(options={'HIDDEN'}, description="operator properties diffrent from folder")
    operator_folder_property_name : bpy.props.StringProperty(options={'HIDDEN'}, description="folder property of the operator to apply the new path to")
    old_filepath : bpy.props.StringProperty(options={'HIDDEN'}, description="old filepath of the operator used for cancel")
    
    def call_operator(self, filepath: str):
        filepath = filepath.replace("\\","\\\\") # needed because a str is placed into a str
        exec(f'bpy.ops.{self.operator_call}("INVOKE_DEFAULT", {self.operator_properties}, {self.operator_folder_property_name}="{filepath}")')

    def execute(self, context):
        user_path = self.properties.filepath
        if (not os.path.isdir(user_path)):
            msg = f"Please select a directory not a file\n{user_path}"
            self.report({'ERROR'}, msg)
            self.cancel(context)
            return {'CANCELLED'}
        self.call_operator(user_path)
        return {'FINISHED'}
    
    def cancel(self, context):
        self.call_operator(self.old_filepath)

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

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

if __name__ == "__main__":
    register()

With this solution there is no need define a setter and getter for the properties. Also the data of the properties do not need to be saved external.

Solution
I don't think an easy solution like this is possible because you are using a dialog which is a floating window and the folder selection will overwrite the current floating window. Also take a look at the info area/info-log of Blender, the Operator isn't executed/registered. Which means it hasn't returned {'FINISHED'} and execute was never called. Therefore, to implement something like this, you would need to call operator SelectDirExample again.

Solution 2 is preferred

Solution
I don't think an easy solution like this is possible because you are using a dialog which is a floating window and the folder selection will overwrite the current floating window. Also take a look at the info area/info-log of Blender, the Operator isn't executed/registered. Which means it hasn't returned {'FINISHED'} and execute was never called. Therefore, to implement something like this, you would need to call operator SelectDirExample again.

EDIT: Solution 2 This is a solution that uses an custom filebrowser operator.

import bpy
from bpy.types import Operator, PropertyGroup
from bpy_extras.io_utils import ExportHelper #, ImportHelper
import os

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder")
    
    def draw_folder_selection(self, layout, property):
        row = layout.row(align=True)
        row.prop(self, property)
        ops = row.operator('select.directory_call_operator', text="", icon='FILEBROWSER')
        ops.operator_call = 'object.select_dir_example'
        # StringProperties need extra "" and \\ need to be doubled with my_string.replace("\\","\\\\")
        # E.g. my_string : StringProperty
        # f'my_string="{self.my_string}"'
        ops.operator_properties = f'input1={self.input1}, input2={self.input2}'
        ops.operator_folder_property_name = "folder"
        ops.old_filepath = self.folder

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

        layout.prop(self, 'input1')
        layout.prop(self, 'input2')
        self.draw_folder_selection(layout, 'folder')
        
    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        return {'FINISHED'}

# Use 'ImportHelper' when something is imported from this directory
class Select_OT_directory_call_operator(Operator, ExportHelper):
    bl_idname = "select.directory_call_operator"
    bl_label = "Select Directory"
    bl_description = " "
    bl_options = {'REGISTER', 'INTERNAL'}

    filename_ext = ""
    filter_glob: bpy.props.StringProperty(
        default="",
        options={'HIDDEN'},
        maxlen=255,  # Max internal buffer length, longer would be clamped.
    )
    use_filter_folder = True
    
    filepath: bpy.props.StringProperty(name="Folder Path", maxlen=0, default=" ")

    operator_call : bpy.props.StringProperty(options={'HIDDEN'}, description="operator to call")
    operator_properties : bpy.props.StringProperty(options={'HIDDEN'}, description="operator properties diffrent from folder")
    operator_folder_property_name : bpy.props.StringProperty(options={'HIDDEN'}, description="folder property of the operator to apply the new path to")
    old_filepath : bpy.props.StringProperty(options={'HIDDEN'}, description="old filepath of the operator used for cancel")
    
    def call_operator(self, filepath: str):
        filepath = filepath.replace("\\","\\\\") # needed because a str is placed into a str
        exec(f'bpy.ops.{self.operator_call}("INVOKE_DEFAULT", {self.operator_properties}, {self.operator_folder_property_name}="{filepath}")')

    def execute(self, context):
        user_path = self.properties.filepath
        if (not os.path.isdir(user_path)):
            msg = f"Please select a directory not a file\n{user_path}"
            self.report({'ERROR'}, msg)
            self.cancel(context)
            return {'CANCELLED'}
        self.call_operator(user_path)
        return {'FINISHED'}
    
    def cancel(self, context):
        self.call_operator(self.old_filepath)

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

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

if __name__ == "__main__":
    register()

With this solution there is no need define a setter and getter for the properties. Also the data of the properties do not need to be saved external.

added `invoked` variable
Source Link
import bpy
from bpy.types import Operator, PropertyGroup

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}

    instances = 0
    
    def setter_getter(name: str):
        return {
            'get': lambda self: getattr(bpy.context.scene.selected_dir, name),
            'set': lambda self, value: setattr(bpy.context.scene.selected_dir, name, value)
        }
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0, **setter_getter('input1'))
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0, **setter_getter('input2'))

    def get_folder(self):
        return bpy.context.scene.selected_dir.folder
    def set_folder(self, value):
        props = bpy.context.scene.selected_dir
        props.folder = value
        if SelectDirExample.instances != 0:
            return
        SelectDirExample.instances += 1
        bpy.ops.object.select_dir_example("INVOKE_DEFAULT", input1=props.input1, input2=props.input2, folder=props.folder)
        SelectDirExample.instances -= 1
        
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH", get=get_folder, set=set_folder)

    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        
        return {'FINISHED'}

class SelectDirExample_Props(PropertyGroup):
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH")

def register():
    bpy.utils.register_class(SelectDirExample)
    bpy.utils.register_class(SelectDirExample_Props)
    bpy.types.Scene.selected_dir = bpy.props.PointerProperty(type=SelectDirExample_Props)

def unregister():
    bpy.utils.unregister_class(SelectDirExample)
    bpy.utils.unregister_class(SelectDirExample_Props)
    del bpy.types.Scene.selected_folder

if __name__ == "__main__":
    register()
    
bpy.ops.object.select_dir_example('INVOKE_DEFAULT')
import bpy
from bpy.types import Operator, PropertyGroup

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}

    instances = 0
    invoked = False
    
    def setter_getter(name: str):
        return {
            'get': lambda self: getattr(bpy.context.scene.selected_dir, name),
            'set': lambda self, value: setattr(bpy.context.scene.selected_dir, name, value)
        }
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0, **setter_getter('input1'))
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0, **setter_getter('input2'))

    def get_folder(self):
        return bpy.context.scene.selected_dir.folder
    def set_folder(self, value):
        props = bpy.context.scene.selected_dir
        props.folder = value
        if SelectDirExample.instances != 0 or not SelectDirExample.invoked:
            return
        SelectDirExample.instances += 1
        bpy.ops.object.select_dir_example("INVOKE_DEFAULT", input1=props.input1, input2=props.input2, folder=props.folder)
        SelectDirExample.instances -= 1
        SelectDirExample.invoked = False
        
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH", get=get_folder, set=set_folder)

    def invoke(self, context, event):
        SelectDirExample.invoked = True
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        
        SelectDirExample.invoked = False
        return {'FINISHED'}

class SelectDirExample_Props(PropertyGroup):
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH")

def register():
    bpy.utils.register_class(SelectDirExample)
    bpy.utils.register_class(SelectDirExample_Props)
    bpy.types.Scene.selected_dir = bpy.props.PointerProperty(type=SelectDirExample_Props)

def unregister():
    bpy.utils.unregister_class(SelectDirExample)
    bpy.utils.unregister_class(SelectDirExample_Props)
    del bpy.types.Scene.selected_folder

if __name__ == "__main__":
    register()

To not cause an EXCEPTION_ACCESS_VIOLATION another variable is used to store the folder path (which could also be inside the AddonPreferences). setter_getter is used to store the values into the same PropertyGroup as the folder. Otherwise, these values could not be moved along with the new operator call. They would be their default values because somehow the self in the set_folder is not the same as the self in the execute or invoke. A new instance with the stored variables will then be launched immediately, and it looks like before (only a movement in location of the dialog). The SelectDirExample.invoked variable is need otherwise it would launch two dialogs if the folder parameter is used on calling the operator e.g., bpy.ops.object.select_dir_example('INVOKE_DEFAULT', folder='<my_path>'). This does not apply if the call is from set_folder.

import bpy
from bpy.types import Operator, PropertyGroup

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}

    instances = 0
    
    def setter_getter(name: str):
        return {
            'get': lambda self: getattr(bpy.context.scene.selected_dir, name),
            'set': lambda self, value: setattr(bpy.context.scene.selected_dir, name, value)
        }
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0, **setter_getter('input1'))
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0, **setter_getter('input2'))

    def get_folder(self):
        return bpy.context.scene.selected_dir.folder
    def set_folder(self, value):
        props = bpy.context.scene.selected_dir
        props.folder = value
        if SelectDirExample.instances != 0:
            return
        SelectDirExample.instances += 1
        bpy.ops.object.select_dir_example("INVOKE_DEFAULT", input1=props.input1, input2=props.input2, folder=props.folder)
        SelectDirExample.instances -= 1
        
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH", get=get_folder, set=set_folder)

    def invoke(self, context, event):
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        
        return {'FINISHED'}

class SelectDirExample_Props(PropertyGroup):
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH")

def register():
    bpy.utils.register_class(SelectDirExample)
    bpy.utils.register_class(SelectDirExample_Props)
    bpy.types.Scene.selected_dir = bpy.props.PointerProperty(type=SelectDirExample_Props)

def unregister():
    bpy.utils.unregister_class(SelectDirExample)
    bpy.utils.unregister_class(SelectDirExample_Props)
    del bpy.types.Scene.selected_folder

if __name__ == "__main__":
    register()
    
bpy.ops.object.select_dir_example('INVOKE_DEFAULT')

To not cause an EXCEPTION_ACCESS_VIOLATION another variable is used to store the folder path (which could also be inside the AddonPreferences). setter_getter is used to store the values into the same PropertyGroup as the folder. Otherwise, these values could not be moved along with the new operator call. They would be their default values because somehow the self in the set_folder is not the same as the self in the execute or invoke. A new instance with the stored variables will then be launched immediately, and it looks like before (only a movement in location of the dialog).

import bpy
from bpy.types import Operator, PropertyGroup

class SelectDirExample(Operator):
    bl_idname = "object.select_dir_example"
    bl_label = "Select Dir Example"
    bl_options = {'REGISTER', 'UNDO'}

    instances = 0
    invoked = False
    
    def setter_getter(name: str):
        return {
            'get': lambda self: getattr(bpy.context.scene.selected_dir, name),
            'set': lambda self, value: setattr(bpy.context.scene.selected_dir, name, value)
        }
    
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0, **setter_getter('input1'))
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0, **setter_getter('input2'))

    def get_folder(self):
        return bpy.context.scene.selected_dir.folder
    def set_folder(self, value):
        props = bpy.context.scene.selected_dir
        props.folder = value
        if SelectDirExample.instances != 0 or not SelectDirExample.invoked:
            return
        SelectDirExample.instances += 1
        bpy.ops.object.select_dir_example("INVOKE_DEFAULT", input1=props.input1, input2=props.input2, folder=props.folder)
        SelectDirExample.instances -= 1
        SelectDirExample.invoked = False
        
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH", get=get_folder, set=set_folder)

    def invoke(self, context, event):
        SelectDirExample.invoked = True
        return_code = context.window_manager.invoke_props_dialog(self, width = 300)
        return return_code

    def execute(self, context):
        
        input1 = self.input1
        input2 = self.input2
        folder = self.folder
        
        # My processing goes here
        print(f"Folder location is: {folder}")
        # Finished processing.
        
        SelectDirExample.invoked = False
        return {'FINISHED'}

class SelectDirExample_Props(PropertyGroup):
    input1: bpy.props.FloatProperty(name="Input 1", default=1.0, min=0.0, max=100.0)
    input2: bpy.props.FloatProperty(name="Input 2", default=1.0, min=0.0, max=100.0)
    folder: bpy.props.StringProperty(name="Choose Folder", subtype="DIR_PATH")

def register():
    bpy.utils.register_class(SelectDirExample)
    bpy.utils.register_class(SelectDirExample_Props)
    bpy.types.Scene.selected_dir = bpy.props.PointerProperty(type=SelectDirExample_Props)

def unregister():
    bpy.utils.unregister_class(SelectDirExample)
    bpy.utils.unregister_class(SelectDirExample_Props)
    del bpy.types.Scene.selected_folder

if __name__ == "__main__":
    register()

To not cause an EXCEPTION_ACCESS_VIOLATION another variable is used to store the folder path (which could also be inside the AddonPreferences). setter_getter is used to store the values into the same PropertyGroup as the folder. Otherwise, these values could not be moved along with the new operator call. They would be their default values because somehow the self in the set_folder is not the same as the self in the execute or invoke. A new instance with the stored variables will then be launched immediately, and it looks like before (only a movement in location of the dialog). The SelectDirExample.invoked variable is need otherwise it would launch two dialogs if the folder parameter is used on calling the operator e.g., bpy.ops.object.select_dir_example('INVOKE_DEFAULT', folder='<my_path>'). This does not apply if the call is from set_folder.

Source Link
Loading