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); }