From 0c585a1b8afd35c8cd96dfedde388cf74e8e994f Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 29 Apr 2024 19:50:11 +0200 Subject: [PATCH] Geometry Nodes: move socket items ui and operators from Python to C++ This has some benefits: * Nodes with dynamic socket amounts can remain more self-contained (2 fewer files to edit with this patch). * It's easier to reuse existing C++ code, reducing redundancy. One new thing I'm doing here is to define operators in node files. It seems reasonable to register operators that belong to a node together with that node. Without this, code spreads out further than necessary without any real benefit. This patch affects the simulation zone, repeat zone, bake node and index switch node. The UI is slightly affected too. Since we had the UI defined in Python before, it wasn't possible to integrate it into the node properties panel. That is possible now and looks better anyway. The previous UI was an artifact of technical limitations. Pull Request: https://projects.blender.org/blender/blender/pulls/121178 --- .../startup/bl_operators/geometry_nodes.py | 233 ---------- scripts/startup/bl_ui/space_node.py | 245 ----------- source/blender/blenkernel/BKE_node.hh | 7 + source/blender/editors/space_node/node_ops.cc | 7 + source/blender/nodes/CMakeLists.txt | 1 + source/blender/nodes/NOD_socket_items_ops.hh | 233 ++++++++++ .../nodes/geometry/nodes/node_geo_bake.cc | 107 +++++ .../geometry/nodes/node_geo_index_switch.cc | 35 ++ .../nodes/geometry/nodes/node_geo_repeat.cc | 127 ++++++ .../geometry/nodes/node_geo_simulation.cc | 409 ++++++++++++------ 10 files changed, 789 insertions(+), 615 deletions(-) create mode 100644 source/blender/nodes/NOD_socket_items_ops.hh diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index a0ea14c68cb..c92bd04b7b7 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -361,242 +361,9 @@ class ZoneOperator: return True -class NodeOperator: - @classmethod - def get_node(cls, context): - node = context.active_node - if node is None: - return None - if node.bl_idname == cls.node_type: - return node - - @classmethod - def poll(cls, context): - space = context.space_data - # Needs active node editor and a tree. - if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library: - return False - node = cls.get_node(context) - if node is None: - return False - return True - - -class SocketItemAddOperator: - items_name = None - active_index_name = None - default_socket_type = 'GEOMETRY' - - def execute(self, context): - node = self.get_node(context) - items = getattr(node, self.items_name) - # Remember index to move the item. - old_active_index = getattr(node, self.active_index_name) - if 0 <= old_active_index < len(items): - old_active_item = items[old_active_index] - dst_index = old_active_index + 1 - dst_type = old_active_item.socket_type - dst_name = old_active_item.name - else: - dst_index = len(items) - dst_type = self.default_socket_type - # Empty name so it is based on the type. - dst_name = "" - items.new(dst_type, dst_name) - items.move(len(items) - 1, dst_index) - setattr(node, self.active_index_name, dst_index) - return {'FINISHED'} - - -class SocketItemRemoveOperator: - items_name = None - active_index_name = None - - def execute(self, context): - node = self.get_node(context) - items = getattr(node, self.items_name) - old_active_index = getattr(node, self.active_index_name) - - if 0 <= old_active_index < len(items): - items.remove(items[old_active_index]) - - return {'FINISHED'} - - -class SocketMoveItemOperator: - items_name = None - active_index_name = None - - direction: EnumProperty( - name="Direction", - items=[('UP', "Up", ""), ('DOWN', "Down", "")], - default='UP', - ) - - def execute(self, context): - node = self.get_node(context) - items = getattr(node, self.items_name) - old_active_index = getattr(node, self.active_index_name) - - if self.direction == 'UP' and old_active_index > 0: - items.move(old_active_index, old_active_index - 1) - setattr(node, self.active_index_name, old_active_index - 1) - elif self.direction == 'DOWN' and old_active_index < len(items) - 1: - items.move(old_active_index, old_active_index + 1) - setattr(node, self.active_index_name, old_active_index + 1) - - return {'FINISHED'} - - -class SimulationZoneOperator(ZoneOperator): - input_node_type = 'GeometryNodeSimulationInput' - output_node_type = 'GeometryNodeSimulationOutput' - - items_name = "state_items" - active_index_name = "active_index" - - -class SimulationZoneItemAddOperator(SimulationZoneOperator, SocketItemAddOperator, Operator): - """Add a state item to the simulation zone""" - bl_idname = "node.simulation_zone_item_add" - bl_label = "Add State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class SimulationZoneItemRemoveOperator(SimulationZoneOperator, SocketItemRemoveOperator, Operator): - """Remove a state item from the simulation zone""" - bl_idname = "node.simulation_zone_item_remove" - bl_label = "Remove State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class SimulationZoneItemMoveOperator(SimulationZoneOperator, SocketMoveItemOperator, Operator): - """Move a simulation state item up or down in the list""" - bl_idname = "node.simulation_zone_item_move" - bl_label = "Move State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class RepeatZoneOperator(ZoneOperator): - input_node_type = 'GeometryNodeRepeatInput' - output_node_type = 'GeometryNodeRepeatOutput' - - items_name = "repeat_items" - active_index_name = "active_index" - - -class RepeatZoneItemAddOperator(RepeatZoneOperator, SocketItemAddOperator, Operator): - """Add a repeat item to the repeat zone""" - bl_idname = "node.repeat_zone_item_add" - bl_label = "Add Repeat Item" - bl_options = {'REGISTER', 'UNDO'} - - -class RepeatZoneItemRemoveOperator(RepeatZoneOperator, SocketItemRemoveOperator, Operator): - """Remove a repeat item from the repeat zone""" - bl_idname = "node.repeat_zone_item_remove" - bl_label = "Remove Repeat Item" - bl_options = {'REGISTER', 'UNDO'} - - -class RepeatZoneItemMoveOperator(RepeatZoneOperator, SocketMoveItemOperator, Operator): - """Move a repeat item up or down in the list""" - bl_idname = "node.repeat_zone_item_move" - bl_label = "Move Repeat Item" - bl_options = {'REGISTER', 'UNDO'} - - -class BakeNodeOperator(NodeOperator): - node_type = 'GeometryNodeBake' - - items_name = "bake_items" - active_index_name = "active_index" - - -class BakeNodeItemAddOperator(BakeNodeOperator, SocketItemAddOperator, Operator): - """Add a bake item to the bake node""" - bl_idname = "node.bake_node_item_add" - bl_label = "Add Bake Item" - bl_options = {'REGISTER', 'UNDO'} - - -class BakeNodeItemRemoveOperator(BakeNodeOperator, SocketItemRemoveOperator, Operator): - """Remove a bake item from the bake node""" - bl_idname = "node.bake_node_item_remove" - bl_label = "Remove Bake Item" - bl_options = {'REGISTER', 'UNDO'} - - -class BakeNodeItemMoveOperator(BakeNodeOperator, SocketMoveItemOperator, Operator): - """Move a bake item up or down in the list""" - bl_idname = "node.bake_node_item_move" - bl_label = "Move Bake Item" - bl_options = {'REGISTER', 'UNDO'} - - -def _editable_tree_with_active_node_type(context, node_type): - space = context.space_data - # Needs active node editor and a tree. - if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library: - return False - node = context.active_node - if node is None or node.bl_idname != node_type: - return False - return True - - -class IndexSwitchItemAddOperator(Operator): - """Add an item to the index switch""" - bl_idname = "node.index_switch_item_add" - bl_label = "Add Item" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return _editable_tree_with_active_node_type(context, 'GeometryNodeIndexSwitch') - - def execute(self, context): - node = context.active_node - node.index_switch_items.new() - return {'FINISHED'} - - -class IndexSwitchItemRemoveOperator(Operator): - """Remove an item from the index switch""" - bl_idname = "node.index_switch_item_remove" - bl_label = "Remove Item" - bl_options = {'REGISTER', 'UNDO'} - - index: IntProperty( - name="Index", - description="Index of item to remove", - ) - - @classmethod - def poll(cls, context): - return _editable_tree_with_active_node_type(context, 'GeometryNodeIndexSwitch') - - def execute(self, context): - node = context.active_node - items = node.index_switch_items - items.remove(items[self.index]) - return {'FINISHED'} - - classes = ( NewGeometryNodesModifier, NewGeometryNodeTreeAssign, NewGeometryNodeGroupTool, MoveModifierToNodes, - SimulationZoneItemAddOperator, - SimulationZoneItemRemoveOperator, - SimulationZoneItemMoveOperator, - RepeatZoneItemAddOperator, - RepeatZoneItemRemoveOperator, - RepeatZoneItemMoveOperator, - BakeNodeItemAddOperator, - BakeNodeItemRemoveOperator, - BakeNodeItemMoveOperator, - IndexSwitchItemAddOperator, - IndexSwitchItemRemoveOperator, ) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 57f65c3d5f9..82ca0d9348e 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -991,244 +991,6 @@ class NODE_PT_node_tree_properties(Panel): col.prop(group, "is_tool") -def draw_socket_item_in_list(uilist, layout, item, icon): - if uilist.layout_type in {'DEFAULT', 'COMPACT'}: - row = layout.row(align=True) - row.template_node_socket(color=item.color) - row.prop(item, "name", text="", emboss=False, icon_value=icon) - elif uilist.layout_type == 'GRID': - layout.alignment = 'CENTER' - layout.template_node_socket(color=item.color) - - -class NODE_UL_simulation_zone_items(UIList): - def draw_item(self, context, layout, _data, item, icon, _active_data, _active_propname, _index): - draw_socket_item_in_list(self, layout, item, icon) - - -class NODE_PT_simulation_zone_items(Panel): - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_category = "Node" - bl_label = "Simulation State" - - input_node_type = 'GeometryNodeSimulationInput' - output_node_type = 'GeometryNodeSimulationOutput' - - @classmethod - def get_output_node(cls, context): - node = context.active_node - if node.bl_idname == cls.input_node_type: - return node.paired_output - if node.bl_idname == cls.output_node_type: - return node - - @classmethod - def poll(cls, context): - snode = context.space_data - if snode is None: - return False - node = context.active_node - if node is None or node.bl_idname not in [cls.input_node_type, cls.output_node_type]: - return False - if cls.get_output_node(context) is None: - return False - return True - - def draw(self, context): - layout = self.layout - - output_node = self.get_output_node(context) - - split = layout.row() - - split.template_list( - "NODE_UL_simulation_zone_items", - "", - output_node, - "state_items", - output_node, - "active_index") - - ops_col = split.column() - - add_remove_col = ops_col.column(align=True) - add_remove_col.operator("node.simulation_zone_item_add", icon='ADD', text="") - add_remove_col.operator("node.simulation_zone_item_remove", icon='REMOVE', text="") - - ops_col.separator() - - up_down_col = ops_col.column(align=True) - props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_UP', text="") - props.direction = 'UP' - props = up_down_col.operator("node.simulation_zone_item_move", icon='TRIA_DOWN', text="") - props.direction = 'DOWN' - - active_item = output_node.active_item - if active_item is not None: - layout.use_property_split = True - layout.use_property_decorate = False - layout.prop(active_item, "socket_type") - if active_item.socket_type in {'VECTOR', 'INT', 'BOOLEAN', 'FLOAT', 'RGBA', 'ROTATION'}: - layout.prop(active_item, "attribute_domain") - - -class NODE_UL_repeat_zone_items(UIList): - def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): - draw_socket_item_in_list(self, layout, item, icon) - - -class NODE_PT_repeat_zone_items(Panel): - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_category = "Node" - bl_label = "Repeat" - - input_node_type = 'GeometryNodeRepeatInput' - output_node_type = 'GeometryNodeRepeatOutput' - - @classmethod - def get_output_node(cls, context): - node = context.active_node - if node.bl_idname == cls.input_node_type: - return node.paired_output - if node.bl_idname == cls.output_node_type: - return node - return None - - @classmethod - def poll(cls, context): - snode = context.space_data - if snode is None: - return False - node = context.active_node - if node is None or node.bl_idname not in {cls.input_node_type, cls.output_node_type}: - return False - if cls.get_output_node(context) is None: - return False - return True - - def draw(self, context): - layout = self.layout - output_node = self.get_output_node(context) - split = layout.row() - split.template_list( - "NODE_UL_repeat_zone_items", - "", - output_node, - "repeat_items", - output_node, - "active_index") - - ops_col = split.column() - - add_remove_col = ops_col.column(align=True) - add_remove_col.operator("node.repeat_zone_item_add", icon='ADD', text="") - add_remove_col.operator("node.repeat_zone_item_remove", icon='REMOVE', text="") - - ops_col.separator() - - up_down_col = ops_col.column(align=True) - props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_UP', text="") - props.direction = 'UP' - props = up_down_col.operator("node.repeat_zone_item_move", icon='TRIA_DOWN', text="") - props.direction = 'DOWN' - - active_item = output_node.active_item - if active_item is not None: - layout.use_property_split = True - layout.use_property_decorate = False - layout.prop(active_item, "socket_type") - - layout.prop(output_node, "inspection_index") - - -class NODE_UL_bake_node_items(UIList): - def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): - draw_socket_item_in_list(self, layout, item, icon) - - -class NODE_PT_bake_node_items(bpy.types.Panel): - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_category = "Node" - bl_label = "Bake Items" - - @classmethod - def poll(cls, context): - snode = context.space_data - if snode is None: - return False - node = context.active_node - if node is None: - return False - if node.bl_idname != "GeometryNodeBake": - return False - return True - - def draw(self, context): - layout = self.layout - node = context.active_node - split = layout.row() - split.template_list( - "NODE_UL_bake_node_items", - "", - node, - "bake_items", - node, - "active_index") - - ops_col = split.column() - - add_remove_col = ops_col.column(align=True) - add_remove_col.operator("node.bake_node_item_add", icon='ADD', text="") - add_remove_col.operator("node.bake_node_item_remove", icon='REMOVE', text="") - - ops_col.separator() - - up_down_col = ops_col.column(align=True) - props = up_down_col.operator("node.bake_node_item_move", icon='TRIA_UP', text="") - props.direction = 'UP' - props = up_down_col.operator("node.bake_node_item_move", icon='TRIA_DOWN', text="") - props.direction = 'DOWN' - - active_item = node.active_item - if active_item is not None: - layout.use_property_split = True - layout.use_property_decorate = False - layout.prop(active_item, "socket_type") - if active_item.socket_type in {'VECTOR', 'INT', 'BOOLEAN', 'FLOAT', 'RGBA', 'ROTATION'}: - layout.prop(active_item, "attribute_domain") - layout.prop(active_item, "is_attribute") - - -class NODE_PT_index_switch_node_items(Panel): - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_category = "Node" - bl_label = "Index Switch" - - @classmethod - def poll(cls, context): - snode = context.space_data - if snode is None: - return False - node = context.active_node - if node is None or node.bl_idname != 'GeometryNodeIndexSwitch': - return False - return True - - def draw(self, context): - layout = self.layout - node = context.active_node - layout.operator("node.index_switch_item_add", icon='ADD', text="Add Item") - col = layout.column() - for i, item in enumerate(node.index_switch_items): - row = col.row() - row.label(text=node.inputs[i + 1].name) - row.operator("node.index_switch_item_remove", icon='REMOVE', text="").index = i - - class NODE_UL_enum_definition_items(bpy.types.UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): layout.prop(item, "name", text="", emboss=False, icon_value=icon) @@ -1349,13 +1111,6 @@ classes = ( NODE_PT_quality, NODE_PT_annotation, NODE_PT_overlay, - NODE_UL_simulation_zone_items, - NODE_PT_simulation_zone_items, - NODE_UL_repeat_zone_items, - NODE_UL_bake_node_items, - NODE_PT_bake_node_items, - NODE_PT_index_switch_node_items, - NODE_PT_repeat_zone_items, NODE_UL_enum_definition_items, NODE_PT_menu_switch_items, NODE_PT_active_node_properties, diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index a96569a0a21..2f40e9ccf36 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -349,6 +349,13 @@ struct bNodeType { /** Get extra information that is drawn next to the node. */ NodeExtraInfoFunction get_extra_info; + /** + * Registers operators that are specific to this node. This allows nodes to be more + * self-contained compared to the alternative to registering all operators in a more central + * place. + */ + void (*register_operators)(); + /** True when the node cannot be muted. */ bool no_muting; /** True when the node still works but it's usage is discouraged. */ diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index 6f2970fe45a..364790c3674 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -106,6 +106,13 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_cryptomatte_layer_add); WM_operatortype_append(NODE_OT_cryptomatte_layer_remove); + + NODE_TYPES_BEGIN (ntype) { + if (ntype->register_operators) { + ntype->register_operators(); + } + } + NODE_TYPES_END; } void node_keymap(wmKeyConfig *keyconf) diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 1e3365e27d7..4afaf28e725 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -111,6 +111,7 @@ set(SRC NOD_socket_declarations.hh NOD_socket_declarations_geometry.hh NOD_socket_items.hh + NOD_socket_items_ops.hh NOD_socket_search_link.hh NOD_static_types.h NOD_texture.h diff --git a/source/blender/nodes/NOD_socket_items_ops.hh b/source/blender/nodes/NOD_socket_items_ops.hh new file mode 100644 index 00000000000..c64c05085c9 --- /dev/null +++ b/source/blender/nodes/NOD_socket_items_ops.hh @@ -0,0 +1,233 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "NOD_socket_items.hh" + +#include "WM_api.hh" + +#include "BKE_context.hh" +#include "BKE_node_tree_update.hh" +#include "BKE_node_tree_zones.hh" + +#include "RNA_access.hh" +#include "RNA_define.hh" +#include "RNA_prototypes.h" + +#include "ED_node.hh" + +#include "DNA_space_types.h" + +namespace blender::nodes::socket_items::ops { + +inline PointerRNA get_active_node_to_operate_on(bContext *C, const int node_type) +{ + SpaceNode *snode = CTX_wm_space_node(C); + if (!snode) { + return PointerRNA_NULL; + } + if (!snode->edittree) { + return PointerRNA_NULL; + } + if (ID_IS_LINKED(snode->edittree)) { + return PointerRNA_NULL; + } + const bke::bNodeTreeZones *zones = snode->edittree->zones(); + if (!zones) { + return PointerRNA_NULL; + } + bNode *active_node = nodeGetActive(snode->edittree); + if (!active_node) { + return PointerRNA_NULL; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(active_node->identifier); + if (zone->input_node == active_node) { + /* Assume the data is generally stored on the output and not the input node. */ + active_node = const_cast(zone->output_node); + } + if (active_node->type != node_type) { + return PointerRNA_NULL; + } + return RNA_pointer_create(&snode->edittree->id, &RNA_Node, active_node); +} + +inline void update_after_node_change(bContext *C, const PointerRNA node_ptr) +{ + bNode *node = static_cast(node_ptr.data); + bNodeTree *ntree = reinterpret_cast(node_ptr.owner_id); + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, CTX_data_main(C), ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +template inline bool editable_node_active_poll(bContext *C) +{ + return get_active_node_to_operate_on(C, Accessor::node_type).data != nullptr; +} + +template +inline void remove_item(wmOperatorType *ot, + const char *name, + const char *idname, + const char *description) +{ + ot->name = name; + ot->idname = idname; + ot->description = description; + ot->poll = editable_node_active_poll; + + ot->exec = [](bContext *C, wmOperator * /*op*/) -> int { + PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type); + bNode &node = *static_cast(node_ptr.data); + SocketItemsRef ref = Accessor::get_items_from_node(node); + dna::array::remove_index( + ref.items, ref.items_num, ref.active_index, *ref.active_index, Accessor::destruct_item); + + update_after_node_change(C, node_ptr); + return OPERATOR_FINISHED; + }; +} + +template +inline void remove_item_by_index(wmOperatorType *ot, + const char *name, + const char *idname, + const char *description) +{ + ot->name = name; + ot->idname = idname; + ot->description = description; + ot->poll = editable_node_active_poll; + + ot->exec = [](bContext *C, wmOperator *op) -> int { + PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type); + bNode &node = *static_cast(node_ptr.data); + const int index_to_remove = RNA_int_get(op->ptr, "index"); + SocketItemsRef ref = Accessor::get_items_from_node(node); + dna::array::remove_index( + ref.items, ref.items_num, ref.active_index, index_to_remove, Accessor::destruct_item); + + update_after_node_change(C, node_ptr); + return OPERATOR_FINISHED; + }; + + RNA_def_int(ot->srna, "index", 0, 0, INT32_MAX, "Index", "Index to remove", 0, INT32_MAX); +} + +template +inline void add_item_with_name_and_type(wmOperatorType *ot, + const char *name, + const char *idname, + const char *description) +{ + static_assert(Accessor::has_type); + static_assert(Accessor::has_name); + + ot->name = name; + ot->idname = idname; + ot->description = description; + ot->poll = editable_node_active_poll; + + ot->exec = [](bContext *C, wmOperator * /*op*/) -> int { + PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type); + bNode &node = *static_cast(node_ptr.data); + SocketItemsRef ref = Accessor::get_items_from_node(node); + const int old_active_index = *ref.active_index; + + eNodeSocketDatatype socket_type; + std::string name; + int dst_index; + if (old_active_index >= 0 && old_active_index < *ref.items_num) { + dst_index = old_active_index + 1; + const typename Accessor::ItemT &active_item = (*ref.items)[old_active_index]; + socket_type = eNodeSocketDatatype(active_item.socket_type); + name = active_item.name; + } + else { + dst_index = *ref.items_num; + socket_type = SOCK_GEOMETRY; + /* Empty name so it is based on the type. */ + name = ""; + } + add_item_with_socket_and_name(node, socket_type, name.c_str()); + dna::array::move_index(*ref.items, *ref.items_num, *ref.items_num - 1, dst_index); + *ref.active_index = dst_index; + + update_after_node_change(C, node_ptr); + return OPERATOR_FINISHED; + }; +} + +template +inline void add_item(wmOperatorType *ot, + const char *name, + const char *idname, + const char *description) +{ + static_assert(!Accessor::has_type); + static_assert(!Accessor::has_name); + + ot->name = name; + ot->idname = idname; + ot->description = description; + ot->poll = editable_node_active_poll; + + ot->exec = [](bContext *C, wmOperator * /*op*/) -> int { + PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type); + bNode &node = *static_cast(node_ptr.data); + socket_items::add_item(node); + + update_after_node_change(C, node_ptr); + return OPERATOR_FINISHED; + }; +} + +enum class MoveDirection { + Up = 0, + Down = 1, +}; + +template +inline void move_item(wmOperatorType *ot, + const char *name, + const char *idname, + const char *description) +{ + ot->name = name; + ot->idname = idname; + ot->description = description; + ot->poll = editable_node_active_poll; + + ot->exec = [](bContext *C, wmOperator *op) -> int { + PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type); + bNode &node = *static_cast(node_ptr.data); + const MoveDirection direction = MoveDirection(RNA_enum_get(op->ptr, "direction")); + + SocketItemsRef ref = Accessor::get_items_from_node(node); + const int old_active_index = *ref.active_index; + if (direction == MoveDirection::Up && old_active_index > 0) { + dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index - 1); + *ref.active_index -= 1; + } + else if (direction == MoveDirection::Down && old_active_index < *ref.items_num - 1) { + dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index + 1); + *ref.active_index += 1; + } + + update_after_node_change(C, node_ptr); + return OPERATOR_FINISHED; + }; + + static const EnumPropertyItem direction_items[] = { + {int(MoveDirection::Up), "UP", 0, "Up", ""}, + {int(MoveDirection::Down), "DOWN", 0, "Down", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_enum(ot->srna, "direction", direction_items, 0, "Direction", "Move direction"); +} + +} // namespace blender::nodes::socket_items::ops diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 35048ad0198..f2e35c11b1c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -7,6 +7,7 @@ #include "NOD_geo_bake.hh" #include "NOD_node_extra_info.hh" #include "NOD_rna_define.hh" +#include "NOD_socket_items_ops.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -114,6 +115,109 @@ static const CPPType &get_item_cpp_type(const eNodeSocketDatatype socket_type) return *typeinfo->geometry_nodes_cpp_type; } +static void draw_bake_item(uiList * /*ui_list*/, + const bContext *C, + uiLayout *layout, + PointerRNA * /*idataptr*/, + PointerRNA *itemptr, + int /*icon*/, + PointerRNA * /*active_dataptr*/, + const char * /*active_propname*/, + int /*index*/, + int /*flt_flag*/) +{ + uiLayout *row = uiLayoutRow(layout, true); + float4 color; + RNA_float_get_array(itemptr, "color", color); + uiTemplateNodeSocket(row, const_cast(C), color); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE); +} + +static void draw_bake_items(const bContext *C, uiLayout *layout, PointerRNA node_ptr) +{ + static const uiListType *bake_items_list = []() { + uiListType *list = MEM_cnew(__func__); + STRNCPY(list->idname, "DATA_UL_bake_node_items"); + list->draw_item = draw_bake_item; + WM_uilisttype_add(list); + return list; + }(); + + bNode &node = *static_cast(node_ptr.data); + + if (uiLayout *panel = uiLayoutPanel(C, layout, "bake_items", false, TIP_("Bake Items"))) { + uiLayout *row = uiLayoutRow(panel, false); + uiTemplateList(row, + C, + bake_items_list->idname, + "", + &node_ptr, + "bake_items", + &node_ptr, + "active_index", + nullptr, + 3, + 5, + UILST_LAYOUT_DEFAULT, + 0, + UI_TEMPLATE_LIST_FLAG_NONE); + + { + uiLayout *ops_col = uiLayoutColumn(row, false); + { + uiLayout *add_remove_col = uiLayoutColumn(ops_col, true); + uiItemO(add_remove_col, "", ICON_ADD, "node.bake_node_item_add"); + uiItemO(add_remove_col, "", ICON_REMOVE, "node.bake_node_item_remove"); + } + { + uiLayout *up_down_col = uiLayoutColumn(ops_col, true); + uiItemEnumO(up_down_col, "node.bake_node_item_move", "", ICON_TRIA_UP, "direction", 0); + uiItemEnumO(up_down_col, "node.bake_node_item_move", "", ICON_TRIA_DOWN, "direction", 1); + } + } + + NodeGeometryBake &storage = node_storage(node); + if (storage.active_index >= 0 && storage.active_index < storage.items_num) { + NodeGeometryBakeItem &active_item = storage.items[storage.active_index]; + PointerRNA item_ptr = RNA_pointer_create( + node_ptr.owner_id, BakeItemsAccessor::item_srna, &active_item); + uiLayoutSetPropSep(panel, true); + uiLayoutSetPropDecorate(panel, false); + uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE); + if (socket_type_supports_fields(eNodeSocketDatatype(active_item.socket_type))) { + uiItemR(panel, &item_ptr, "attribute_domain", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(panel, &item_ptr, "is_attribute", UI_ITEM_NONE, nullptr, ICON_NONE); + } + } + } +} + +static void NODE_OT_bake_node_item_remove(wmOperatorType *ot) +{ + socket_items::ops::remove_item( + ot, "Remove Bake Item", __func__, "Remove active bake item"); +} + +static void NODE_OT_bake_node_item_add(wmOperatorType *ot) +{ + socket_items::ops::add_item_with_name_and_type( + ot, "Add Bake Item", __func__, "Add bake item"); +} + +static void NODE_OT_bake_node_item_move(wmOperatorType *ot) +{ + socket_items::ops::move_item( + ot, "Move Bake Item", __func__, "Move active bake item"); +} + +static void node_operators() +{ + WM_operatortype_append(NODE_OT_bake_node_item_add); + WM_operatortype_append(NODE_OT_bake_node_item_remove); + WM_operatortype_append(NODE_OT_bake_node_item_move); +} + static bake::BakeSocketConfig make_bake_socket_config(const Span bake_items) { bake::BakeSocketConfig config; @@ -563,6 +667,8 @@ static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr) static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) { + draw_bake_items(C, layout, *ptr); + BakeDrawContext ctx; const bNode &node = *static_cast(ptr->data); if (!get_bake_draw_context(C, node, ctx)) { @@ -628,6 +734,7 @@ static void node_register() ntype.insert_link = node_insert_link; ntype.draw_buttons_ex = node_layout_ex; ntype.get_extra_info = node_extra_info; + ntype.register_operators = node_operators; node_type_storage(&ntype, "NodeGeometryBake", node_free_storage, node_copy_storage); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc index e189913cdd9..f5601ad31f9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc @@ -10,6 +10,7 @@ #include "NOD_geo_index_switch.hh" #include "NOD_rna_define.hh" #include "NOD_socket.hh" +#include "NOD_socket_items_ops.hh" #include "NOD_socket_search_link.hh" #include "RNA_enum_types.hh" @@ -65,6 +66,38 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); } +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) +{ + bNode &node = *static_cast(ptr->data); + NodeIndexSwitch &storage = node_storage(node); + if (uiLayout *panel = uiLayoutPanel(C, layout, "index_switch_items", false, TIP_("Items"))) { + uiItemO(panel, "Add Item", ICON_ADD, "node.index_switch_item_add"); + uiLayout *col = uiLayoutColumn(panel, false); + for (const int i : IndexRange(storage.items_num)) { + uiLayout *row = uiLayoutRow(col, false); + uiItemL(row, node.input_socket(i + 1).name, ICON_NONE); + uiItemIntO(row, "", ICON_REMOVE, "node.index_switch_item_remove", "index", i); + } + } +} + +static void NODE_OT_index_switch_item_add(wmOperatorType *ot) +{ + socket_items::ops::add_item(ot, "Add Item", __func__, "Add bake item"); +} + +static void NODE_OT_index_switch_item_remove(wmOperatorType *ot) +{ + socket_items::ops::remove_item_by_index( + ot, "Remove Item", __func__, "Remove an item from the index switch"); +} + +static void node_operators() +{ + WM_operatortype_append(NODE_OT_index_switch_item_add); + WM_operatortype_append(NODE_OT_index_switch_item_remove); +} + static void node_init(bNodeTree * /*tree*/, bNode *node) { NodeIndexSwitch *data = MEM_cnew(__func__); @@ -344,6 +377,8 @@ static void register_node() node_type_storage(&ntype, "NodeIndexSwitch", node_free_storage, node_copy_storage); ntype.gather_link_search_ops = node_gather_link_searches; ntype.draw_buttons = node_layout; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; nodeRegisterType(&ntype); node_rna(ntype.rna_ext.srna); diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc index 8a267c29607..8519d107ca8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc @@ -7,17 +7,116 @@ #include "NOD_geo_repeat.hh" #include "NOD_socket.hh" +#include "NOD_socket_items_ops.hh" #include "BLO_read_write.hh" #include "BLI_string_utils.hh" +#include "RNA_access.hh" #include "RNA_prototypes.h" +#include "BKE_screen.hh" + +#include "WM_api.hh" + +#include "UI_interface.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_repeat_cc { +static void draw_repeat_state_item(uiList * /*ui_list*/, + const bContext *C, + uiLayout *layout, + PointerRNA * /*idataptr*/, + PointerRNA *itemptr, + int /*icon*/, + PointerRNA * /*active_dataptr*/, + const char * /*active_propname*/, + int /*index*/, + int /*flt_flag*/) +{ + uiLayout *row = uiLayoutRow(layout, true); + float4 color; + RNA_float_get_array(itemptr, "color", color); + uiTemplateNodeSocket(row, const_cast(C), color); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE); +} + +/** Shared between repeat zone input and output node. */ +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr) +{ + bNodeTree &ntree = *reinterpret_cast(current_node_ptr->owner_id); + bNode *current_node = static_cast(current_node_ptr->data); + + const bke::bNodeTreeZones *zones = ntree.zones(); + if (!zones) { + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier); + if (!zone) { + return; + } + if (!zone->output_node) { + return; + } + bNode &output_node = const_cast(*zone->output_node); + PointerRNA output_node_ptr = RNA_pointer_create( + current_node_ptr->owner_id, &RNA_Node, &output_node); + + static const uiListType *state_items_list = []() { + uiListType *list = MEM_cnew(__func__); + STRNCPY(list->idname, "DATA_UL_repeat_zone_state"); + list->draw_item = draw_repeat_state_item; + WM_uilisttype_add(list); + return list; + }(); + + if (uiLayout *panel = uiLayoutPanel(C, layout, "repeat_items", false, TIP_("Repeat Items"))) { + uiLayout *row = uiLayoutRow(panel, false); + uiTemplateList(row, + C, + state_items_list->idname, + "", + &output_node_ptr, + "repeat_items", + &output_node_ptr, + "active_index", + nullptr, + 3, + 5, + UILST_LAYOUT_DEFAULT, + 0, + UI_TEMPLATE_LIST_FLAG_NONE); + { + uiLayout *ops_col = uiLayoutColumn(row, false); + { + uiLayout *add_remove_col = uiLayoutColumn(ops_col, true); + uiItemO(add_remove_col, "", ICON_ADD, "node.repeat_zone_item_add"); + uiItemO(add_remove_col, "", ICON_REMOVE, "node.repeat_zone_item_remove"); + } + { + uiLayout *up_down_col = uiLayoutColumn(ops_col, true); + uiItemEnumO(up_down_col, "node.repeat_zone_item_move", "", ICON_TRIA_UP, "direction", 0); + uiItemEnumO(up_down_col, "node.repeat_zone_item_move", "", ICON_TRIA_DOWN, "direction", 1); + } + } + auto &storage = *static_cast(output_node.storage); + if (storage.active_index >= 0 && storage.active_index < storage.items_num) { + NodeRepeatItem &active_item = storage.items[storage.active_index]; + PointerRNA item_ptr = RNA_pointer_create( + output_node_ptr.owner_id, RepeatItemsAccessor::item_srna, &active_item); + uiLayoutSetPropSep(panel, true); + uiLayoutSetPropDecorate(panel, false); + uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE); + } + } + + uiItemR(layout, &output_node_ptr, "inspection_index", UI_ITEM_NONE, nullptr, ICON_NONE); +} + namespace repeat_input_node { NODE_STORAGE_FUNCS(NodeGeometryRepeatInput); @@ -90,6 +189,7 @@ static void node_register() ntype.gather_link_search_ops = nullptr; ntype.insert_link = node_insert_link; ntype.no_muting = true; + ntype.draw_buttons_ex = node_layout_ex; node_type_storage( &ntype, "NodeGeometryRepeatInput", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); @@ -162,6 +262,31 @@ static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) *ntree, *node, *node, *link); } +static void NODE_OT_repeat_zone_item_remove(wmOperatorType *ot) +{ + socket_items::ops::remove_item( + ot, "Remove Repeat Zone Item", __func__, "Remove active repeat zone item"); +} + +static void NODE_OT_repeat_zone_item_add(wmOperatorType *ot) +{ + socket_items::ops::add_item_with_name_and_type( + ot, "Add Repeat Zone Item", __func__, "Add repeat zone item"); +} + +static void NODE_OT_repeat_zone_item_move(wmOperatorType *ot) +{ + socket_items::ops::move_item( + ot, "Move Repeat Zone Item", __func__, "Move active repeat zone item"); +} + +static void node_operators() +{ + WM_operatortype_append(NODE_OT_repeat_zone_item_add); + WM_operatortype_append(NODE_OT_repeat_zone_item_remove); + WM_operatortype_append(NODE_OT_repeat_zone_item_move); +} + static void node_register() { static bNodeType ntype; @@ -171,6 +296,8 @@ static void node_register() ntype.labelfunc = repeat_input_node::node_label; ntype.insert_link = node_insert_link; ntype.no_muting = true; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; node_type_storage(&ntype, "NodeGeometryRepeatOutput", node_free_storage, node_copy_storage); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc index f75a45f51d0..4e9053b9e37 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc @@ -17,8 +17,11 @@ #include "BKE_instances.hh" #include "BKE_modifier.hh" #include "BKE_node_socket_value.hh" +#include "BKE_node_tree_update.hh" +#include "BKE_node_tree_zones.hh" #include "BKE_object.hh" #include "BKE_scene.hh" +#include "BKE_screen.hh" #include "DEG_depsgraph_query.hh" @@ -28,6 +31,7 @@ #include "NOD_geo_simulation.hh" #include "NOD_geometry.hh" #include "NOD_socket.hh" +#include "NOD_socket_items_ops.hh" #include "DNA_curves_types.h" #include "DNA_mesh_types.h" @@ -38,6 +42,7 @@ #include "ED_node.hh" #include "RNA_access.hh" +#include "RNA_define.hh" #include "RNA_prototypes.h" #include "MOD_nodes.hh" @@ -181,6 +186,267 @@ static bke::bake::BakeState move_values_to_simulation_state( return bake_state; } +static void draw_simulation_state_item(uiList * /*ui_list*/, + const bContext *C, + uiLayout *layout, + PointerRNA * /*idataptr*/, + PointerRNA *itemptr, + int /*icon*/, + PointerRNA * /*active_dataptr*/, + const char * /*active_propname*/, + int /*index*/, + int /*flt_flag*/) +{ + uiLayout *row = uiLayoutRow(layout, true); + float4 color; + RNA_float_get_array(itemptr, "color", color); + uiTemplateNodeSocket(row, const_cast(C), color); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, itemptr, "name", UI_ITEM_NONE, "", ICON_NONE); +} + +static void draw_simulation_state(const bContext *C, + uiLayout *layout, + bNodeTree &ntree, + bNode &output_node) +{ + static const uiListType *state_items_list = []() { + uiListType *list = MEM_cnew(__func__); + STRNCPY(list->idname, "DATA_UL_simulation_zone_state"); + list->draw_item = draw_simulation_state_item; + WM_uilisttype_add(list); + return list; + }(); + + PointerRNA output_node_ptr = RNA_pointer_create(&ntree.id, &RNA_Node, &output_node); + + if (uiLayout *panel = uiLayoutPanel( + C, layout, "simulation_state_items", false, TIP_("Simulation State"))) + { + uiLayout *row = uiLayoutRow(panel, false); + uiTemplateList(row, + C, + state_items_list->idname, + "", + &output_node_ptr, + "state_items", + &output_node_ptr, + "active_index", + nullptr, + 3, + 5, + UILST_LAYOUT_DEFAULT, + 0, + UI_TEMPLATE_LIST_FLAG_NONE); + + { + uiLayout *ops_col = uiLayoutColumn(row, false); + { + uiLayout *add_remove_col = uiLayoutColumn(ops_col, true); + uiItemO(add_remove_col, "", ICON_ADD, "node.simulation_zone_item_add"); + uiItemO(add_remove_col, "", ICON_REMOVE, "node.simulation_zone_item_remove"); + } + { + uiLayout *up_down_col = uiLayoutColumn(ops_col, true); + uiItemEnumO( + up_down_col, "node.simulation_zone_item_move", "", ICON_TRIA_UP, "direction", 0); + uiItemEnumO( + up_down_col, "node.simulation_zone_item_move", "", ICON_TRIA_DOWN, "direction", 1); + } + } + + auto &storage = *static_cast(output_node.storage); + if (storage.active_index >= 0 && storage.active_index < storage.items_num) { + NodeSimulationItem &active_item = storage.items[storage.active_index]; + PointerRNA item_ptr = RNA_pointer_create( + output_node_ptr.owner_id, SimulationItemsAccessor::item_srna, &active_item); + uiLayoutSetPropSep(panel, true); + uiLayoutSetPropDecorate(panel, false); + uiItemR(panel, &item_ptr, "socket_type", UI_ITEM_NONE, nullptr, ICON_NONE); + if (socket_type_supports_fields(eNodeSocketDatatype(active_item.socket_type))) { + uiItemR(panel, &item_ptr, "attribute_domain", UI_ITEM_NONE, nullptr, ICON_NONE); + } + } + } +} + +static void NODE_OT_simulation_zone_item_remove(wmOperatorType *ot) +{ + socket_items::ops::remove_item( + ot, "Remove Simulation Zone Item", __func__, "Remove active simulation zone item"); +} + +static void NODE_OT_simulation_zone_item_add(wmOperatorType *ot) +{ + socket_items::ops::add_item_with_name_and_type( + ot, "Add Simulation Zone Item", __func__, "Add simulation zone item"); +} + +static void NODE_OT_simulation_zone_item_move(wmOperatorType *ot) +{ + socket_items::ops::move_item( + ot, "Move Simulation Zone Item", __func__, "Move active simulation zone item"); +} + +/** Shared for simulation input and output node. */ +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr) +{ + bNodeTree &ntree = *reinterpret_cast(current_node_ptr->owner_id); + bNode *current_node = static_cast(current_node_ptr->data); + + Scene *scene = CTX_data_scene(C); + SpaceNode *snode = CTX_wm_space_node(C); + + const bke::bNodeTreeZones *zones = ntree.zones(); + if (!zones) { + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier); + if (!zone) { + return; + } + if (!zone->output_node) { + return; + } + bNode &output_node = const_cast(*zone->output_node); + + draw_simulation_state(C, layout, ntree, output_node); + + if (snode == nullptr) { + return; + } + std::optional object_and_modifier = + ed::space_node::get_modifier_for_node_editor(*snode); + if (!object_and_modifier.has_value()) { + return; + } + const Object &object = *object_and_modifier->object; + const NodesModifierData &nmd = *object_and_modifier->nmd; + const std::optional bake_id = ed::space_node::find_nested_node_id_in_root(*snode, + output_node); + if (!bake_id.has_value()) { + return; + } + const NodesModifierBake *bake = nullptr; + for (const NodesModifierBake &iter_bake : Span{nmd.bakes, nmd.bakes_num}) { + if (iter_bake.id == *bake_id) { + bake = &iter_bake; + break; + } + } + if (bake == nullptr) { + return; + } + + PointerRNA bake_rna = RNA_pointer_create( + const_cast(&object.id), &RNA_NodesModifierBake, (void *)bake); + + const std::optional simulation_range = bke::bake::get_node_bake_frame_range( + *scene, object, nmd, *bake_id); + + std::optional baked_range; + if (nmd.runtime->cache) { + const bke::bake::ModifierCache &cache = *nmd.runtime->cache; + std::lock_guard lock{cache.mutex}; + if (const std::unique_ptr *node_cache_ptr = + cache.simulation_cache_by_id.lookup_ptr(*bake_id)) + { + const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr; + if (node_cache.cache_status == bke::bake::CacheStatus::Baked && + !node_cache.bake.frames.is_empty()) + { + const int first_frame = node_cache.bake.frames.first()->frame.frame(); + const int last_frame = node_cache.bake.frames.last()->frame.frame(); + baked_range = IndexRange(first_frame, last_frame - first_frame + 1); + } + } + } + bool is_baked = baked_range.has_value(); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiLayoutSetEnabled(layout, !ID_IS_LINKED(&object)); + + { + uiLayout *col = uiLayoutColumn(layout, false); + uiLayout *row = uiLayoutRow(col, true); + { + char bake_label[1024] = N_("Bake"); + + PointerRNA ptr; + uiItemFullO(row, + "OBJECT_OT_geometry_node_bake_single", + bake_label, + ICON_NONE, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); + RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); + RNA_int_set(&ptr, "bake_id", bake->id); + } + { + PointerRNA ptr; + uiItemFullO(row, + "OBJECT_OT_geometry_node_bake_delete_single", + "", + ICON_TRASH, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); + RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); + RNA_int_set(&ptr, "bake_id", bake->id); + } + if (is_baked) { + char baked_range_label[64]; + SNPRINTF(baked_range_label, + N_("Baked %d - %d"), + int(baked_range->first()), + int(baked_range->last())); + uiItemL(layout, baked_range_label, ICON_NONE); + } + else if (simulation_range.has_value()) { + char simulation_range_label[64]; + SNPRINTF(simulation_range_label, + N_("Frames %d - %d"), + int(simulation_range->first()), + int(simulation_range->last())); + uiItemL(layout, simulation_range_label, ICON_NONE); + } + } + { + uiLayout *settings_col = uiLayoutColumn(layout, false); + uiLayoutSetActive(settings_col, !is_baked); + { + uiLayout *col = uiLayoutColumn(settings_col, true); + uiLayoutSetActive(col, !is_baked); + uiItemR(col, &bake_rna, "use_custom_path", UI_ITEM_NONE, "Custom Path", ICON_NONE); + uiLayout *subcol = uiLayoutColumn(col, true); + uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH); + uiItemR(subcol, &bake_rna, "directory", UI_ITEM_NONE, "Path", ICON_NONE); + } + { + uiLayout *col = uiLayoutColumn(settings_col, true); + uiItemR(col, + &bake_rna, + "use_custom_simulation_frame_range", + UI_ITEM_NONE, + "Custom Range", + ICON_NONE); + uiLayout *subcol = uiLayoutColumn(col, true); + uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE); + uiItemR(subcol, &bake_rna, "frame_start", UI_ITEM_NONE, "Start", ICON_NONE); + uiItemR(subcol, &bake_rna, "frame_end", UI_ITEM_NONE, "End", ICON_NONE); + } + } + + draw_data_blocks(C, layout, bake_rna); +} + namespace sim_input_node { NODE_STORAGE_FUNCS(NodeGeometrySimulationInput); @@ -412,6 +678,7 @@ static void node_register() ntype.insert_link = node_insert_link; ntype.gather_link_search_ops = nullptr; ntype.no_muting = true; + ntype.draw_buttons_ex = node_layout_ex; node_type_storage(&ntype, "NodeGeometrySimulationInput", node_free_standard_storage, @@ -744,144 +1011,11 @@ static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const b socket_items::copy_array(*src_node, *dst_node); } -static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) +static void node_operators() { - const bNode *node = static_cast(ptr->data); - Scene *scene = CTX_data_scene(C); - SpaceNode *snode = CTX_wm_space_node(C); - if (snode == nullptr) { - return; - } - std::optional object_and_modifier = - ed::space_node::get_modifier_for_node_editor(*snode); - if (!object_and_modifier.has_value()) { - return; - } - const Object &object = *object_and_modifier->object; - const NodesModifierData &nmd = *object_and_modifier->nmd; - const std::optional bake_id = ed::space_node::find_nested_node_id_in_root(*snode, - *node); - if (!bake_id.has_value()) { - return; - } - const NodesModifierBake *bake = nullptr; - for (const NodesModifierBake &iter_bake : Span{nmd.bakes, nmd.bakes_num}) { - if (iter_bake.id == *bake_id) { - bake = &iter_bake; - break; - } - } - if (bake == nullptr) { - return; - } - - PointerRNA bake_rna = RNA_pointer_create( - const_cast(&object.id), &RNA_NodesModifierBake, (void *)bake); - - const std::optional simulation_range = bke::bake::get_node_bake_frame_range( - *scene, object, nmd, *bake_id); - - std::optional baked_range; - if (nmd.runtime->cache) { - const bke::bake::ModifierCache &cache = *nmd.runtime->cache; - std::lock_guard lock{cache.mutex}; - if (const std::unique_ptr *node_cache_ptr = - cache.simulation_cache_by_id.lookup_ptr(*bake_id)) - { - const bke::bake::SimulationNodeCache &node_cache = **node_cache_ptr; - if (node_cache.cache_status == bke::bake::CacheStatus::Baked && - !node_cache.bake.frames.is_empty()) - { - const int first_frame = node_cache.bake.frames.first()->frame.frame(); - const int last_frame = node_cache.bake.frames.last()->frame.frame(); - baked_range = IndexRange(first_frame, last_frame - first_frame + 1); - } - } - } - bool is_baked = baked_range.has_value(); - - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - - uiLayoutSetEnabled(layout, !ID_IS_LINKED(&object)); - - { - uiLayout *col = uiLayoutColumn(layout, false); - uiLayout *row = uiLayoutRow(col, true); - { - char bake_label[1024] = N_("Bake"); - - PointerRNA ptr; - uiItemFullO(row, - "OBJECT_OT_geometry_node_bake_single", - bake_label, - ICON_NONE, - nullptr, - WM_OP_INVOKE_DEFAULT, - UI_ITEM_NONE, - &ptr); - WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); - RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); - RNA_int_set(&ptr, "bake_id", bake->id); - } - { - PointerRNA ptr; - uiItemFullO(row, - "OBJECT_OT_geometry_node_bake_delete_single", - "", - ICON_TRASH, - nullptr, - WM_OP_INVOKE_DEFAULT, - UI_ITEM_NONE, - &ptr); - WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); - RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); - RNA_int_set(&ptr, "bake_id", bake->id); - } - if (is_baked) { - char baked_range_label[64]; - SNPRINTF(baked_range_label, - N_("Baked %d - %d"), - int(baked_range->first()), - int(baked_range->last())); - uiItemL(layout, baked_range_label, ICON_NONE); - } - else if (simulation_range.has_value()) { - char simulation_range_label[64]; - SNPRINTF(simulation_range_label, - N_("Frames %d - %d"), - int(simulation_range->first()), - int(simulation_range->last())); - uiItemL(layout, simulation_range_label, ICON_NONE); - } - } - { - uiLayout *settings_col = uiLayoutColumn(layout, false); - uiLayoutSetActive(settings_col, !is_baked); - { - uiLayout *col = uiLayoutColumn(settings_col, true); - uiLayoutSetActive(col, !is_baked); - uiItemR(col, &bake_rna, "use_custom_path", UI_ITEM_NONE, "Custom Path", ICON_NONE); - uiLayout *subcol = uiLayoutColumn(col, true); - uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH); - uiItemR(subcol, &bake_rna, "directory", UI_ITEM_NONE, "Path", ICON_NONE); - } - { - uiLayout *col = uiLayoutColumn(settings_col, true); - uiItemR(col, - &bake_rna, - "use_custom_simulation_frame_range", - UI_ITEM_NONE, - "Custom Range", - ICON_NONE); - uiLayout *subcol = uiLayoutColumn(col, true); - uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE); - uiItemR(subcol, &bake_rna, "frame_start", UI_ITEM_NONE, "Start", ICON_NONE); - uiItemR(subcol, &bake_rna, "frame_end", UI_ITEM_NONE, "End", ICON_NONE); - } - } - - draw_data_blocks(C, layout, bake_rna); + WM_operatortype_append(NODE_OT_simulation_zone_item_add); + WM_operatortype_append(NODE_OT_simulation_zone_item_remove); + WM_operatortype_append(NODE_OT_simulation_zone_item_move); } static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) @@ -903,6 +1037,7 @@ static void node_register() ntype.insert_link = node_insert_link; ntype.draw_buttons_ex = node_layout_ex; ntype.no_muting = true; + ntype.register_operators = node_operators; node_type_storage(&ntype, "NodeGeometrySimulationOutput", node_free_storage, node_copy_storage); nodeRegisterType(&ntype); }