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:
parent
09063a3632
commit
1254fee589
|
@ -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)
|
|
@ -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)
|
|
@ -35,6 +35,11 @@ struct FileHandlerType {
|
||||||
|
|
||||||
/** RNA integration. */
|
/** RNA integration. */
|
||||||
ExtensionRNA rna_ext;
|
ExtensionRNA rna_ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a vector of indices in #paths of file paths supported by the file handler.
|
||||||
|
*/
|
||||||
|
blender::Vector<int64_t> filter_supported_paths(const blender::Span<std::string> paths) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,4 +63,12 @@ void file_handler_remove(FileHandlerType *file_handler);
|
||||||
/** Return pointers to all registered file handlers. */
|
/** Return pointers to all registered file handlers. */
|
||||||
Span<std::unique_ptr<FileHandlerType>> file_handlers();
|
Span<std::unique_ptr<FileHandlerType>> file_handlers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a vector of file handlers that support any file path in `paths` and the call to
|
||||||
|
* `poll_drop` returns #true. Caller must check if each file handler have a valid
|
||||||
|
* `import_operator`.
|
||||||
|
*/
|
||||||
|
blender::Vector<FileHandlerType *> file_handlers_poll_file_drop(
|
||||||
|
const bContext *C, const blender::Span<std::string> paths);
|
||||||
|
|
||||||
} // namespace blender::bke
|
} // namespace blender::bke
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "BKE_file_handler.hh"
|
#include "BKE_file_handler.hh"
|
||||||
|
|
||||||
|
#include "BLI_path_util.h"
|
||||||
#include "BLI_string.h"
|
#include "BLI_string.h"
|
||||||
|
|
||||||
namespace blender::bke {
|
namespace blender::bke {
|
||||||
|
@ -62,4 +63,58 @@ void file_handler_remove(FileHandlerType *file_handler)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blender::Vector<FileHandlerType *> file_handlers_poll_file_drop(
|
||||||
|
const bContext *C, const blender::Span<std::string> paths)
|
||||||
|
{
|
||||||
|
blender::Vector<std::string> path_extensions;
|
||||||
|
for (const std::string &path : paths) {
|
||||||
|
const char *extension = BLI_path_extension(path.c_str());
|
||||||
|
if (!extension) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
path_extensions.append_non_duplicates(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
blender::Vector<FileHandlerType *> result;
|
||||||
|
for (const std::unique_ptr<FileHandlerType> &file_handler_ptr : file_handlers()) {
|
||||||
|
FileHandlerType &file_handler = *file_handler_ptr;
|
||||||
|
const auto &file_extensions = file_handler.file_extensions;
|
||||||
|
|
||||||
|
const bool support_any_extension = std::any_of(
|
||||||
|
file_extensions.begin(),
|
||||||
|
file_extensions.end(),
|
||||||
|
[&path_extensions](const std::string &test_extension) {
|
||||||
|
return path_extensions.contains(test_extension);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!support_any_extension) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(file_handler.poll_drop && file_handler.poll_drop(C, &file_handler))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(&file_handler);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
blender::Vector<int64_t> FileHandlerType::filter_supported_paths(
|
||||||
|
const blender::Span<std::string> paths) const
|
||||||
|
{
|
||||||
|
blender::Vector<int64_t> indices;
|
||||||
|
|
||||||
|
for (const int idx : paths.index_range()) {
|
||||||
|
const char *extension = BLI_path_extension(paths[idx].c_str());
|
||||||
|
if (!extension) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (file_extensions.contains(extension)) {
|
||||||
|
indices.append(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::bke
|
} // namespace blender::bke
|
||||||
|
|
|
@ -17,6 +17,7 @@ set(INC
|
||||||
../../io/wavefront_obj
|
../../io/wavefront_obj
|
||||||
../../makesrna
|
../../makesrna
|
||||||
../../windowmanager
|
../../windowmanager
|
||||||
|
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||||
)
|
)
|
||||||
|
|
||||||
set(INC_SYS
|
set(INC_SYS
|
||||||
|
@ -27,6 +28,7 @@ set(SRC
|
||||||
io_alembic.cc
|
io_alembic.cc
|
||||||
io_cache.cc
|
io_cache.cc
|
||||||
io_collada.cc
|
io_collada.cc
|
||||||
|
io_drop_import_file.cc
|
||||||
io_gpencil_export.cc
|
io_gpencil_export.cc
|
||||||
io_gpencil_import.cc
|
io_gpencil_import.cc
|
||||||
io_gpencil_utils.cc
|
io_gpencil_utils.cc
|
||||||
|
@ -39,6 +41,7 @@ set(SRC
|
||||||
io_alembic.hh
|
io_alembic.hh
|
||||||
io_cache.hh
|
io_cache.hh
|
||||||
io_collada.hh
|
io_collada.hh
|
||||||
|
io_drop_import_file.hh
|
||||||
io_gpencil.hh
|
io_gpencil.hh
|
||||||
io_obj.hh
|
io_obj.hh
|
||||||
io_ops.hh
|
io_ops.hh
|
||||||
|
@ -53,6 +56,7 @@ set(LIB
|
||||||
PRIVATE bf::depsgraph
|
PRIVATE bf::depsgraph
|
||||||
PRIVATE bf::dna
|
PRIVATE bf::dna
|
||||||
PRIVATE bf::intern::guardedalloc
|
PRIVATE bf::intern::guardedalloc
|
||||||
|
PRIVATE bf::intern::clog
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WITH_OPENCOLLADA)
|
if(WITH_OPENCOLLADA)
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#include "BLI_path_util.h"
|
||||||
|
#include "BLI_string.h"
|
||||||
|
|
||||||
|
#include "BLT_translation.h"
|
||||||
|
|
||||||
|
#include "BKE_file_handler.hh"
|
||||||
|
|
||||||
|
#include "CLG_log.h"
|
||||||
|
|
||||||
|
#include "DNA_space_types.h"
|
||||||
|
|
||||||
|
#include "RNA_access.hh"
|
||||||
|
#include "RNA_define.hh"
|
||||||
|
#include "RNA_prototypes.h"
|
||||||
|
|
||||||
|
#include "WM_api.hh"
|
||||||
|
#include "WM_types.hh"
|
||||||
|
|
||||||
|
#include "UI_interface.hh"
|
||||||
|
|
||||||
|
#include "io_drop_import_file.hh"
|
||||||
|
|
||||||
|
static CLG_LogRef LOG = {"io.drop_import_file"};
|
||||||
|
|
||||||
|
/* Retuns the list of file paths stored in #WM_OT_drop_import_file operator properties. */
|
||||||
|
static blender::Vector<std::string> drop_import_file_paths(const wmOperator *op)
|
||||||
|
{
|
||||||
|
blender::Vector<std::string> result;
|
||||||
|
char dir[FILE_MAX], file[FILE_MAX];
|
||||||
|
|
||||||
|
RNA_string_get(op->ptr, "directory", dir);
|
||||||
|
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "files");
|
||||||
|
int files_len = RNA_property_collection_length(op->ptr, prop);
|
||||||
|
|
||||||
|
for (int i = 0; i < files_len; i++) {
|
||||||
|
PointerRNA fileptr;
|
||||||
|
RNA_property_collection_lookup_int(op->ptr, prop, i, &fileptr);
|
||||||
|
RNA_string_get(&fileptr, "name", file);
|
||||||
|
char file_path[FILE_MAX];
|
||||||
|
BLI_path_join(file_path, sizeof(file_path), dir, file);
|
||||||
|
result.append(file_path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a vector of file handlers that support any file path in `paths` and the call to
|
||||||
|
* `poll_drop` returns #true. Unlike `bke::file_handlers_poll_file_drop`, it ensures that file
|
||||||
|
* handlers have a valid import operator.
|
||||||
|
*/
|
||||||
|
static blender::Vector<blender::bke::FileHandlerType *> drop_import_file_poll_file_handlers(
|
||||||
|
const bContext *C, const blender::Span<std::string> paths, const bool quiet = true)
|
||||||
|
{
|
||||||
|
using namespace blender;
|
||||||
|
auto file_handlers = bke::file_handlers_poll_file_drop(C, paths);
|
||||||
|
file_handlers.remove_if([quiet](const bke::FileHandlerType *file_handler) {
|
||||||
|
return WM_operatortype_find(file_handler->import_operator, quiet) == nullptr;
|
||||||
|
});
|
||||||
|
return file_handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a RNA pointer for the `FileHandlerType.import_operator` and sets on it all supported
|
||||||
|
* file paths from `paths`.
|
||||||
|
*/
|
||||||
|
static PointerRNA file_handler_import_operator_create_ptr(
|
||||||
|
const blender::bke::FileHandlerType *file_handler, const blender::Span<std::string> paths)
|
||||||
|
{
|
||||||
|
wmOperatorType *ot = WM_operatortype_find(file_handler->import_operator, false);
|
||||||
|
BLI_assert(ot != nullptr);
|
||||||
|
PointerRNA props;
|
||||||
|
WM_operator_properties_create_ptr(&props, ot);
|
||||||
|
|
||||||
|
const auto supported_paths = file_handler->filter_supported_paths(paths);
|
||||||
|
|
||||||
|
PropertyRNA *filepath_prop = RNA_struct_find_property_check(props, "filepath", PROP_STRING);
|
||||||
|
if (filepath_prop) {
|
||||||
|
RNA_property_string_set(&props, filepath_prop, paths[supported_paths[0]].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyRNA *directory_prop = RNA_struct_find_property_check(props, "directory", PROP_STRING);
|
||||||
|
if (directory_prop) {
|
||||||
|
char dir[FILE_MAX];
|
||||||
|
BLI_path_split_dir_part(paths[0].c_str(), dir, sizeof(dir));
|
||||||
|
RNA_property_string_set(&props, directory_prop, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyRNA *files_prop = RNA_struct_find_collection_property_check(
|
||||||
|
props, "files", &RNA_OperatorFileListElement);
|
||||||
|
if (files_prop) {
|
||||||
|
RNA_property_collection_clear(&props, files_prop);
|
||||||
|
for (const auto &index : supported_paths) {
|
||||||
|
char file[FILE_MAX];
|
||||||
|
BLI_path_split_file_part(paths[index].c_str(), file, sizeof(file));
|
||||||
|
|
||||||
|
PointerRNA item_ptr{};
|
||||||
|
RNA_property_collection_add(&props, files_prop, &item_ptr);
|
||||||
|
RNA_string_set(&item_ptr, "name", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool has_any_filepath_prop = filepath_prop || directory_prop || files_prop;
|
||||||
|
/**
|
||||||
|
* The `directory` and `files` properties are both required for handling multiple files, if
|
||||||
|
* only one is defined means that the other is missing.
|
||||||
|
*/
|
||||||
|
const bool has_missing_filepath_prop = bool(directory_prop) != bool(files_prop);
|
||||||
|
|
||||||
|
if (!has_any_filepath_prop || has_missing_filepath_prop) {
|
||||||
|
const char *message =
|
||||||
|
"Expected operator properties filepath or files and directory not found. Refer to "
|
||||||
|
"FileHandler documentation for details.";
|
||||||
|
CLOG_WARN(&LOG, TIP_(message));
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wm_drop_import_file_exec(bContext *C, wmOperator *op)
|
||||||
|
{
|
||||||
|
auto paths = drop_import_file_paths(op);
|
||||||
|
if (paths.is_empty()) {
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file_handlers = drop_import_file_poll_file_handlers(C, paths, false);
|
||||||
|
if (file_handlers.is_empty()) {
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
wmOperatorType *ot = WM_operatortype_find(file_handlers[0]->import_operator, false);
|
||||||
|
PointerRNA file_props = file_handler_import_operator_create_ptr(file_handlers[0], paths);
|
||||||
|
|
||||||
|
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &file_props, nullptr);
|
||||||
|
WM_operator_properties_free(&file_props);
|
||||||
|
return OPERATOR_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wm_drop_import_file_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
||||||
|
{
|
||||||
|
const auto paths = drop_import_file_paths(op);
|
||||||
|
if (paths.is_empty()) {
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file_handlers = drop_import_file_poll_file_handlers(C, paths, false);
|
||||||
|
if (file_handlers.size() == 1) {
|
||||||
|
return wm_drop_import_file_exec(C, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a menu with all file handler import operators that can support any files in paths and
|
||||||
|
* let user decide which to use.
|
||||||
|
*/
|
||||||
|
uiPopupMenu *pup = UI_popup_menu_begin(C, "", ICON_NONE);
|
||||||
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
||||||
|
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
|
||||||
|
|
||||||
|
for (auto *file_handler : file_handlers) {
|
||||||
|
const PointerRNA file_props = file_handler_import_operator_create_ptr(file_handler, paths);
|
||||||
|
wmOperatorType *ot = WM_operatortype_find(file_handler->import_operator, false);
|
||||||
|
uiItemFullO_ptr(layout,
|
||||||
|
ot,
|
||||||
|
TIP_(ot->name),
|
||||||
|
ICON_NONE,
|
||||||
|
static_cast<IDProperty *>(file_props.data),
|
||||||
|
WM_OP_INVOKE_DEFAULT,
|
||||||
|
UI_ITEM_NONE,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_popup_menu_end(C, pup);
|
||||||
|
return OPERATOR_INTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WM_OT_drop_import_file(wmOperatorType *ot)
|
||||||
|
{
|
||||||
|
ot->name = "Drop to Import File";
|
||||||
|
ot->description = "Operator that allows file handlers to receive file drops";
|
||||||
|
ot->idname = "WM_OT_drop_import_file";
|
||||||
|
ot->flag = OPTYPE_INTERNAL;
|
||||||
|
ot->exec = wm_drop_import_file_exec;
|
||||||
|
ot->invoke = wm_drop_import_file_invoke;
|
||||||
|
|
||||||
|
PropertyRNA *prop;
|
||||||
|
|
||||||
|
prop = RNA_def_string_dir_path(
|
||||||
|
ot->srna, "directory", nullptr, FILE_MAX, "Directory", "Directory of the file");
|
||||||
|
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
|
||||||
|
|
||||||
|
prop = RNA_def_collection_runtime(ot->srna, "files", &RNA_OperatorFileListElement, "Files", "");
|
||||||
|
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drop_import_file_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
|
||||||
|
{
|
||||||
|
const auto paths = WM_drag_get_paths(drag);
|
||||||
|
|
||||||
|
char dir[FILE_MAX];
|
||||||
|
BLI_path_split_dir_part(paths[0].c_str(), dir, sizeof(dir));
|
||||||
|
RNA_string_set(drop->ptr, "directory", dir);
|
||||||
|
|
||||||
|
RNA_collection_clear(drop->ptr, "files");
|
||||||
|
for (const auto &path : paths) {
|
||||||
|
char file[FILE_MAX];
|
||||||
|
BLI_path_split_file_part(path.c_str(), file, sizeof(file));
|
||||||
|
|
||||||
|
PointerRNA itemptr{};
|
||||||
|
RNA_collection_add(drop->ptr, "files", &itemptr);
|
||||||
|
RNA_string_set(&itemptr, "name", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool drop_import_file_poll(bContext *C, wmDrag *drag, const wmEvent * /*event*/)
|
||||||
|
{
|
||||||
|
if (drag->type != WM_DRAG_PATH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto paths = WM_drag_get_paths(drag);
|
||||||
|
return !drop_import_file_poll_file_handlers(C, paths, true).is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *drop_import_file_tooltip(bContext *C,
|
||||||
|
wmDrag *drag,
|
||||||
|
const int /*xy*/[2],
|
||||||
|
wmDropBox * /*drop*/)
|
||||||
|
{
|
||||||
|
const auto paths = WM_drag_get_paths(drag);
|
||||||
|
const auto file_handlers = drop_import_file_poll_file_handlers(C, paths, true);
|
||||||
|
if (file_handlers.size() == 1) {
|
||||||
|
wmOperatorType *ot = WM_operatortype_find(file_handlers[0]->import_operator, false);
|
||||||
|
return BLI_strdup(TIP_(ot->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BLI_strdup(TIP_("Multiple file handlers can be used, drop to pick which to use"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ED_dropbox_drop_import_file()
|
||||||
|
{
|
||||||
|
ListBase *lb = WM_dropboxmap_find("Window", SPACE_EMPTY, RGN_TYPE_WINDOW);
|
||||||
|
WM_dropbox_add(lb,
|
||||||
|
"WM_OT_drop_import_file",
|
||||||
|
drop_import_file_poll,
|
||||||
|
drop_import_file_copy,
|
||||||
|
nullptr,
|
||||||
|
drop_import_file_tooltip);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void WM_OT_drop_import_file(wmOperatorType *ot);
|
||||||
|
void ED_dropbox_drop_import_file();
|
|
@ -23,6 +23,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "io_cache.hh"
|
#include "io_cache.hh"
|
||||||
|
#include "io_drop_import_file.hh"
|
||||||
#include "io_gpencil.hh"
|
#include "io_gpencil.hh"
|
||||||
#include "io_obj.hh"
|
#include "io_obj.hh"
|
||||||
#include "io_ply_ops.hh"
|
#include "io_ply_ops.hh"
|
||||||
|
@ -74,4 +75,6 @@ void ED_operatortypes_io()
|
||||||
WM_operatortype_append(WM_OT_stl_import);
|
WM_operatortype_append(WM_OT_stl_import);
|
||||||
WM_operatortype_append(WM_OT_stl_export);
|
WM_operatortype_append(WM_OT_stl_export);
|
||||||
#endif
|
#endif
|
||||||
|
WM_operatortype_append(WM_OT_drop_import_file);
|
||||||
|
ED_dropbox_drop_import_file();
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,22 @@ bool RNA_struct_idprops_contains_datablock(const StructRNA *type);
|
||||||
bool RNA_struct_idprops_unset(PointerRNA *ptr, const char *identifier);
|
bool RNA_struct_idprops_unset(PointerRNA *ptr, const char *identifier);
|
||||||
|
|
||||||
PropertyRNA *RNA_struct_find_property(PointerRNA *ptr, const char *identifier);
|
PropertyRNA *RNA_struct_find_property(PointerRNA *ptr, const char *identifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `RNA_struct_find_property` but returns `nullptr` if the property type is no same to
|
||||||
|
* `property_type_check`.
|
||||||
|
*/
|
||||||
|
PropertyRNA *RNA_struct_find_property_check(PointerRNA &props,
|
||||||
|
const char *name,
|
||||||
|
const PropertyType property_type_check);
|
||||||
|
/**
|
||||||
|
* Same as `RNA_struct_find_property` but returns `nullptr` if the property type is not
|
||||||
|
* #PropertyType::PROP_COLLECTION or property struct type is different to `struct_type_check`.
|
||||||
|
*/
|
||||||
|
PropertyRNA *RNA_struct_find_collection_property_check(PointerRNA &props,
|
||||||
|
const char *name,
|
||||||
|
const StructRNA *struct_type_check);
|
||||||
|
|
||||||
bool RNA_struct_contains_property(PointerRNA *ptr, PropertyRNA *prop_test);
|
bool RNA_struct_contains_property(PointerRNA *ptr, PropertyRNA *prop_test);
|
||||||
unsigned int RNA_struct_count_properties(StructRNA *srna);
|
unsigned int RNA_struct_count_properties(StructRNA *srna);
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
#include "BKE_node.hh"
|
#include "BKE_node.hh"
|
||||||
#include "BKE_report.h"
|
#include "BKE_report.h"
|
||||||
|
|
||||||
|
#include "CLG_log.h"
|
||||||
|
|
||||||
#include "DEG_depsgraph.hh"
|
#include "DEG_depsgraph.hh"
|
||||||
#include "DEG_depsgraph_build.hh"
|
#include "DEG_depsgraph_build.hh"
|
||||||
|
|
||||||
|
@ -61,6 +63,8 @@
|
||||||
|
|
||||||
const PointerRNA PointerRNA_NULL = {nullptr};
|
const PointerRNA PointerRNA_NULL = {nullptr};
|
||||||
|
|
||||||
|
static CLG_LogRef LOG = {"rna.access"};
|
||||||
|
|
||||||
/* Init/Exit */
|
/* Init/Exit */
|
||||||
|
|
||||||
void RNA_init()
|
void RNA_init()
|
||||||
|
@ -780,6 +784,86 @@ PropertyRNA *RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *rna_property_type_identifier(PropertyType prop_type)
|
||||||
|
{
|
||||||
|
switch (prop_type) {
|
||||||
|
case PROP_BOOLEAN:
|
||||||
|
return RNA_struct_identifier(&RNA_BoolProperty);
|
||||||
|
case PROP_INT:
|
||||||
|
return RNA_struct_identifier(&RNA_IntProperty);
|
||||||
|
case PROP_FLOAT:
|
||||||
|
return RNA_struct_identifier(&RNA_FloatProperty);
|
||||||
|
case PROP_STRING:
|
||||||
|
return RNA_struct_identifier(&RNA_StringProperty);
|
||||||
|
case PROP_ENUM:
|
||||||
|
return RNA_struct_identifier(&RNA_EnumProperty);
|
||||||
|
case PROP_POINTER:
|
||||||
|
return RNA_struct_identifier(&RNA_PointerProperty);
|
||||||
|
case PROP_COLLECTION:
|
||||||
|
return RNA_struct_identifier(&RNA_CollectionProperty);
|
||||||
|
default:
|
||||||
|
return RNA_struct_identifier(&RNA_Property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyRNA *RNA_struct_find_property_check(PointerRNA &props,
|
||||||
|
const char *name,
|
||||||
|
const PropertyType property_type_check)
|
||||||
|
{
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(&props, name);
|
||||||
|
if (!prop) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const PropertyType prop_type = RNA_property_type(prop);
|
||||||
|
if (prop_type == property_type_check) {
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
CLOG_WARN(&LOG,
|
||||||
|
TIP_("'%s : %s()' expected, got '%s : %s()'"),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(property_type_check),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(prop_type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyRNA *RNA_struct_find_collection_property_check(PointerRNA &props,
|
||||||
|
const char *name,
|
||||||
|
const StructRNA *struct_type_check)
|
||||||
|
{
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(&props, name);
|
||||||
|
if (!prop) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PropertyType prop_type = RNA_property_type(prop);
|
||||||
|
const StructRNA *prop_struct_type = RNA_property_pointer_type(&props, prop);
|
||||||
|
if (prop_type == PROP_COLLECTION && prop_struct_type == struct_type_check) {
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop_type != PROP_COLLECTION) {
|
||||||
|
CLOG_WARN(&LOG,
|
||||||
|
TIP_("'%s : %s(type = %s)' expected, got '%s : %s()'"),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(PROP_COLLECTION),
|
||||||
|
RNA_struct_identifier(struct_type_check),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(prop_type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOG_WARN(&LOG,
|
||||||
|
TIP_("'%s : %s(type = %s)' expected, got '%s : %s(type = %s)'."),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(PROP_COLLECTION),
|
||||||
|
RNA_struct_identifier(struct_type_check),
|
||||||
|
name,
|
||||||
|
rna_property_type_identifier(PROP_COLLECTION),
|
||||||
|
RNA_struct_identifier(prop_struct_type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
PropertyRNA *rna_struct_find_nested(PointerRNA *ptr, StructRNA *srna)
|
PropertyRNA *rna_struct_find_nested(PointerRNA *ptr, StructRNA *srna)
|
||||||
{
|
{
|
||||||
PropertyRNA *prop = nullptr;
|
PropertyRNA *prop = nullptr;
|
||||||
|
|
Loading…
Reference in New Issue