diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 3e0963ef0e0..7f3619e1982 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2277,6 +2277,34 @@ def km_node_editor(params): ("wm.context_toggle", {"type": 'Z', "value": 'PRESS', "alt": True, "shift": True}, {"properties": [("data_path", "space_data.overlay.show_overlays")]}), *_template_items_context_menu("NODE_MT_context_menu", params.context_menu_event), + # Viewer shortcuts. + ("node.viewer_shortcut_get", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("viewer_index", 1)]}), + ("node.viewer_shortcut_set", {"type": 'ONE', "value": 'PRESS', + 'ctrl': True}, {"properties": [("viewer_index", 1)]}), + ("node.viewer_shortcut_get", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("viewer_index", 2)]}), + ("node.viewer_shortcut_set", {"type": 'TWO', "value": 'PRESS', + 'ctrl': True}, {"properties": [("viewer_index", 2)]}), + ("node.viewer_shortcut_get", {"type": 'THREE', "value": 'PRESS'}, {"properties": [("viewer_index", 3)]}), + ("node.viewer_shortcut_set", {"type": 'THREE', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 3)]}), + ("node.viewer_shortcut_get", {"type": 'FOUR', "value": 'PRESS'}, {"properties": [("viewer_index", 4)]}), + ("node.viewer_shortcut_set", {"type": 'FOUR', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 4)]}), + ("node.viewer_shortcut_get", {"type": 'FIVE', "value": 'PRESS'}, {"properties": [("viewer_index", 5)]}), + ("node.viewer_shortcut_set", {"type": 'FIVE', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 5)]}), + ("node.viewer_shortcut_get", {"type": 'SIX', "value": 'PRESS'}, {"properties": [("viewer_index", 6)]}), + ("node.viewer_shortcut_set", {"type": 'SIX', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 6)]}), + ("node.viewer_shortcut_get", {"type": 'SEVEN', "value": 'PRESS'}, {"properties": [("viewer_index", 7)]}), + ("node.viewer_shortcut_set", {"type": 'SEVEN', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 7)]}), + ("node.viewer_shortcut_get", {"type": 'EIGHT', "value": 'PRESS'}, {"properties": [("viewer_index", 8)]}), + ("node.viewer_shortcut_set", {"type": 'EIGHT', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 8)]}), + ("node.viewer_shortcut_get", {"type": 'NINE', "value": 'PRESS'}, {"properties": [("viewer_index", 9)]}), + ("node.viewer_shortcut_set", {"type": 'NINE', "value": 'PRESS', 'ctrl': True}, + {"properties": [("viewer_index", 9)]}), ]) return keymap diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index e994fb3962c..0fdc7d4044c 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -16,6 +16,7 @@ from bpy.props import ( EnumProperty, FloatVectorProperty, StringProperty, + IntProperty, ) from mathutils import ( Vector, @@ -408,6 +409,104 @@ class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator): return {'FINISHED'} +class NODE_OT_viewer_shortcut_set(Operator): + """Create a compositor viewer shortcut for the selected node by pressing ctrl+1,2,..9""" + bl_idname = "node.viewer_shortcut_set" + bl_label = "Fast Preview" + bl_options = {'REGISTER', 'UNDO'} + + viewer_index: IntProperty( + name="Viewer Index", + description="Index corresponding to the shortcut, e.g. number key 1 corresponds to index 1 etc..") + + def get_connected_viewer(self, node): + for out in node.outputs: + for link in out.links: + nv = link.to_node + if nv.type == 'VIEWER': + return nv + return None + + @classmethod + def poll(self, context): + return context.space_data.tree_type == 'CompositorNodeTree' + + def execute(self, context): + nodes = context.space_data.edit_tree.nodes + links = context.space_data.edit_tree.links + selected_nodes = context.selected_nodes + + if len(selected_nodes) == 0: + self.report({'ERROR'}, "Select a node to assign a shortcut") + return {'CANCELLED'} + + fav_node = selected_nodes[0] + + # Only viewer nodes can be set to favorites. However, the user can + # create a new favorite viewer by selecting any node and pressing ctrl+1. + old_active = nodes.active + if fav_node.type == 'VIEWER': + viewer_node = fav_node + else: + viewer_node = self.get_connected_viewer(fav_node) + if not viewer_node: + # Calling link_viewer() if a viewer node is connected will connect the next available socket to the viewer node. + # This behavior is not desired as we want to create a shortcut to the exisiting connected viewer node. + # Therefore link_viewer() is called only when no viewer node is connected. + bpy.ops.node.link_viewer() + viewer_node = self.get_connected_viewer(fav_node) + + if not viewer_node: + self.report({'ERROR'}, "Unable to set shortcut, selected node is not a viewer node or does not support viewing") + return {'CANCELLED'} + + # Use the node active status to enable this viewer node and disable others. + nodes.active = viewer_node + if old_active.type != 'VIEWER': + nodes.active = old_active + + viewer_node.ui_shortcut = self.viewer_index + self.report({'INFO'}, "Assigned shortcut %i to %s" % (self.viewer_index, viewer_node.name)) + + return {'FINISHED'} + + +class NODE_OT_viewer_shortcut_get(Operator): + """Activate a specific compositor viewer node using 1,2,..,9 keys""" + bl_idname = "node.viewer_shortcut_get" + bl_label = "Fast Preview" + bl_options = {'REGISTER', 'UNDO'} + + viewer_index: IntProperty( + name="Viewer Index", + description="Index corresponding to the shortcut, e.g. number key 1 corresponds to index 1 etc..") + + @classmethod + def poll(self, context): + return context.space_data.tree_type == 'CompositorNodeTree' + + def execute(self, context): + nodes = context.space_data.edit_tree.nodes + + # Get viewer node with exisiting shortcut. + viewer_node = None + for n in nodes: + if n.type == 'VIEWER' and n.ui_shortcut == self.viewer_index: + viewer_node = n + + if not viewer_node: + self.report({'INFO'}, "Shortcut %i is not assigned to a Viewer node yet" % self.viewer_index) + return {'CANCELLED'} + + # Use the node active status to enable this viewer node and disable others. + old_active = nodes.active + nodes.active = viewer_node + if old_active.type != "VIEWER": + nodes.active = old_active + + return {'FINISHED'} + + class NODE_FH_image_node(FileHandler): bl_idname = "NODE_FH_image_node" bl_label = "Image node" @@ -438,4 +537,6 @@ classes = ( NODE_OT_interface_item_duplicate, NODE_OT_interface_item_remove, NODE_OT_tree_path_parent, + NODE_OT_viewer_shortcut_get, + NODE_OT_viewer_shortcut_set, ) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 70c170915e6..d4ef8a0638a 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -3674,6 +3674,9 @@ void node_tree_set_output(bNodeTree *ntree) /* same type, exception for viewer */ const bool tnode_is_output = tnode->type_legacy == CMP_NODE_VIEWER; const bool compositor_case = is_compositor && tnode_is_output && node_is_output; + const bool has_same_shortcut = compositor_case && node != tnode && + tnode->custom1 == node->custom1 && + tnode->custom1 != NODE_VIEWER_SHORTCUT_NONE; if (tnode->type_legacy == node->type_legacy || compositor_case) { if (tnode->flag & NODE_DO_OUTPUT) { output++; @@ -3682,6 +3685,9 @@ void node_tree_set_output(bNodeTree *ntree) } } } + if (has_same_shortcut) { + tnode->custom1 = NODE_VIEWER_SHORTCUT_NONE; + } } if (output == 0) { diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index e387969fb5b..562f7fccbad 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -1283,6 +1283,17 @@ static void do_version_color_to_float_conversion(bNodeTree *node_tree) } } +static void do_version_viewer_shortcut(bNodeTree *node_tree) +{ + LISTBASE_FOREACH_MUTABLE (bNode *, node, &node_tree->nodes) { + if (node->type_legacy != CMP_NODE_VIEWER) { + continue; + } + /* custom1 was previously used for Tile Order for the Tiled Compositor. */ + node->custom1 = NODE_VIEWER_SHORTCUT_NONE; + } +} + static bool all_scenes_use(Main *bmain, const blender::Span engines) { if (!bmain->scenes.first) { @@ -5807,6 +5818,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 27)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type == NTREE_COMPOSIT) { + do_version_viewer_shortcut(ntree); + } + } + FOREACH_NODETREE_END; + } + /* Always run this versioning; meshes are written with the legacy format which always needs to * be converted to the new format on file load. Can be moved to a subversion check in a larger * breaking release. */ diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 0d4762a8169..a85a8283cdb 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -3268,6 +3268,36 @@ static void node_draw_extra_info_panel(const bContext &C, } } +static short get_viewer_shortcut_icon(const bNode &node) +{ + BLI_assert(node.is_type("CompositorNodeViewer")); + switch (node.custom1) { + case NODE_VIEWER_SHORTCUT_NONE: + /* No change by default. */ + return node.typeinfo->ui_icon; + case NODE_VIEWER_SHORCTUT_SLOT_1: + return ICON_EVENT_ONEKEY; + case NODE_VIEWER_SHORCTUT_SLOT_2: + return ICON_EVENT_TWOKEY; + case NODE_VIEWER_SHORCTUT_SLOT_3: + return ICON_EVENT_THREEKEY; + case NODE_VIEWER_SHORCTUT_SLOT_4: + return ICON_EVENT_FOURKEY; + case NODE_VIEWER_SHORCTUT_SLOT_5: + return ICON_EVENT_FIVEKEY; + case NODE_VIEWER_SHORCTUT_SLOT_6: + return ICON_EVENT_SIXKEY; + case NODE_VIEWER_SHORCTUT_SLOT_7: + return ICON_EVENT_SEVENKEY; + case NODE_VIEWER_SHORCTUT_SLOT_8: + return ICON_EVENT_EIGHTKEY; + case NODE_VIEWER_SHORCTUT_SLOT_9: + return ICON_EVENT_NINEKEY; + } + + return node.typeinfo->ui_icon; +} + static void node_draw_basis(const bContext &C, TreeDrawContext &tree_draw_ctx, const View2D &v2d, @@ -3461,6 +3491,25 @@ static void node_draw_basis(const bContext &C, but, node_toggle_button_cb, POINTER_FROM_INT(node.identifier), (void *)operator_idname); UI_block_emboss_set(&block, UI_EMBOSS); } + /* Viewer node shortcuts. */ + if (node.is_type("CompositorNodeViewer")) { + short shortcut_icon = get_viewer_shortcut_icon(node); + iconofs -= iconbutw; + UI_block_emboss_set(&block, UI_EMBOSS_NONE); + uiDefIconBut(&block, + UI_BTYPE_BUT, + 0, + shortcut_icon, + iconofs, + rct.ymax - NODE_DY, + iconbutw, + UI_UNIT_Y, + nullptr, + 0, + 0, + ""); + UI_block_emboss_set(&block, UI_EMBOSS); + } node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 92ebc271c8d..a57a9c0317d 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -739,7 +739,7 @@ static int view_socket(const bContext &C, /* Try to find a viewer that is already active. */ for (bNode *node : btree.all_nodes()) { if (is_viewer_node(*node)) { - if (node->flag & NODE_DO_OUTPUT) { + if (node->flag & NODE_DO_OUTPUT && node->custom1 == NODE_VIEWER_SHORTCUT_NONE) { viewer_node = node; break; } @@ -759,7 +759,7 @@ static int view_socket(const bContext &C, if (viewer_node == nullptr) { for (bNode *node : btree.all_nodes()) { - if (is_viewer_node(*node)) { + if (is_viewer_node(*node) && node->custom1 == NODE_VIEWER_SHORTCUT_NONE) { viewer_node = node; break; } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 89bb7176dac..9ce2171a8ea 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -353,6 +353,21 @@ typedef struct bNodePanelState { #endif } bNodePanelState; +typedef enum eViewerNodeShortcut { + NODE_VIEWER_SHORTCUT_NONE = 0, + /* Users can set custom keys to shortcuts, + * but shortcuts should always be referred to as enums. */ + NODE_VIEWER_SHORCTUT_SLOT_1 = 1, + NODE_VIEWER_SHORCTUT_SLOT_2 = 2, + NODE_VIEWER_SHORCTUT_SLOT_3 = 3, + NODE_VIEWER_SHORCTUT_SLOT_4 = 4, + NODE_VIEWER_SHORCTUT_SLOT_5 = 5, + NODE_VIEWER_SHORCTUT_SLOT_6 = 6, + NODE_VIEWER_SHORCTUT_SLOT_7 = 7, + NODE_VIEWER_SHORCTUT_SLOT_8 = 8, + NODE_VIEWER_SHORCTUT_SLOT_9 = 9 +} eViewerNodeShortcut; + typedef enum NodeWarningPropagation { NODE_WARNING_PROPAGATION_ALL = 0, NODE_WARNING_PROPAGATION_NONE = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index a831600ef09..f3d0553a21b 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -1356,6 +1356,20 @@ static void rna_NodeTree_active_node_set(PointerRNA *ptr, } } +static void rna_Node_shortcut_node_set(PointerRNA *ptr, int value) +{ + bNode *curr_node = static_cast(ptr->data); + bNodeTree &ntree = curr_node->owner_tree(); + + /* Avoid having two nodes with the same shortcut. */ + for (bNode *node : ntree.all_nodes()) { + if (node->is_type("CompositorNodeViewer") && node->custom1 == value) { + node->custom1 = NODE_VIEWER_SHORTCUT_NONE; + } + } + curr_node->custom1 = value; +} + static bNodeLink *rna_NodeTree_link_new(bNodeTree *ntree, Main *bmain, ReportList *reports, @@ -9125,6 +9139,14 @@ static void def_cmp_viewer(BlenderRNA * /*brna*/, StructRNA *srna) "Use Alpha", "Colors are treated alpha premultiplied, or colors output straight (alpha gets set to 1)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "ui_shortcut", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "custom1"); + RNA_def_property_int_funcs(prop, nullptr, "rna_Node_shortcut_node_set", nullptr); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); + RNA_def_property_int_default(prop, NODE_VIEWER_SHORTCUT_NONE); + RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, nullptr); } static void def_cmp_composite(BlenderRNA * /*brna*/, StructRNA *srna) diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index 3dd17097940..7aea35db2eb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -40,6 +40,7 @@ static void node_composit_init_viewer(bNodeTree * /*ntree*/, bNode *node) ImageUser *iuser = MEM_cnew(__func__); node->storage = iuser; iuser->sfra = 1; + node->custom1 = NODE_VIEWER_SHORTCUT_NONE; node->id = (ID *)BKE_image_ensure_viewer(G.main, IMA_TYPE_COMPOSITE, "Viewer Node"); }