IO: Add support for Drag and Drop to FileHandlers

Added support to Drag and Drop to file handlers, part of #111242.

If file handlers are registered with an import operator they can now be
invoked with drag and drop path data.

Import operators must either declare a `filepath` StringProperty or both
a `directory` StringProperty and a `files` CollectionProperty depending
on if they support single or multiple files respectively.

Multiple FileHandlers could be valid for handling a dropped path. When
this happens a menu is shown so the user can choose which exact handler
to use for the file.

Pull Request: https://projects.blender.org/blender/blender/pulls/116047
This commit is contained in:
Guillermo Venegas
2024-01-06 03:51:45 +01:00
committed by Jesse Yurkovich
parent 09063a3632
commit 1254fee589
10 changed files with 605 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
"""
Basic FileHandler for Operator that imports just one file
-----------------
When creating a ``Operator`` that imports files, you may want to
add them 'drag-and-drop' support, File Handlers helps to define
a set of files extensions (:class:`FileHandler.bl_file_extensions`)
that the ``Operator`` support and a :class:`FileHandler.poll_drop`
function that can be used to check in what specific context the ``Operator``
can be invoked with 'drag-and-drop' filepath data.
Same as operators that uses the file select window, this operators
required a set of properties, when the ``Operator`` can import just one
file per execution it needs to define the following property:
.. code-block:: python
filepath: bpy.props.StringProperty(subtype='FILE_PATH')
This ``filepath`` property now will be used by the ``FileHandler`` to
set the 'drag-and-drop' filepath data.
"""
import bpy
class CurveTextImport(bpy.types.Operator):
""" Test importer that creates a text object from a .txt file """
bl_idname = "curve.text_import"
bl_label = "Import a text file as text object"
"""
This Operator supports import one .txt file at the time, we need the
following filepath property that the file handler will use to set file path data.
"""
filepath: bpy.props.StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return (context.area and context.area.type == "VIEW_3D")
def execute(self, context):
""" Calls to this Operator can set unfiltered filepaths, ensure the file extension is .txt. """
if not self.filepath or not self.filepath.endswith(".txt"):
return {'CANCELLED'}
with open(self.filepath) as file:
text_curve = bpy.data.curves.new(type="FONT", name="Text")
text_curve.body = ''.join(file.readlines())
text_object = bpy.data.objects.new(name="Text", object_data=text_curve)
bpy.context.scene.collection.objects.link(text_object)
return {'FINISHED'}
"""
By default the file handler invokes the operator with the filepath property set.
In this example if this property is set the operator is executed, if not the
file select window is invoked.
This depends on setting 'options={'SKIP_SAVE'}' to the property options to avoid
to reuse filepath data between operator calls.
"""
def invoke(self, context, event):
if self.filepath:
return self.execute(context)
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
class CURVE_FH_text_import(bpy.types.FileHandler):
bl_idname = "CURVE_FH_text_import"
bl_label = "File handler for curve text object import"
bl_import_operator = "curve.text_import"
bl_file_extensions = ".txt"
@classmethod
def poll_drop(cls, context):
return (context.area and context.area.type == 'VIEW_3D')
bpy.utils.register_class(CurveTextImport)
bpy.utils.register_class(CURVE_FH_text_import)

View File

@@ -0,0 +1,91 @@
"""
Basic FileHandler for Operator that imports multiple files
-----------------
Also operators can be invoked with multiple files from 'drag-and-drop',
but for this it is require to define the following properties:
.. code-block:: python
directory: StringProperty(subtype='FILE_PATH')
files: CollectionProperty(type=bpy.types.OperatorFileListElement)
This ``directory`` and ``files`` properties now will be used by the
``FileHandler`` to set 'drag-and-drop' filepath data.
"""
import bpy
from mathutils import Vector
class ShaderScriptImport(bpy.types.Operator):
"""Test importer that creates scripts nodes from .txt files"""
bl_idname = "shader.script_import"
bl_label = "Import a text file as a script node"
"""
This Operator can import multiple .txt files, we need following directory and files
properties that the file handler will use to set files path data
"""
directory: bpy.props.StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE'})
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return (context.region and context.region.type == 'WINDOW'
and context.area and context.area.ui_type == 'ShaderNodeTree'
and context.object and context.object.type == 'MESH'
and context.material)
def execute(self, context):
""" The directory property need to be set. """
if not self.directory:
return {'CANCELLED'}
x = 0.0
y = 0.0
for file in self.files:
"""
Calls to the operator can set unfiltered file names,
ensure the file extension is .txt
"""
if file.name.endswith(".txt"):
node_tree = context.material.node_tree
text_node = node_tree.nodes.new(type="ShaderNodeScript")
text_node.mode = 'EXTERNAL'
import os
filepath = os.path.join(self.directory, file.name)
text_node.filepath = filepath
text_node.location = Vector((x, y))
x += 20.0
y -= 20.0
return {'FINISHED'}
"""
By default the file handler invokes the operator with the directory and files properties set.
In this example if this properties are set the operator is executed, if not the
file select window is invoked.
This depends on setting 'options={'SKIP_SAVE'}' to the properties options to avoid
to reuse filepath data between operator calls.
"""
def invoke(self, context, event):
if self.directory:
return self.execute(context)
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
class SHADER_FH_script_import(bpy.types.FileHandler):
bl_idname = "SHADER_FH_script_import"
bl_label = "File handler for shader script node import"
bl_import_operator = "shader.script_import"
bl_file_extensions = ".txt"
@classmethod
def poll_drop(cls, context):
return (context.region and context.region.type == 'WINDOW'
and context.area and context.area.ui_type == 'ShaderNodeTree')
bpy.utils.register_class(ShaderScriptImport)
bpy.utils.register_class(SHADER_FH_script_import)