diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index cc6383c89ff..ce8d5576e28 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -6,6 +6,7 @@ from __future__ import annotations import bpy from bpy.types import ( + FileHandler, Operator, PropertyGroup, ) @@ -441,9 +442,27 @@ class NODE_OT_enum_definition_item_move(Operator): return {'FINISHED'} +class NODE_FH_image_node(FileHandler): + bl_idname = "NODE_FH_image_node" + bl_label = "Image node" + bl_import_operator = "node.add_file" + bl_file_extensions = ";".join((*bpy.path.extensions_image, *bpy.path.extensions_movie)) + + @classmethod + def poll_drop(cls, context): + return ( + (context.area is not None) and + (context.area.type == 'NODE_EDITOR') and + (context.region is not None) and + (context.region.type == 'WINDOW') + ) + + classes = ( NodeSetting, + NODE_FH_image_node, + NODE_OT_add_node, NODE_OT_add_simulation_zone, NODE_OT_add_repeat_zone, diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 14acd22b79c..1d2ef47337a 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -4,6 +4,7 @@ set(INC ../include + ../io ../../asset_system ../../blenkernel ../../blenloader diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index e86c751ba2d..529ed7c16d6 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -16,6 +16,7 @@ #include "DNA_node_types.h" #include "DNA_texture_types.h" +#include "BLI_easing.h" #include "BLI_listbase.h" #include "BLI_math_geom.h" @@ -50,6 +51,9 @@ #include "UI_view2d.hh" +#include "io_utils.hh" +#include + #include "node_intern.hh" /* own include */ namespace blender::ed::space_node { @@ -683,17 +687,58 @@ static bool node_add_file_poll(bContext *C) ELEM(snode->nodetree->type, NTREE_SHADER, NTREE_TEXTURE, NTREE_COMPOSIT, NTREE_GEOMETRY); } +/** Node stack animation data, sorts nodes so each node is placed on top of each other. */ +struct NodeStackAnimationData { + Vector nodes; + wmTimer *anim_timer; +}; + +static int node_add_file_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + NodeStackAnimationData *data = static_cast(op->customdata); + + if (event->type != TIMER || data == nullptr || data->anim_timer != event->customdata) { + return OPERATOR_PASS_THROUGH; + } + + const float node_stack_anim_duration = 0.25f; + const float duration = float(data->anim_timer->time_duration); + const float prev_duration = duration - float(data->anim_timer->time_delta); + const float clamped_duration = math::min(duration, node_stack_anim_duration); + const float delta_factor = + BLI_easing_cubic_ease_in_out(clamped_duration, 0.0f, 1.0f, node_stack_anim_duration) - + BLI_easing_cubic_ease_in_out(prev_duration, 0.0f, 1.0f, node_stack_anim_duration); + + bool redraw = false; + /* Each node is pushed by all previous nodes in the stack. */ + float stack_offset = 0.0f; + + for (bNode *node : data->nodes) { + node->locy -= stack_offset; + stack_offset += (node->runtime->totr.ymax - node->runtime->totr.ymin) * delta_factor; + redraw = true; + } + + if (redraw) { + ED_region_tag_redraw(CTX_wm_region(C)); + } + + /* End stack animation. */ + if (duration > node_stack_anim_duration) { + WM_event_timer_remove(CTX_wm_manager(C), nullptr, data->anim_timer); + MEM_delete(data); + op->customdata = nullptr; + return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); + } + + return OPERATOR_RUNNING_MODAL; +} + static int node_add_file_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); int type = 0; - - Image *ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM); - if (!ima) { - return OPERATOR_CANCELLED; - } - switch (snode.nodetree->type) { case NTREE_SHADER: type = SH_NODE_TEX_IMAGE; @@ -710,36 +755,82 @@ static int node_add_file_exec(bContext *C, wmOperator *op) default: return OPERATOR_CANCELLED; } + Vector images; + /* Load all paths as ID Images. */ + const Vector paths = ed::io::paths_from_operator_properties(op->ptr); + for (const std::string &path : paths) { + RNA_string_set(op->ptr, "filepath", path.c_str()); + Image *image = (Image *)WM_operator_drop_load_path(C, op, ID_IM); + if (!image) { + BKE_report(op->reports, RPT_WARNING, fmt::format("Could not load {}", path).c_str()); + continue; + } + images.append(image); + /* When adding new image file via drag-drop we need to load #ImBuf in order + * to get proper image source. */ + BKE_image_signal(bmain, image, nullptr, IMA_SIGNAL_RELOAD); + WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, image); + } - ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); + /* If not path is provided, try to get a ID Image from operator. */ + if (paths.is_empty()) { + Image *image = (Image *)WM_operator_drop_load_path(C, op, ID_IM); + if (image) { + images.append(image); + } + } - bNode *node = add_static_node(*C, type, snode.runtime->cursor); + float2 position = snode.runtime->cursor; + Vector nodes; + /* Add a node for each image. */ + for (Image *image : images) { + bNode *node = add_static_node(*C, type, position); + if (!node) { + BKE_report(op->reports, RPT_WARNING, "Could not add an image node"); + continue; + } + if (type == GEO_NODE_IMAGE_TEXTURE) { + bNodeSocket *image_socket = (bNodeSocket *)node->inputs.first; + bNodeSocketValueImage *socket_value = (bNodeSocketValueImage *)image_socket->default_value; + socket_value->value = image; + } + else { + node->id = (ID *)image; + } + nodes.append(node); + /* Initial offset between nodes. */ + position[1] -= 20.0f; + } - if (!node) { - BKE_report(op->reports, RPT_WARNING, "Could not add an image node"); + if (nodes.is_empty()) { return OPERATOR_CANCELLED; } - if (type == GEO_NODE_IMAGE_TEXTURE) { - bNodeSocket *image_socket = (bNodeSocket *)node->inputs.first; - bNodeSocketValueImage *socket_value = (bNodeSocketValueImage *)image_socket->default_value; - socket_value->value = ima; - } - else { - node->id = (ID *)ima; + /* Set new nodes as selected. */ + bNodeTree &node_tree = *snode.edittree; + node_deselect_all(node_tree); + for (bNode *node : nodes) { + nodeSetSelected(node, true); } + ED_node_set_active(bmain, &snode, &node_tree, nodes[0], nullptr); - /* When adding new image file via drag-drop we need to load #ImBuf in order - * to get proper image source. */ - if (RNA_struct_property_is_set(op->ptr, "filepath")) { - BKE_image_signal(bmain, ima, nullptr, IMA_SIGNAL_RELOAD); - WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ima); - } + ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); ED_node_tree_propagate_change(C, bmain, snode.edittree); DEG_relations_tag_update(bmain); - return OPERATOR_FINISHED; + if (nodes.size() == 1) { + return OPERATOR_FINISHED; + } + + /* Start the stack animation, so each node is placed on top of each other. */ + NodeStackAnimationData *data = MEM_new(__func__); + data->nodes = std::move(nodes); + data->anim_timer = WM_event_timer_add(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.02); + op->customdata = data; + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; } static int node_add_file_invoke(bContext *C, wmOperator *op, const wmEvent *event) @@ -774,6 +865,7 @@ void NODE_OT_add_file(wmOperatorType *ot) /* callbacks */ ot->exec = node_add_file_exec; + ot->modal = node_add_file_modal; ot->invoke = node_add_file_invoke; ot->poll = node_add_file_poll; @@ -784,7 +876,8 @@ void NODE_OT_add_file(wmOperatorType *ot) FILE_TYPE_FOLDER | FILE_TYPE_IMAGE | FILE_TYPE_MOVIE, FILE_SPECIAL, FILE_OPENFILE, - WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH, + WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_DIRECTORY | + WM_FILESEL_FILES, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); WM_operator_properties_id_lookup(ot, true); diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index a02bc486f70..28de313cc88 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -879,13 +879,8 @@ static bool node_collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent * return WM_drag_is_ID_type(drag, ID_GR) && !UI_but_active_drop_name(C); } -static bool node_ima_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) +static bool node_id_im_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) { - if (drag->type == WM_DRAG_PATH) { - const eFileSel_File_Types file_type = static_cast( - WM_drag_get_path_file_type(drag)); - return ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE); - } return WM_drag_is_ID_type(drag, ID_IM); } @@ -915,22 +910,14 @@ static void node_id_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) RNA_int_set(drop->ptr, "session_uid", int(id->session_uid)); } -static void node_id_path_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) +static void node_id_im_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) { ID *id = WM_drag_get_local_ID_or_import_from_asset(C, drag, 0); - if (id) { RNA_int_set(drop->ptr, "session_uid", int(id->session_uid)); RNA_struct_property_unset(drop->ptr, "filepath"); return; } - - const char *path = WM_drag_get_single_path(drag); - if (path) { - RNA_string_set(drop->ptr, "filepath", path); - RNA_struct_property_unset(drop->ptr, "name"); - return; - } } /* this region dropbox definition */ @@ -958,8 +945,8 @@ static void node_dropboxes() nullptr); WM_dropbox_add(lb, "NODE_OT_add_file", - node_ima_drop_poll, - node_id_path_drop_copy, + node_id_im_drop_poll, + node_id_im_drop_copy, WM_drag_free_imported_drag_ID, nullptr); WM_dropbox_add(lb,