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
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user