Nodes: Swap Node Operator

Implement a native method to swap between different node and zone types.

This implementation repurposes the existing menu definitions as base
classes, from which both an "Add" and a "Swap" version would be generated
from. This allows both menus to have the same layout, but use their own
operators for handling the different node/zone types.

In this PR, support for all node editors has been implemented.

Invoking the menu is currently bound to `Shift + S`, same as the old
implementation in Node Wrangler. Since "Swap" is implemented as a
regular menu, features that menus already have such as type-to-search
and adding to Quick Favorites don't require any extra caveats to
consider.

Resolves #133452

Pull Request: https://projects.blender.org/blender/blender/pulls/143997
This commit is contained in:
quackarooni
2025-09-25 16:12:02 +02:00
committed by Hans Goudey
parent fc4fc2d16c
commit 2a1a658492
21 changed files with 2495 additions and 1292 deletions

View File

@@ -408,15 +408,6 @@ class NWAttributeMenu(bpy.types.Menu):
l.label(text="No attributes on objects with this material") l.label(text="No attributes on objects with this material")
class NWSwitchNodeTypeMenu(Menu, NWBaseMenu):
bl_idname = "NODE_MT_nw_switch_node_type_menu"
bl_label = "Switch Type to..."
def draw(self, context):
layout = self.layout
layout.label(text="This operator is removed due to the changes of node menus.", icon='ERROR')
layout.label(text="A native implementation of the function is expected in the future.")
# #
# APPENDAGES TO EXISTING UI # APPENDAGES TO EXISTING UI
# #
@@ -499,7 +490,6 @@ classes = (
NWLinkUseNodeNameMenu, NWLinkUseNodeNameMenu,
NWLinkUseOutputsNamesMenu, NWLinkUseOutputsNamesMenu,
NWAttributeMenu, NWAttributeMenu,
NWSwitchNodeTypeMenu,
) )

View File

@@ -357,8 +357,6 @@ kmi_defs = (
(('name', interface.NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"), (('name', interface.NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
('wm.call_menu', 'C', 'PRESS', False, True, False, ('wm.call_menu', 'C', 'PRESS', False, True, False,
(('name', interface.NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"), (('name', interface.NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"),
('wm.call_menu', 'S', 'PRESS', False, True, False,
(('name', interface.NWSwitchNodeTypeMenu.bl_idname),), "Switch node type menu"),
) )
classes = ( classes = (

View File

@@ -2231,6 +2231,7 @@ def km_node_editor(params):
("node.link_make", {"type": 'J', "value": 'PRESS', "shift": True}, ("node.link_make", {"type": 'J', "value": 'PRESS', "shift": True},
{"properties": [("replace", True)]}), {"properties": [("replace", True)]}),
op_menu("NODE_MT_add", {"type": 'A', "value": 'PRESS', "shift": True}), op_menu("NODE_MT_add", {"type": 'A', "value": 'PRESS', "shift": True}),
op_menu("NODE_MT_swap", {"type": 'S', "value": 'PRESS', "shift": True}),
("node.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, ("node.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True},
{"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}), {"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),
("node.duplicate_move_linked", {"type": 'D', "value": 'PRESS', "alt": True}, ("node.duplicate_move_linked", {"type": 'D', "value": 'PRESS', "alt": True},

View File

@@ -28,6 +28,84 @@ from bpy.app.translations import (
) )
math_nodes = {
"ShaderNodeMath",
"ShaderNodeVectorMath",
"FunctionNodeIntegerMath",
"FunctionNodeBooleanMath",
"FunctionNodeBitMath",
}
switch_nodes = {
"GeometryNodeMenuSwitch",
"GeometryNodeIndexSwitch",
}
# A context manager for temporarily unparenting nodes from their frames
# This gets rid of issues with framed nodes using relative coordinates
class temporary_unframe:
def __init__(self, nodes):
self.parent_dict = {}
for node in nodes:
if node.parent is not None:
self.parent_dict[node] = node.parent
node.parent = None
def __enter__(self):
return self
def __exit__(self, _type, _value, _traceback):
for node, parent in self.parent_dict.items():
node.parent = parent
def cast_value(source, target):
source_type = source.type
target_type = target.type
value = source.default_value
def to_bool(value): return value > 0
def single_value_to_color(value): return Vector((value, value, value, 1.0))
def single_value_to_vector(value): return Vector([value,] * len(target.default_value))
def color_to_float(color): return (0.2126 * color[0]) + (0.7152 * color[1]) + (0.0722 * color[2])
def vector_to_float(vector): return sum(vector) / len(vector)
func_map = {
('VALUE', 'INT'): int,
('VALUE', 'BOOLEAN'): to_bool,
('VALUE', 'RGBA'): single_value_to_color,
('VALUE', 'VECTOR'): single_value_to_vector,
('INT', 'VALUE'): float,
('INT', 'BOOLEAN'): to_bool,
('INT', 'RGBA'): single_value_to_color,
('INT', 'VECTOR'): single_value_to_vector,
('BOOLEAN', 'VALUE'): float,
('BOOLEAN', 'INT'): int,
('BOOLEAN', 'RGBA'): single_value_to_color,
('BOOLEAN', 'VECTOR'): single_value_to_vector,
('RGBA', 'VALUE'): color_to_float,
('RGBA', 'INT'): lambda color: int(color_to_float(color)),
('RGBA', 'BOOLEAN'): lambda color: to_bool(color_to_float(color)),
('RGBA', 'VECTOR'): lambda color: color[:len(target.default_value)],
('VECTOR', 'VALUE'): vector_to_float,
('VECTOR', 'INT'): lambda vector: int(vector_to_float(vector)),
# Even negative vectors get implicitly converted to True, hence to_bool is not used
('VECTOR', 'BOOLEAN'): lambda vector: bool(vector_to_float(vector)),
('VECTOR', 'RGBA'): lambda vector: list(vector).extend([0.0] * (len(target.default_value) - len(vector)))
}
if source_type == target_type:
return value
cast_func = func_map.get((source_type, target_type))
if cast_func is not None:
return cast_func(value)
return None
class NodeSetting(PropertyGroup): class NodeSetting(PropertyGroup):
__slots__ = () __slots__ = ()
@@ -39,14 +117,7 @@ class NodeSetting(PropertyGroup):
) )
# Base class for node "Add" operators. class NodeOperator:
class NodeAddOperator:
use_transform: BoolProperty(
name="Use Transform",
description="Start transform operator after inserting the node",
default=False,
)
settings: CollectionProperty( settings: CollectionProperty(
name="Settings", name="Settings",
description="Settings to be applied on the newly created node", description="Settings to be applied on the newly created node",
@@ -54,6 +125,77 @@ class NodeAddOperator:
options={'SKIP_SAVE'}, options={'SKIP_SAVE'},
) )
@classmethod
def description(cls, _context, properties):
from nodeitems_builtins import node_tree_group_type
nodetype = properties["type"]
if nodetype in node_tree_group_type.values():
for setting in properties.settings:
if setting.name == "node_tree":
node_group = eval(setting.value)
if node_group.description:
return node_group.description
bl_rna = bpy.types.Node.bl_rna_get_subclass(nodetype)
if bl_rna is not None:
return tip_(bl_rna.description)
else:
return ""
# Deselect all nodes in the tree.
@staticmethod
def deselect_nodes(context):
space = context.space_data
tree = space.edit_tree
for n in tree.nodes:
n.select = False
def create_node(self, context, node_type):
space = context.space_data
tree = space.edit_tree
try:
node = tree.nodes.new(type=node_type)
except RuntimeError as ex:
self.report({'ERROR'}, str(ex))
return None
node.select = True
tree.nodes.active = node
node.location = space.cursor_location
return node
def apply_node_settings(self, node):
for setting in self.settings:
# XXX catch exceptions here?
value = eval(setting.value)
node_data = node
node_attr_name = setting.name
# Support path to nested data.
if '.' in node_attr_name:
node_data_path, node_attr_name = node_attr_name.rsplit(".", 1)
node_data = node.path_resolve(node_data_path)
try:
setattr(node_data, node_attr_name, value)
except AttributeError as ex:
self.report(
{'ERROR_INVALID_INPUT'},
rpt_("Node has no attribute {:s}").format(setting.name))
print(str(ex))
# Continue despite invalid attribute
return node
# Base class for node "Add" operators.
class NodeAddOperator(NodeOperator):
use_transform: BoolProperty(
name="Use Transform",
description="Start transform operator after inserting the node",
default=False,
)
@staticmethod @staticmethod
def store_mouse_cursor(context, event): def store_mouse_cursor(context, event):
space = context.space_data space = context.space_data
@@ -72,49 +214,6 @@ class NodeAddOperator:
else: else:
space.cursor_location = tree.view_center space.cursor_location = tree.view_center
# Deselect all nodes in the tree.
@staticmethod
def deselect_nodes(context):
space = context.space_data
tree = space.edit_tree
for n in tree.nodes:
n.select = False
def create_node(self, context, node_type):
space = context.space_data
tree = space.edit_tree
try:
node = tree.nodes.new(type=node_type)
except RuntimeError as ex:
self.report({'ERROR'}, str(ex))
return None
for setting in self.settings:
# XXX catch exceptions here?
value = eval(setting.value)
node_data = node
node_attr_name = setting.name
# Support path to nested data.
if '.' in node_attr_name:
node_data_path, node_attr_name = node_attr_name.rsplit(".", 1)
node_data = node.path_resolve(node_data_path)
try:
setattr(node_data, node_attr_name, value)
except AttributeError as ex:
self.report(
{'ERROR_INVALID_INPUT'},
rpt_("Node has no attribute {:s}").format(setting.name))
print(str(ex))
# Continue despite invalid attribute
node.select = True
tree.nodes.active = node
node.location = space.cursor_location
return node
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
space = context.space_data space = context.space_data
@@ -135,6 +234,176 @@ class NodeAddOperator:
return result return result
class NodeSwapOperator(NodeOperator):
properties_to_pass = (
'color',
'hide',
'label',
'mute',
'parent',
'show_options',
'show_preview',
'show_texture',
'use_alpha',
'use_clamp',
'use_custom_color',
"operation",
"domain",
"data_type",
)
@classmethod
def poll(cls, context):
if (context.area is None) or (context.area.type != "NODE_EDITOR"):
return False
if len(context.selected_nodes) <= 0:
cls.poll_message_set("No nodes selected.")
return False
return True
def transfer_node_properties(self, old_node, new_node):
for attr in self.properties_to_pass:
if (attr in self.settings):
return
if hasattr(old_node, attr) and hasattr(new_node, attr):
try:
setattr(new_node, attr, getattr(old_node, attr))
except (TypeError, ValueError):
pass
def transfer_input_values(self, old_node, new_node):
if (old_node.bl_idname in math_nodes) and (new_node.bl_idname in math_nodes):
for source_input, target_input in zip(old_node.inputs, new_node.inputs):
new_value = cast_value(source=source_input, target=target_input)
if new_value is not None:
target_input.default_value = new_value
else:
for input in old_node.inputs:
try:
new_socket = new_node.inputs[input.name]
new_value = cast_value(source=input, target=new_socket)
settings_name = f'inputs["{input.name}"].default_value'
already_defined = (settings_name in self.settings)
if (new_value is not None) and not already_defined:
new_socket.default_value = new_value
except (AttributeError, KeyError, TypeError):
pass
@staticmethod
def transfer_links(tree, old_node, new_node, is_input):
both_math_nodes = (old_node.bl_idname in math_nodes) and (new_node.bl_idname in math_nodes)
if is_input:
if both_math_nodes:
for i, input in enumerate(old_node.inputs):
for link in input.links[:]:
try:
new_socket = new_node.inputs[i]
if new_socket.hide or not new_socket.enabled:
continue
tree.links.new(link.from_socket, new_socket)
except IndexError:
pass
else:
for input in old_node.inputs:
links = sorted(input.links, key=lambda link: link.multi_input_sort_id)
for link in links:
try:
new_socket = new_node.inputs[input.name]
if new_socket.hide or not new_socket.enabled:
continue
tree.links.new(link.from_socket, new_socket)
except KeyError:
pass
else:
if both_math_nodes:
for i, output in enumerate(old_node.outputs):
for link in output.links[:]:
try:
new_socket = new_node.outputs[i]
if new_socket.hide or not new_socket.enabled:
continue
new_link = tree.links.new(new_socket, link.to_socket)
except IndexError:
pass
else:
for output in old_node.outputs:
for link in output.links[:]:
try:
new_socket = new_node.outputs[output.name]
if new_socket.hide or not new_socket.enabled:
continue
new_link = tree.links.new(new_socket, link.to_socket)
try:
if link.to_socket.is_multi_input:
new_link.swap_multi_input_sort_id(link)
except AttributeError:
pass
except KeyError:
pass
@staticmethod
def get_switch_items(node):
switch_type = node.bl_idname
if switch_type == "GeometryNodeMenuSwitch":
return node.enum_definition.enum_items
elif switch_type == "GeometryNodeIndexSwitch":
return node.index_switch_items
def transfer_switch_data(self, old_node, new_node):
old_switch_items = self.get_switch_items(old_node)
new_switch_items = self.get_switch_items(new_node)
new_switch_items.clear()
if new_node.bl_idname == "GeometryNodeMenuSwitch":
for i, old_item in enumerate(old_switch_items[:]):
# Change the menu item names to numerical indices
# This makes it so that later functions that match by socket name work on the switches
if hasattr(old_item, "name"):
old_item.name = str(i)
new_switch_items.new(str(i))
if (old_switch_value := old_node.inputs[0].default_value) != '':
new_node.inputs[0].default_value = str(old_switch_value)
elif new_node.bl_idname == "GeometryNodeIndexSwitch":
for i, old_item in enumerate(old_switch_items[:]):
# Change the menu item names to numerical indices
# This makes it so that later functions that match by socket name work on the switches
if hasattr(old_item, "name"):
old_item.name = str(i)
new_switch_items.new()
if (old_switch_value := old_node.inputs[0].default_value) != '':
new_node.inputs[0].default_value = int(old_switch_value)
# Simple basic operator for adding a node. # Simple basic operator for adding a node.
class NODE_OT_add_node(NodeAddOperator, Operator): class NODE_OT_add_node(NodeAddOperator, Operator):
"""Add a node to the active tree""" """Add a node to the active tree"""
@@ -158,6 +427,7 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
if self.properties.is_property_set("type"): if self.properties.is_property_set("type"):
self.deselect_nodes(context) self.deselect_nodes(context)
if node := self.create_node(context, self.type): if node := self.create_node(context, self.type):
self.apply_node_settings(node)
if self.visible_output: if self.visible_output:
for socket in node.outputs: for socket in node.outputs:
if socket.name != self.visible_output: if socket.name != self.visible_output:
@@ -166,22 +436,91 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
else: else:
return {'CANCELLED'} return {'CANCELLED'}
@classmethod
def description(cls, _context, properties):
from nodeitems_builtins import node_tree_group_type
nodetype = properties["type"] class NODE_OT_swap_node(NodeSwapOperator, Operator):
if nodetype in node_tree_group_type.values(): """Replace the selected nodes with the specified type"""
for setting in properties.settings: bl_idname = "node.swap_node"
if setting.name == "node_tree": bl_label = "Swap Node"
node_group = eval(setting.value) bl_options = {"REGISTER", "UNDO"}
if node_group.description:
return node_group.description type: StringProperty(
bl_rna = bpy.types.Node.bl_rna_get_subclass(nodetype) name="Node Type",
if bl_rna is not None: description="Node type",
return tip_(bl_rna.description) )
else:
return "" visible_output: StringProperty(
name="Output Name",
description="If provided, all outputs that are named differently will be hidden",
options={'SKIP_SAVE'},
)
@staticmethod
def get_zone_pair(tree, node):
# Get paired output node
if hasattr(node, "paired_output"):
return node, node.paired_output
# Get paired input node
for input_node in tree.nodes:
if hasattr(input_node, "paired_output"):
if input_node.paired_output == node:
return input_node, node
return None
def execute(self, context):
tree = context.space_data.edit_tree
for old_node in context.selected_nodes[:]:
if tree.nodes.get(old_node.name) is None:
continue
if old_node.bl_idname == self.type:
self.apply_node_settings(old_node)
continue
new_node = self.create_node(context, self.type)
self.apply_node_settings(new_node)
self.transfer_node_properties(old_node, new_node)
if self.visible_output:
for socket in new_node.outputs:
if socket.name != self.visible_output:
socket.hide = True
with temporary_unframe((old_node,)):
new_node.location = old_node.location
new_node.select = True
zone_pair = self.get_zone_pair(tree, old_node)
if zone_pair is not None:
input_node, output_node = zone_pair
if input_node.select and output_node.select:
with temporary_unframe((input_node, output_node)):
new_node.location = (input_node.location + output_node.location) / 2
new_node.select = True
self.transfer_input_values(input_node, new_node)
self.transfer_links(tree, input_node, new_node, is_input=True)
self.transfer_links(tree, output_node, new_node, is_input=False)
for node in zone_pair:
tree.nodes.remove(node)
else:
if (old_node.bl_idname in switch_nodes) and (new_node.bl_idname in switch_nodes):
self.transfer_switch_data(old_node, new_node)
self.transfer_input_values(old_node, new_node)
self.transfer_links(tree, old_node, new_node, is_input=True)
self.transfer_links(tree, old_node, new_node, is_input=False)
tree.nodes.remove(old_node)
return {'FINISHED'}
class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator): class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator):
@@ -190,12 +529,19 @@ class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator):
bl_description = "Add a group node with an empty group" bl_description = "Add a group node with an empty group"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
# Override inherited method from NodeOperator
# Return None so that bl_description is used
@classmethod
def description(cls, _context, properties):
...
def execute(self, context): def execute(self, context):
from nodeitems_builtins import node_tree_group_type from nodeitems_builtins import node_tree_group_type
tree = context.space_data.edit_tree tree = context.space_data.edit_tree
group = self.create_empty_group(tree.bl_idname) group = self.create_empty_group(tree.bl_idname)
self.deselect_nodes(context) self.deselect_nodes(context)
node = self.create_node(context, node_tree_group_type[tree.bl_idname]) node = self.create_node(context, node_tree_group_type[tree.bl_idname])
self.apply_node_settings(node)
node.node_tree = group node.node_tree = group
return {"FINISHED"} return {"FINISHED"}
@@ -213,7 +559,45 @@ class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator):
return group return group
class NodeAddZoneOperator(NodeAddOperator): class NODE_OT_swap_empty_group(NodeSwapOperator, bpy.types.Operator):
bl_idname = "node.swap_empty_group"
bl_label = "Swap Empty Group"
bl_description = "Replace active node with an empty group"
bl_options = {'REGISTER', 'UNDO'}
# Override inherited method from NodeOperator
# Return None so that bl_description is used
@classmethod
def description(cls, _context, properties):
...
def execute(self, context):
from nodeitems_builtins import node_tree_group_type
tree = context.space_data.edit_tree
group = self.create_empty_group(tree.bl_idname)
bpy.ops.node.swap_node('INVOKE_DEFAULT', type=node_tree_group_type[tree.bl_idname])
for node in context.selected_nodes:
node.node_tree = group
return {"FINISHED"}
@staticmethod
def create_empty_group(idname):
group = bpy.data.node_groups.new(name="NodeGroup", type=idname)
input_node = group.nodes.new('NodeGroupInput')
input_node.select = False
input_node.location.x = -200 - input_node.width
output_node = group.nodes.new('NodeGroupOutput')
output_node.is_active_output = True
output_node.select = False
output_node.location.x = 200
return group
class ZoneOperator:
offset: FloatVectorProperty( offset: FloatVectorProperty(
name="Offset", name="Offset",
description="Offset of nodes from the cursor when added", description="Offset of nodes from the cursor when added",
@@ -221,6 +605,25 @@ class NodeAddZoneOperator(NodeAddOperator):
default=(150, 0), default=(150, 0),
) )
zone_tooltips = {
"GeometryNodeSimulationInput": "Simulate the execution of nodes across a time span",
"GeometryNodeRepeatInput": "Execute nodes with a dynamic number of repetitions",
"GeometryNodeForeachGeometryElementInput": "Perform operations separately for each geometry element (e.g. vertices, edges, etc.)",
"NodeClosureInput": "Wrap nodes inside a closure that can be executed at a different part of the nodetree",
}
@classmethod
def description(cls, _context, properties):
input_node_type = getattr(properties, "input_node_type", None)
# For Add Zone operators, use class variable instead of operator property
if input_node_type is None:
input_node_type = cls.input_node_type
return cls.zone_tooltips.get(input_node_type, None)
class NodeAddZoneOperator(ZoneOperator, NodeAddOperator):
add_default_geometry_link = True add_default_geometry_link = True
def execute(self, context): def execute(self, context):
@@ -230,6 +633,10 @@ class NodeAddZoneOperator(NodeAddOperator):
self.deselect_nodes(context) self.deselect_nodes(context)
input_node = self.create_node(context, self.input_node_type) input_node = self.create_node(context, self.input_node_type)
output_node = self.create_node(context, self.output_node_type) output_node = self.create_node(context, self.output_node_type)
self.apply_node_settings(input_node)
self.apply_node_settings(output_node)
if input_node is None or output_node is None: if input_node is None or output_node is None:
return {'CANCELLED'} return {'CANCELLED'}
@@ -249,6 +656,145 @@ class NodeAddZoneOperator(NodeAddOperator):
return {'FINISHED'} return {'FINISHED'}
class NODE_OT_add_zone(NodeAddZoneOperator, Operator):
bl_idname = "node.add_zone"
bl_label = "Add Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type: StringProperty(
name="Input Node",
description="Specifies the input node used the created zone",
)
output_node_type: StringProperty(
name="Output Node",
description="Specifies the output node used the created zone",
)
add_default_geometry_link: BoolProperty(
name="Add Geometry Link",
description="When enabled, create a link between geometry sockets in this zone",
default=False,
)
class NODE_OT_swap_zone(ZoneOperator, NodeSwapOperator, Operator):
bl_idname = "node.swap_zone"
bl_label = "Swap Zone"
bl_options = {"REGISTER", "UNDO"}
input_node_type: StringProperty(
name="Input Node",
description="Specifies the input node used the created zone",
)
output_node_type: StringProperty(
name="Output Node",
description="Specifies the output node used the created zone",
)
add_default_geometry_link: BoolProperty(
name="Add Geometry Link",
description="When enabled, create a link between geometry sockets in this zone",
default=False,
)
@staticmethod
def get_zone_pair(tree, node):
# Get paired output node
if hasattr(node, "paired_output"):
return node, node.paired_output
# Get paired input node
for input_node in tree.nodes:
if hasattr(input_node, "paired_output"):
if input_node.paired_output == node:
return input_node, node
return None
def execute(self, context):
tree = context.space_data.edit_tree
for old_node in context.selected_nodes[:]:
if tree.nodes.get(old_node.name) is None:
continue
zone_pair = self.get_zone_pair(tree, old_node)
if (old_node.bl_idname in {self.input_node_type, self.output_node_type}):
if zone_pair is not None:
old_input_node, old_output_node = zone_pair
self.apply_node_settings(old_input_node)
self.apply_node_settings(old_output_node)
else:
self.apply_node_settings(old_node)
continue
input_node = self.create_node(context, self.input_node_type)
output_node = self.create_node(context, self.output_node_type)
self.apply_node_settings(input_node)
self.apply_node_settings(output_node)
if input_node is None or output_node is None:
return {'CANCELLED'}
# Simulation input must be paired with the output.
input_node.pair_with_output(output_node)
if zone_pair is not None:
old_input_node, old_output_node = zone_pair
with temporary_unframe((old_input_node, old_output_node)):
input_node.location = old_input_node.location
output_node.location = old_output_node.location
self.transfer_node_properties(old_input_node, input_node)
self.transfer_node_properties(old_output_node, output_node)
self.transfer_input_values(old_input_node, input_node)
self.transfer_input_values(old_output_node, output_node)
self.transfer_links(tree, old_input_node, input_node, is_input=True)
self.transfer_links(tree, old_input_node, input_node, is_input=False)
self.transfer_links(tree, old_output_node, output_node, is_input=True)
self.transfer_links(tree, old_output_node, output_node, is_input=False)
for node in zone_pair:
tree.nodes.remove(node)
else:
with temporary_unframe((old_node,)):
input_node.location = old_node.location
output_node.location = old_node.location
input_node.location -= Vector(self.offset)
output_node.location += Vector(self.offset)
self.transfer_node_properties(old_node, input_node)
self.transfer_node_properties(old_node, output_node)
self.transfer_input_values(old_node, input_node)
self.transfer_links(tree, old_node, input_node, is_input=True)
self.transfer_links(tree, old_node, output_node, is_input=False)
tree.nodes.remove(old_node)
if tree.type == "GEOMETRY" and self.add_default_geometry_link:
# Connect geometry sockets by default if available.
# Get the sockets by their types, because the name is not guaranteed due to i18n.
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
if not (from_socket.is_linked or to_socket.is_linked):
tree.links.new(to_socket, from_socket)
return {'FINISHED'}
class NODE_OT_add_simulation_zone(NodeAddZoneOperator, Operator): class NODE_OT_add_simulation_zone(NodeAddZoneOperator, Operator):
"""Add simulation zone input and output nodes to the active tree""" """Add simulation zone input and output nodes to the active tree"""
bl_idname = "node.add_simulation_zone" bl_idname = "node.add_simulation_zone"
@@ -750,8 +1296,12 @@ classes = (
NODE_FH_image_node, NODE_FH_image_node,
NODE_OT_add_empty_group,
NODE_OT_add_node, NODE_OT_add_node,
NODE_OT_swap_node,
NODE_OT_add_empty_group,
NODE_OT_swap_empty_group,
NODE_OT_add_zone,
NODE_OT_swap_zone,
NODE_OT_add_simulation_zone, NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone, NODE_OT_add_repeat_zone,
NODE_OT_add_foreach_geometry_element_zone, NODE_OT_add_foreach_geometry_element_zone,

View File

@@ -25,112 +25,20 @@ from bpy.app.translations import (
) )
# NOTE: This is kept for compatibility's sake, as some scripts import node_add_menu.add_node_type
def add_node_type(layout, node_type, *, label=None, poll=None, search_weight=0.0, translate=True): def add_node_type(layout, node_type, *, label=None, poll=None, search_weight=0.0, translate=True):
"""Add a node type to a menu.""" """Add a node type to a menu."""
bl_rna = bpy.types.Node.bl_rna_get_subclass(node_type) return AddNodeMenu.node_operator(
if not label: layout,
label = bl_rna.name if bl_rna else iface_("Unknown") node_type,
label=label,
if poll is True or poll is None: poll=poll,
translation_context = bl_rna.translation_context if bl_rna else i18n_contexts.default search_weight=search_weight,
props = layout.operator( translate=translate)
"node.add_node",
text=label,
text_ctxt=translation_context,
translate=translate,
search_weight=search_weight)
props.type = node_type
props.use_transform = True
return props
return None
def add_node_type_with_outputs(context, layout, node_type, subnames, *, label=None, search_weight=0.0):
bl_rna = bpy.types.Node.bl_rna_get_subclass(node_type)
if not label:
label = bl_rna.name if bl_rna else "Unknown"
props = []
props.append(add_node_type(layout, node_type, label=label, search_weight=search_weight))
if getattr(context, "is_menu_search", False):
for subname in subnames:
item_props = add_node_type(
layout,
node_type,
label="{:s} \u25B8 {:s}".format(iface_(label), iface_(subname)),
search_weight=search_weight,
translate=False,
)
item_props.visible_output = subname
props.append(item_props)
return props
def draw_node_group_add_menu(context, layout):
"""Add items to the layout used for interacting with node groups."""
space_node = context.space_data
node_tree = space_node.edit_tree
all_node_groups = context.blend_data.node_groups
if node_tree in all_node_groups.values():
layout.separator()
add_node_type(layout, "NodeGroupInput")
add_node_type(layout, "NodeGroupOutput")
add_empty_group(layout)
if node_tree:
from nodeitems_builtins import node_tree_group_type
prefs = bpy.context.preferences
show_hidden = prefs.filepaths.show_hidden_files_datablocks
groups = [
group for group in context.blend_data.node_groups
if (group.bl_idname == node_tree.bl_idname and
not group.contains_tree(node_tree) and
(show_hidden or not group.name.startswith('.')))
]
if groups:
layout.separator()
for group in groups:
props = add_node_type(layout, node_tree_group_type[group.bl_idname], label=group.name)
ops = props.settings.add()
ops.name = "node_tree"
ops.value = "bpy.data.node_groups[{!r}]".format(group.name)
ops = props.settings.add()
ops.name = "width"
ops.value = repr(group.default_group_node_width)
ops = props.settings.add()
ops.name = "name"
ops.value = repr(group.name)
def draw_assets_for_catalog(layout, catalog_path):
layout.template_node_asset_menu_items(catalog_path=catalog_path)
def draw_root_assets(layout):
layout.menu_contents("NODE_MT_node_add_root_catalogs")
def add_node_type_with_searchable_enum(context, layout, node_idname, property_name, search_weight=0.0): def add_node_type_with_searchable_enum(context, layout, node_idname, property_name, search_weight=0.0):
add_node_type(layout, node_idname, search_weight=search_weight) return AddNodeMenu.node_operator_with_searchable_enum(context, layout, node_idname, property_name, search_weight)
if getattr(context, "is_menu_search", False):
node_type = getattr(bpy.types, node_idname)
translation_context = node_type.bl_rna.properties[property_name].translation_context
for item in node_type.bl_rna.properties[property_name].enum_items_static:
props = add_node_type(
layout,
node_idname,
label="{:s} \u25B8 {:s}".format(iface_(node_type.bl_rna.name), iface_(item.name, translation_context)),
translate=False,
search_weight=search_weight,
)
prop = props.settings.add()
prop.name = property_name
prop.value = repr(item.identifier)
def add_node_type_with_searchable_enum_socket( def add_node_type_with_searchable_enum_socket(
@@ -139,45 +47,32 @@ def add_node_type_with_searchable_enum_socket(
node_idname, node_idname,
socket_identifier, socket_identifier,
enum_names, enum_names,
search_weight=0.0, search_weight=0.0):
): return AddNodeMenu.node_operator_with_searchable_enum_socket(
add_node_type(layout, node_idname, search_weight=search_weight) context, layout, node_idname, socket_identifier, enum_names, search_weight)
if getattr(context, "is_menu_search", False):
node_type = getattr(bpy.types, node_idname)
for enum_name in enum_names: def add_node_type_with_outputs(context, layout, node_type, subnames, *, label=None, search_weight=0.0):
props = add_node_type( return AddNodeMenu.node_operator_with_outputs(
layout, context,
node_idname, layout,
label="{:s} \u25B8 {:s}".format(iface_(node_type.bl_rna.name), iface_(enum_name)), node_type,
translate=False, subnames,
search_weight=search_weight) label=label,
prop = props.settings.add() search_weight=search_weight)
prop.name = f'inputs["{socket_identifier}"].default_value'
prop.value = repr(enum_name)
def add_color_mix_node(context, layout): def add_color_mix_node(context, layout):
label = iface_("Mix Color") return AddNodeMenu.color_mix_node(context, layout)
props = node_add_menu.add_node_type(layout, "ShaderNodeMix", label=label, translate=False)
ops = props.settings.add()
ops.name = "data_type"
ops.value = "'RGBA'"
if getattr(context, "is_menu_search", False):
translation_context = bpy.types.ShaderNodeMix.bl_rna.properties["blend_type"].translation_context def add_empty_group(layout):
for item in bpy.types.ShaderNodeMix.bl_rna.properties["blend_type"].enum_items_static: return AddNodeMenu.new_empty_group(layout)
props = node_add_menu.add_node_type(
layout,
"ShaderNodeMix", def draw_node_group_add_menu(context, layout):
label="{:s} \u25B8 {:s}".format(label, iface_(item.name, translation_context)), """Add items to the layout used for interacting with node groups."""
translate=False, return AddNodeMenu.draw_group_menu(context, layout)
)
prop = props.settings.add()
prop.name = "data_type"
prop.value = "'RGBA'"
prop = props.settings.add()
prop.name = "blend_type"
prop.value = repr(item.identifier)
def add_simulation_zone(layout, label): def add_simulation_zone(layout, label):
@@ -210,26 +105,385 @@ def add_closure_zone(layout, label):
return props return props
def add_empty_group(layout): class NodeMenu(Menu):
props = layout.operator("node.add_empty_group", text="New Group", text_ctxt=i18n_contexts.default) """A baseclass defining the shared methods for AddNodeMenu and SwapNodeMenu"""
props.use_transform = True draw_assets: bool
return props use_transform: bool
main_operator_id: str
zone_operator_id: str
new_empty_group_operator_id: str
root_asset_menu: str
pathing_dict: dict[str, str]
@classmethod
def node_operator(cls, layout, node_type, *, label=None, poll=None, search_weight=0.0, translate=True):
"""The main operator defined for the node menu.
\n(e.g. 'Add Node' for AddNodeMenu, or 'Swap Node' for SwapNodeMenu)"""
bl_rna = bpy.types.Node.bl_rna_get_subclass(node_type)
if not label:
label = bl_rna.name if bl_rna else iface_("Unknown")
if poll is True or poll is None:
translation_context = bl_rna.translation_context if bl_rna else i18n_contexts.default
props = layout.operator(
cls.main_operator_id,
text=label,
text_ctxt=translation_context,
translate=translate,
search_weight=search_weight)
props.type = node_type
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
return None
@classmethod
def node_operator_with_searchable_enum(cls, context, layout, node_idname, property_name, search_weight=0.0):
"""Similar to `node_operator`, but with extra entries based on a enum property while in search"""
operators = []
operators.append(cls.node_operator(layout, node_idname, search_weight=search_weight))
if getattr(context, "is_menu_search", False):
node_type = getattr(bpy.types, node_idname)
translation_context = node_type.bl_rna.properties[property_name].translation_context
for item in node_type.bl_rna.properties[property_name].enum_items_static:
props = cls.node_operator(
layout,
node_idname,
label="{:s} \u25B8 {:s}".format(
iface_(
node_type.bl_rna.name),
iface_(
item.name,
translation_context)),
translate=False,
search_weight=search_weight)
prop = props.settings.add()
prop.name = property_name
prop.value = repr(item.identifier)
operators.append(props)
for props in operators:
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return operators
@classmethod
def node_operator_with_searchable_enum_socket(
cls,
context,
layout,
node_idname,
socket_identifier,
enum_names,
search_weight=0.0):
"""Similar to `node_operator`, but with extra entries based on a enum socket while in search"""
operators = []
operators.append(cls.node_operator(layout, node_idname, search_weight=search_weight))
if getattr(context, "is_menu_search", False):
node_type = getattr(bpy.types, node_idname)
for enum_name in enum_names:
props = cls.node_operator(
layout,
node_idname,
label="{:s} \u25B8 {:s}".format(iface_(node_type.bl_rna.name), iface_(enum_name)),
translate=False,
search_weight=search_weight)
prop = props.settings.add()
prop.name = f'inputs["{socket_identifier}"].default_value'
prop.value = repr(enum_name)
operators.append(props)
for props in operators:
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return operators
@classmethod
def node_operator_with_outputs(cls, context, layout, node_type, subnames, *, label=None, search_weight=0.0):
"""Similar to `node_operator`, but with extra entries based on a enum socket while in search"""
bl_rna = bpy.types.Node.bl_rna_get_subclass(node_type)
if not label:
label = bl_rna.name if bl_rna else "Unknown"
operators = []
operators.append(cls.node_operator(layout, node_type, label=label, search_weight=search_weight))
if getattr(context, "is_menu_search", False):
for subname in subnames:
item_props = cls.node_operator(layout, node_type, label="{:s} \u25B8 {:s}".format(
iface_(label), iface_(subname)), search_weight=search_weight, translate=False)
item_props.visible_output = subname
operators.append(item_props)
for props in operators:
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return operators
@classmethod
def color_mix_node(cls, context, layout):
"""The 'Mix Color' node, with its different blend modes available while in search"""
label = iface_("Mix Color")
operators = []
props = cls.node_operator(layout, "ShaderNodeMix", label=label, translate=False)
ops = props.settings.add()
ops.name = "data_type"
ops.value = "'RGBA'"
operators.append(props)
if getattr(context, "is_menu_search", False):
translation_context = bpy.types.ShaderNodeMix.bl_rna.properties["blend_type"].translation_context
for item in bpy.types.ShaderNodeMix.bl_rna.properties["blend_type"].enum_items_static:
props = cls.node_operator(
layout,
"ShaderNodeMix",
label="{:s} \u25B8 {:s}".format(
label,
iface_(
item.name,
translation_context)),
translate=False)
prop = props.settings.add()
prop.name = "data_type"
prop.value = "'RGBA'"
prop = props.settings.add()
prop.name = "blend_type"
prop.value = repr(item.identifier)
operators.append(props)
for props in operators:
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return operators
@classmethod
def new_empty_group(cls, layout):
"""Group Node with a newly created empty group as its assigned nodetree"""
props = layout.operator(cls.new_empty_group_operator_id, text="New Group", text_ctxt=i18n_contexts.default)
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
@classmethod
def draw_group_menu(cls, context, layout):
"""Show operators used for interacting with node groups"""
space_node = context.space_data
node_tree = space_node.edit_tree
all_node_groups = context.blend_data.node_groups
if node_tree in all_node_groups.values():
layout.separator()
cls.node_operator(layout, "NodeGroupInput")
cls.node_operator(layout, "NodeGroupOutput")
operators = []
operators.append(cls.new_empty_group(layout))
if node_tree:
from nodeitems_builtins import node_tree_group_type
prefs = bpy.context.preferences
show_hidden = prefs.filepaths.show_hidden_files_datablocks
groups = [
group for group in context.blend_data.node_groups
if (group.bl_idname == node_tree.bl_idname and
not group.contains_tree(node_tree) and
(show_hidden or not group.name.startswith('.')))
]
if groups:
layout.separator()
for group in groups:
props = cls.node_operator(layout, node_tree_group_type[group.bl_idname], label=group.name)
ops = props.settings.add()
ops.name = "node_tree"
ops.value = "bpy.data.node_groups[{!r}]".format(group.name)
ops = props.settings.add()
ops.name = "width"
ops.value = repr(group.default_group_node_width)
ops = props.settings.add()
ops.name = "name"
ops.value = repr(group.name)
operators.append(props)
for props in operators:
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return operators
@classmethod
def draw_menu(cls, layout, path):
"""Takes the given menu path and draws the corresponding menu.
\n Menu paths are either explicitly defined, or based on bl_label if not."""
if cls.pathing_dict is None:
raise ValueError("`pathing_dict` was not set for {}".format(cls))
layout.menu(cls.pathing_dict[path])
@classmethod
def simulation_zone(cls, layout, label):
props = layout.operator(cls.zone_operator_id, text=label)
props.input_node_type = "GeometryNodeSimulationInput"
props.output_node_type = "GeometryNodeSimulationOutput"
props.add_default_geometry_link = True
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
@classmethod
def repeat_zone(cls, layout, label):
props = layout.operator(cls.zone_operator_id, text=label)
props.input_node_type = "GeometryNodeRepeatInput"
props.output_node_type = "GeometryNodeRepeatOutput"
props.add_default_geometry_link = True
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
@classmethod
def for_each_element_zone(cls, layout, label):
props = layout.operator(cls.zone_operator_id, text=label)
props.input_node_type = "GeometryNodeForeachGeometryElementInput"
props.output_node_type = "GeometryNodeForeachGeometryElementOutput"
props.add_default_geometry_link = False
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
@classmethod
def closure_zone(cls, layout, label):
props = layout.operator(cls.zone_operator_id, text=label)
props.input_node_type = "NodeClosureInput"
props.output_node_type = "NodeClosureOutput"
props.add_default_geometry_link = False
if hasattr(props, "use_transform"):
props.use_transform = cls.use_transform
return props
@classmethod
def draw_root_assets(cls, layout):
if cls.draw_assets:
layout.menu_contents(cls.root_asset_menu)
class NODE_MT_category_layout(Menu): class AddNodeMenu(NodeMenu):
bl_idname = "NODE_MT_category_layout" draw_assets = True
use_transform = True
main_operator_id = "node.add_node"
zone_operator_id = "node.add_zone"
new_empty_group_operator_id = "node.add_empty_group"
root_asset_menu = "NODE_MT_node_add_root_catalogs"
@classmethod
def draw_assets_for_catalog(cls, layout, catalog_path):
if cls.draw_assets:
layout.template_node_asset_menu_items(catalog_path=catalog_path, operator='ADD')
class SwapNodeMenu(NodeMenu):
draw_assets = True
# NOTE: Swap operators don't have a `use_transform` property, so defining it here has no effect
main_operator_id = "node.swap_node"
zone_operator_id = "node.swap_zone"
new_empty_group_operator_id = "node.swap_empty_group"
root_asset_menu = "NODE_MT_node_swap_root_catalogs"
@classmethod
def draw_assets_for_catalog(cls, layout, catalog_path):
if cls.draw_assets:
layout.template_node_asset_menu_items(catalog_path=catalog_path, operator='SWAP')
class NODE_MT_group_base(NodeMenu):
bl_label = "Group"
def draw(self, context):
layout = self.layout
self.draw_group_menu(context, layout)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_layout_base(NodeMenu):
bl_label = "Layout" bl_label = "Layout"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "NodeFrame", search_weight=-1) self.node_operator(layout, "NodeFrame", search_weight=-1)
node_add_menu.add_node_type(layout, "NodeReroute") self.node_operator(layout, "NodeReroute")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
add_base_pathing_dict = {
"Group": "NODE_MT_group_add",
"Layout": "NODE_MT_category_layout",
}
swap_base_pathing_dict = {
"Group": "NODE_MT_group_swap",
"Layout": "NODE_MT_layout_swap",
}
def generate_menu(bl_idname: str, template: Menu, layout_base: Menu, pathing_dict: dict = None):
return type(bl_idname, (template, layout_base), {"bl_idname": bl_idname, "pathing_dict": pathing_dict})
def generate_menus(menus: dict, template: Menu, base_dict: dict):
import copy
pathing_dict = copy.copy(base_dict)
menus = tuple(
generate_menu(bl_idname, template, layout_base, pathing_dict)
for bl_idname, layout_base in menus.items()
)
generate_pathing_dict(pathing_dict, menus)
return menus
def generate_pathing_dict(pathing_dict, menus):
for menu in menus:
if hasattr(menu, "menu_path"):
menu_path = menu.menu_path
else:
menu_path = menu.bl_label
pathing_dict[menu_path] = menu.bl_idname
classes = ( classes = (
NODE_MT_category_layout, generate_menu("NODE_MT_group_add", template=AddNodeMenu, layout_base=NODE_MT_group_base),
generate_menu("NODE_MT_group_swap", template=SwapNodeMenu, layout_base=NODE_MT_group_base),
generate_menu("NODE_MT_category_layout", template=AddNodeMenu, layout_base=NODE_MT_layout_base),
generate_menu("NODE_MT_layout_swap", template=SwapNodeMenu, layout_base=NODE_MT_layout_base),
) )
if __name__ == "__main__": # only for live edit. if __name__ == "__main__": # only for live edit.

View File

@@ -9,373 +9,395 @@ from bpy.app.translations import (
) )
class NODE_MT_category_compositor_input(Menu): class NODE_MT_compositor_node_input_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_input"
bl_label = "Input" bl_label = "Input"
def draw(self, context): def draw(self, context):
del context del context
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_compositor_input_constant") self.draw_menu(layout, path="Input/Constant")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "NodeGroupInput") self.node_operator(layout, "NodeGroupInput")
node_add_menu.add_node_type(layout, "CompositorNodeBokehImage") self.node_operator(layout, "CompositorNodeBokehImage")
node_add_menu.add_node_type(layout, "CompositorNodeImage") self.node_operator(layout, "CompositorNodeImage")
node_add_menu.add_node_type(layout, "CompositorNodeImageInfo") self.node_operator(layout, "CompositorNodeImageInfo")
node_add_menu.add_node_type(layout, "CompositorNodeImageCoordinates") self.node_operator(layout, "CompositorNodeImageCoordinates")
node_add_menu.add_node_type(layout, "CompositorNodeMask") self.node_operator(layout, "CompositorNodeMask")
node_add_menu.add_node_type(layout, "CompositorNodeMovieClip") self.node_operator(layout, "CompositorNodeMovieClip")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_input_scene") self.draw_menu(layout, path="Input/Scene")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_input_constant(Menu): class NODE_MT_compositor_node_input_constant_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_input_constant"
bl_label = "Constant" bl_label = "Constant"
menu_path = "Input/Constant"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRGB") self.node_operator(layout, "CompositorNodeRGB")
node_add_menu.add_node_type(layout, "ShaderNodeValue") self.node_operator(layout, "ShaderNodeValue")
node_add_menu.add_node_type(layout, "CompositorNodeNormal") self.node_operator(layout, "CompositorNodeNormal")
node_add_menu.draw_assets_for_catalog(layout, "Input/Constant") self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_input_scene(Menu): class NODE_MT_compositor_node_input_scene_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_input_scene"
bl_label = "Scene" bl_label = "Scene"
menu_path = "Input/Scene"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRLayers") self.node_operator(layout, "CompositorNodeRLayers")
node_add_menu.add_node_type_with_outputs(context, layout, "CompositorNodeSceneTime", ["Frame", "Seconds"]) self.node_operator_with_outputs(context, layout, "CompositorNodeSceneTime", ["Frame", "Seconds"])
node_add_menu.add_node_type(layout, "CompositorNodeTime") self.node_operator(layout, "CompositorNodeTime")
node_add_menu.draw_assets_for_catalog(layout, "Input/Scene") self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_output(Menu): class NODE_MT_compositor_node_output_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_output"
bl_label = "Output" bl_label = "Output"
def draw(self, context): def draw(self, context):
del context del context
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupOutput") self.node_operator(layout, "NodeGroupOutput")
node_add_menu.add_node_type(layout, "CompositorNodeViewer") self.node_operator(layout, "CompositorNodeViewer")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeOutputFile") self.node_operator(layout, "CompositorNodeOutputFile")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_color(Menu): class NODE_MT_compositor_node_color_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_color"
bl_label = "Color" bl_label = "Color"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_compositor_color_adjust") self.draw_menu(layout, path="Color/Adjust")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_color_mix") self.draw_menu(layout, path="Color/Mix")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodePremulKey") self.node_operator(layout, "CompositorNodePremulKey")
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody") self.node_operator(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB") self.node_operator(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "CompositorNodeConvertColorSpace") self.node_operator(layout, "CompositorNodeConvertColorSpace")
node_add_menu.add_node_type(layout, "CompositorNodeConvertToDisplay") self.node_operator(layout, "CompositorNodeConvertToDisplay")
node_add_menu.add_node_type(layout, "CompositorNodeSetAlpha") self.node_operator(layout, "CompositorNodeSetAlpha")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeInvert") self.node_operator(layout, "CompositorNodeInvert")
node_add_menu.add_node_type(layout, "CompositorNodeRGBToBW") self.node_operator(layout, "CompositorNodeRGBToBW")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_color_adjust(Menu): class NODE_MT_compositor_node_color_adjust_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_color_adjust"
bl_label = "Adjust" bl_label = "Adjust"
menu_path = "Color/Adjust"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeBrightContrast") self.node_operator(layout, "CompositorNodeBrightContrast")
node_add_menu.add_node_type(layout, "CompositorNodeColorBalance") self.node_operator(layout, "CompositorNodeColorBalance")
node_add_menu.add_node_type(layout, "CompositorNodeColorCorrection") self.node_operator(layout, "CompositorNodeColorCorrection")
node_add_menu.add_node_type(layout, "CompositorNodeExposure") self.node_operator(layout, "CompositorNodeExposure")
node_add_menu.add_node_type(layout, "ShaderNodeGamma") self.node_operator(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "CompositorNodeHueCorrect") self.node_operator(layout, "CompositorNodeHueCorrect")
node_add_menu.add_node_type(layout, "CompositorNodeHueSat") self.node_operator(layout, "CompositorNodeHueSat")
node_add_menu.add_node_type(layout, "CompositorNodeCurveRGB") self.node_operator(layout, "CompositorNodeCurveRGB")
node_add_menu.add_node_type(layout, "CompositorNodeTonemap") self.node_operator(layout, "CompositorNodeTonemap")
node_add_menu.draw_assets_for_catalog(layout, "Color/Adjust") self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_color_mix(Menu): class NODE_MT_compositor_node_color_mix_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_color_mix"
bl_label = "Mix" bl_label = "Mix"
menu_path = "Color/Mix"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeAlphaOver") self.node_operator(layout, "CompositorNodeAlphaOver")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeCombineColor") self.node_operator(layout, "CompositorNodeCombineColor")
node_add_menu.add_node_type(layout, "CompositorNodeSeparateColor") self.node_operator(layout, "CompositorNodeSeparateColor")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeZcombine") self.node_operator(layout, "CompositorNodeZcombine")
node_add_menu.add_color_mix_node(context, layout) self.color_mix_node(context, layout)
node_add_menu.draw_assets_for_catalog(layout, "Color/Mix")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_filter(Menu): class NODE_MT_compositor_node_filter_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_filter"
bl_label = "Filter" bl_label = "Filter"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_compositor_filter_blur") self.draw_menu(layout, path="Filter/Blur")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeAntiAliasing") self.node_operator(layout, "CompositorNodeAntiAliasing")
node_add_menu.add_node_type(layout, "CompositorNodeConvolve") self.node_operator(layout, "CompositorNodeConvolve")
node_add_menu.add_node_type(layout, "CompositorNodeDenoise") self.node_operator(layout, "CompositorNodeDenoise")
node_add_menu.add_node_type(layout, "CompositorNodeDespeckle") self.node_operator(layout, "CompositorNodeDespeckle")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDilateErode") self.node_operator(layout, "CompositorNodeDilateErode")
node_add_menu.add_node_type(layout, "CompositorNodeInpaint") self.node_operator(layout, "CompositorNodeInpaint")
layout.separator() layout.separator()
node_add_menu.add_node_type_with_searchable_enum_socket( self.node_operator_with_searchable_enum_socket(
context, layout, "CompositorNodeFilter", "Type", [ context, layout, "CompositorNodeFilter", "Type", [
"Soften", "Box Sharpen", "Diamond Sharpen", "Laplace", "Sobel", "Prewitt", "Kirsch", "Shadow"]) "Soften", "Box Sharpen", "Diamond Sharpen", "Laplace", "Sobel", "Prewitt", "Kirsch", "Shadow"])
node_add_menu.add_node_type_with_searchable_enum_socket( self.node_operator_with_searchable_enum_socket(
context, layout, "CompositorNodeGlare", "Type", [ context, layout, "CompositorNodeGlare", "Type", [
"Bloom", "Ghosts", "Streaks", "Fog Glow", "Simple Star", "Sun Beams", "Kernel"]) "Bloom", "Ghosts", "Streaks", "Fog Glow", "Simple Star", "Sun Beams", "Kernel"])
node_add_menu.add_node_type(layout, "CompositorNodeKuwahara") self.node_operator(layout, "CompositorNodeKuwahara")
node_add_menu.add_node_type(layout, "CompositorNodePixelate") self.node_operator(layout, "CompositorNodePixelate")
node_add_menu.add_node_type(layout, "CompositorNodePosterize") self.node_operator(layout, "CompositorNodePosterize")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_filter_blur(Menu): class NODE_MT_compositor_node_filter_blur_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_filter_blur"
bl_label = "Blur" bl_label = "Blur"
menu_path = "Filter/Blur"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeBilateralblur") self.node_operator(layout, "CompositorNodeBilateralblur")
node_add_menu.add_node_type(layout, "CompositorNodeBlur") self.node_operator(layout, "CompositorNodeBlur")
node_add_menu.add_node_type(layout, "CompositorNodeBokehBlur") self.node_operator(layout, "CompositorNodeBokehBlur")
node_add_menu.add_node_type(layout, "CompositorNodeDefocus") self.node_operator(layout, "CompositorNodeDefocus")
node_add_menu.add_node_type(layout, "CompositorNodeDBlur") self.node_operator(layout, "CompositorNodeDBlur")
node_add_menu.add_node_type(layout, "CompositorNodeVecBlur") self.node_operator(layout, "CompositorNodeVecBlur")
node_add_menu.draw_assets_for_catalog(layout, "Filter/Blur") self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_group(Menu): class NODE_MT_compositor_node_keying_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_group"
bl_label = "Group"
def draw(self, context):
layout = self.layout
node_add_menu.draw_node_group_add_menu(context, layout)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_keying(Menu):
bl_idname = "NODE_MT_category_compositor_keying"
bl_label = "Keying" bl_label = "Keying"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeChannelMatte") self.node_operator(layout, "CompositorNodeChannelMatte")
node_add_menu.add_node_type(layout, "CompositorNodeChromaMatte") self.node_operator(layout, "CompositorNodeChromaMatte")
node_add_menu.add_node_type(layout, "CompositorNodeColorMatte") self.node_operator(layout, "CompositorNodeColorMatte")
node_add_menu.add_node_type(layout, "CompositorNodeColorSpill") self.node_operator(layout, "CompositorNodeColorSpill")
node_add_menu.add_node_type(layout, "CompositorNodeDiffMatte") self.node_operator(layout, "CompositorNodeDiffMatte")
node_add_menu.add_node_type(layout, "CompositorNodeDistanceMatte") self.node_operator(layout, "CompositorNodeDistanceMatte")
node_add_menu.add_node_type(layout, "CompositorNodeKeying") self.node_operator(layout, "CompositorNodeKeying")
node_add_menu.add_node_type(layout, "CompositorNodeKeyingScreen") self.node_operator(layout, "CompositorNodeKeyingScreen")
node_add_menu.add_node_type(layout, "CompositorNodeLumaMatte") self.node_operator(layout, "CompositorNodeLumaMatte")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_mask(Menu): class NODE_MT_compositor_node_mask_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_mask"
bl_label = "Mask" bl_label = "Mask"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeCryptomatteV2") self.node_operator(layout, "CompositorNodeCryptomatteV2")
node_add_menu.add_node_type(layout, "CompositorNodeCryptomatte") self.node_operator(layout, "CompositorNodeCryptomatte")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeBoxMask") self.node_operator(layout, "CompositorNodeBoxMask")
node_add_menu.add_node_type(layout, "CompositorNodeEllipseMask") self.node_operator(layout, "CompositorNodeEllipseMask")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDoubleEdgeMask") self.node_operator(layout, "CompositorNodeDoubleEdgeMask")
node_add_menu.add_node_type(layout, "CompositorNodeIDMask") self.node_operator(layout, "CompositorNodeIDMask")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_tracking(Menu): class NODE_MT_compositor_node_tracking_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_tracking"
bl_label = "Tracking" bl_label = "Tracking"
bl_translation_context = i18n_contexts.id_movieclip bl_translation_context = i18n_contexts.id_movieclip
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodePlaneTrackDeform") self.node_operator(layout, "CompositorNodePlaneTrackDeform")
node_add_menu.add_node_type(layout, "CompositorNodeStabilize") self.node_operator(layout, "CompositorNodeStabilize")
node_add_menu.add_node_type(layout, "CompositorNodeTrackPos") self.node_operator(layout, "CompositorNodeTrackPos")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_transform(Menu): class NODE_MT_compositor_node_transform_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_transform"
bl_label = "Transform" bl_label = "Transform"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRotate") self.node_operator(layout, "CompositorNodeRotate")
node_add_menu.add_node_type(layout, "CompositorNodeScale") self.node_operator(layout, "CompositorNodeScale")
node_add_menu.add_node_type(layout, "CompositorNodeTransform") self.node_operator(layout, "CompositorNodeTransform")
node_add_menu.add_node_type(layout, "CompositorNodeTranslate") self.node_operator(layout, "CompositorNodeTranslate")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeCornerPin") self.node_operator(layout, "CompositorNodeCornerPin")
node_add_menu.add_node_type(layout, "CompositorNodeCrop") self.node_operator(layout, "CompositorNodeCrop")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDisplace") self.node_operator(layout, "CompositorNodeDisplace")
node_add_menu.add_node_type(layout, "CompositorNodeFlip") self.node_operator(layout, "CompositorNodeFlip")
node_add_menu.add_node_type(layout, "CompositorNodeMapUV") self.node_operator(layout, "CompositorNodeMapUV")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeLensdist") self.node_operator(layout, "CompositorNodeLensdist")
node_add_menu.add_node_type(layout, "CompositorNodeMovieDistortion") self.node_operator(layout, "CompositorNodeMovieDistortion")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_texture(Menu): class NODE_MT_compositor_node_texture_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_texture"
bl_label = "Texture" bl_label = "Texture"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick") self.node_operator(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker") self.node_operator(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor") self.node_operator(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient") self.node_operator(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic") self.node_operator(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise") self.node_operator(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi") self.node_operator(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave") self.node_operator(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise") self.node_operator(layout, "ShaderNodeTexWhiteNoise")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_utilities(Menu): class NODE_MT_compositor_node_utilities_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_utilities"
bl_label = "Utilities" bl_label = "Utilities"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeMapRange") self.node_operator(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix") self.node_operator(layout, "ShaderNodeMix")
node_add_menu.add_node_type(layout, "ShaderNodeClamp") self.node_operator(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve") self.node_operator(layout, "ShaderNodeFloatCurve")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeLevels") self.node_operator(layout, "CompositorNodeLevels")
node_add_menu.add_node_type(layout, "CompositorNodeNormalize") self.node_operator(layout, "CompositorNodeNormalize")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeSplit") self.node_operator(layout, "CompositorNodeSplit")
node_add_menu.add_node_type(layout, "CompositorNodeSwitch") self.node_operator(layout, "CompositorNodeSwitch")
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch") self.node_operator(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type( self.node_operator(
layout, "CompositorNodeSwitchView", layout, "CompositorNodeSwitchView",
label="Switch Stereo View") label="Switch Stereo View")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeRelativeToPixel") self.node_operator(layout, "CompositorNodeRelativeToPixel")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_compositor_vector(Menu): class NODE_MT_compositor_node_vector_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_compositor_vector"
bl_label = "Vector" bl_label = "Vector"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ") self.node_operator(layout, "ShaderNodeCombineXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ") self.node_operator(layout, "ShaderNodeSeparateXYZ")
layout.separator() layout.separator()
props = node_add_menu.add_node_type(layout, "ShaderNodeMix", label="Mix Vector") props = self.node_operator(layout, "ShaderNodeMix", label="Mix Vector")
ops = props.settings.add() ops = props.settings.add()
ops.name = "data_type" ops.name = "data_type"
ops.value = "'VECTOR'" ops.value = "'VECTOR'"
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling") self.node_operator(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve") self.node_operator(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate") self.node_operator(layout, "ShaderNodeVectorRotate")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_compositor_node_add_all(Menu): class NODE_MT_compositor_node_all_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_compositor_node_add_all"
bl_label = "" bl_label = ""
menu_path = "Root"
bl_translation_context = i18n_contexts.operator_default
# NOTE: Menus are looked up via their label, this is so that both the Add
# & Swap menus can share the same layout while each using their
# corresponding menus
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_compositor_input") self.draw_menu(layout, "Input")
layout.menu("NODE_MT_category_compositor_output") self.draw_menu(layout, "Output")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_color") self.draw_menu(layout, "Color")
layout.menu("NODE_MT_category_compositor_filter") self.draw_menu(layout, "Filter")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_keying") self.draw_menu(layout, "Keying")
layout.menu("NODE_MT_category_compositor_mask") self.draw_menu(layout, "Mask")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_tracking") self.draw_menu(layout, "Tracking")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_texture") self.draw_menu(layout, "Texture")
layout.menu("NODE_MT_category_compositor_transform") self.draw_menu(layout, "Transform")
layout.menu("NODE_MT_category_compositor_utilities") self.draw_menu(layout, "Utilities")
layout.menu("NODE_MT_category_compositor_vector") self.draw_menu(layout, "Vector")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_compositor_group") self.draw_menu(layout, "Group")
layout.menu("NODE_MT_category_layout") self.draw_menu(layout, "Layout")
node_add_menu.draw_root_assets(layout) self.draw_root_assets(layout)
add_menus = {
# menu bl_idname: baseclass
"NODE_MT_category_compositor_input": NODE_MT_compositor_node_input_base,
"NODE_MT_category_compositor_input_constant": NODE_MT_compositor_node_input_constant_base,
"NODE_MT_category_compositor_input_scene": NODE_MT_compositor_node_input_scene_base,
"NODE_MT_category_compositor_output": NODE_MT_compositor_node_output_base,
"NODE_MT_category_compositor_color": NODE_MT_compositor_node_color_base,
"NODE_MT_category_compositor_color_adjust": NODE_MT_compositor_node_color_adjust_base,
"NODE_MT_category_compositor_color_mix": NODE_MT_compositor_node_color_mix_base,
"NODE_MT_category_compositor_filter": NODE_MT_compositor_node_filter_base,
"NODE_MT_category_compositor_filter_blur": NODE_MT_compositor_node_filter_blur_base,
"NODE_MT_category_compositor_texture": NODE_MT_compositor_node_texture_base,
"NODE_MT_category_compositor_keying": NODE_MT_compositor_node_keying_base,
"NODE_MT_category_compositor_mask": NODE_MT_compositor_node_mask_base,
"NODE_MT_category_compositor_tracking": NODE_MT_compositor_node_tracking_base,
"NODE_MT_category_compositor_transform": NODE_MT_compositor_node_transform_base,
"NODE_MT_category_compositor_utilities": NODE_MT_compositor_node_utilities_base,
"NODE_MT_category_compositor_vector": NODE_MT_compositor_node_vector_base,
"NODE_MT_compositor_node_add_all": NODE_MT_compositor_node_all_base,
}
add_menus = node_add_menu.generate_menus(
add_menus,
template=node_add_menu.AddNodeMenu,
base_dict=node_add_menu.add_base_pathing_dict
)
swap_menus = {
# menu bl_idname: baseclass
"NODE_MT_compositor_node_input_swap": NODE_MT_compositor_node_input_base,
"NODE_MT_compositor_node_input_constant_swap": NODE_MT_compositor_node_input_constant_base,
"NODE_MT_compositor_node_input_scene_swap": NODE_MT_compositor_node_input_scene_base,
"NODE_MT_compositor_node_output_swap": NODE_MT_compositor_node_output_base,
"NODE_MT_compositor_node_color_swap": NODE_MT_compositor_node_color_base,
"NODE_MT_compositor_node_color_adjust_swap": NODE_MT_compositor_node_color_adjust_base,
"NODE_MT_compositor_node_color_mix_swap": NODE_MT_compositor_node_color_mix_base,
"NODE_MT_compositor_node_filter_swap": NODE_MT_compositor_node_filter_base,
"NODE_MT_compositor_node_filter_blur_swap": NODE_MT_compositor_node_filter_blur_base,
"NODE_MT_compositor_node_texture_swap": NODE_MT_compositor_node_texture_base,
"NODE_MT_compositor_node_keying_swap": NODE_MT_compositor_node_keying_base,
"NODE_MT_compositor_node_mask_swap": NODE_MT_compositor_node_mask_base,
"NODE_MT_compositor_node_tracking_swap": NODE_MT_compositor_node_tracking_base,
"NODE_MT_compositor_node_transform_swap": NODE_MT_compositor_node_transform_base,
"NODE_MT_compositor_node_utilities_swap": NODE_MT_compositor_node_utilities_base,
"NODE_MT_compositor_node_vector_swap": NODE_MT_compositor_node_vector_base,
"NODE_MT_compositor_node_swap_all": NODE_MT_compositor_node_all_base,
}
swap_menus = node_add_menu.generate_menus(
swap_menus,
template=node_add_menu.SwapNodeMenu,
base_dict=node_add_menu.swap_base_pathing_dict
)
classes = ( classes = (
NODE_MT_compositor_node_add_all, *add_menus,
NODE_MT_category_compositor_input, *swap_menus,
NODE_MT_category_compositor_input_constant,
NODE_MT_category_compositor_input_scene,
NODE_MT_category_compositor_output,
NODE_MT_category_compositor_color,
NODE_MT_category_compositor_color_adjust,
NODE_MT_category_compositor_color_mix,
NODE_MT_category_compositor_filter,
NODE_MT_category_compositor_filter_blur,
NODE_MT_category_compositor_texture,
NODE_MT_category_compositor_keying,
NODE_MT_category_compositor_mask,
NODE_MT_category_compositor_tracking,
NODE_MT_category_compositor_transform,
NODE_MT_category_compositor_utilities,
NODE_MT_category_compositor_vector,
NODE_MT_category_compositor_group,
) )
if __name__ == "__main__": # only for live edit. if __name__ == "__main__": # only for live edit.

View File

@@ -10,336 +10,352 @@ from bpy.app.translations import (
) )
class NODE_MT_geometry_node_GEO_ATTRIBUTE(Menu): class NODE_MT_gn_attribute_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_ATTRIBUTE"
bl_label = "Attribute" bl_label = "Attribute"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeAttributeStatistic") self.node_operator(layout, "GeometryNodeAttributeStatistic")
node_add_menu.add_node_type(layout, "GeometryNodeAttributeDomainSize") self.node_operator(layout, "GeometryNodeAttributeDomainSize")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeBlurAttribute") self.node_operator(layout, "GeometryNodeBlurAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeCaptureAttribute") self.node_operator(layout, "GeometryNodeCaptureAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeRemoveAttribute") self.node_operator(layout, "GeometryNodeRemoveAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeStoreNamedAttribute", search_weight=1.0) self.node_operator(layout, "GeometryNodeStoreNamedAttribute", search_weight=1.0)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_COLOR(Menu): class NODE_MT_gn_utilities_color_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_COLOR"
bl_label = "Color" bl_label = "Color"
menu_path = "Utilities/Color"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody") self.node_operator(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeGamma") self.node_operator(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB") self.node_operator(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "ShaderNodeRGBCurve") self.node_operator(layout, "ShaderNodeRGBCurve")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeCombineColor") self.node_operator(layout, "FunctionNodeCombineColor")
node_add_menu.add_color_mix_node(context, layout) self.color_mix_node(context, layout)
node_add_menu.add_node_type(layout, "FunctionNodeSeparateColor") self.node_operator(layout, "FunctionNodeSeparateColor")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Color")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE(Menu): class NODE_MT_gn_curve_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE"
bl_label = "Curve" bl_label = "Curve"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_CURVE_READ") self.draw_menu(layout, path="Curve/Read")
layout.menu("NODE_MT_geometry_node_GEO_CURVE_SAMPLE") self.draw_menu(layout, path="Curve/Sample")
layout.menu("NODE_MT_geometry_node_GEO_CURVE_WRITE") self.draw_menu(layout, path="Curve/Write")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_CURVE_OPERATIONS") self.draw_menu(layout, path="Curve/Operations")
layout.menu("NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE") self.draw_menu(layout, path="Curve/Primitives")
layout.menu("NODE_MT_geometry_node_curve_topology") self.draw_menu(layout, path="Curve/Topology")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_CURVE_READ(Menu): class NODE_MT_gn_curve_read_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_READ"
bl_label = "Read" bl_label = "Read"
menu_path = "Curve/Read"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveHandlePositions") self.node_operator(layout, "GeometryNodeInputCurveHandlePositions")
node_add_menu.add_node_type(layout, "GeometryNodeCurveLength") self.node_operator(layout, "GeometryNodeCurveLength")
node_add_menu.add_node_type(layout, "GeometryNodeInputTangent") self.node_operator(layout, "GeometryNodeInputTangent")
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveTilt") self.node_operator(layout, "GeometryNodeInputCurveTilt")
node_add_menu.add_node_type(layout, "GeometryNodeCurveEndpointSelection") self.node_operator(layout, "GeometryNodeCurveEndpointSelection")
node_add_menu.add_node_type(layout, "GeometryNodeCurveHandleTypeSelection") self.node_operator(layout, "GeometryNodeCurveHandleTypeSelection")
node_add_menu.add_node_type(layout, "GeometryNodeInputSplineCyclic") self.node_operator(layout, "GeometryNodeInputSplineCyclic")
node_add_menu.add_node_type(layout, "GeometryNodeSplineLength") self.node_operator(layout, "GeometryNodeSplineLength")
node_add_menu.add_node_type(layout, "GeometryNodeSplineParameter") self.node_operator(layout, "GeometryNodeSplineParameter")
node_add_menu.add_node_type(layout, "GeometryNodeInputSplineResolution") self.node_operator(layout, "GeometryNodeInputSplineResolution")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Read")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_SAMPLE(Menu): class NODE_MT_gn_curve_sample_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_SAMPLE"
bl_label = "Sample" bl_label = "Sample"
menu_path = "Curve/Sample"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleCurve") self.node_operator(layout, "GeometryNodeSampleCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Sample")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_WRITE(Menu): class NODE_MT_gn_curve_write_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_WRITE"
bl_label = "Write" bl_label = "Write"
menu_path = "Curve/Write"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveNormal") self.node_operator(layout, "GeometryNodeSetCurveNormal")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveRadius") self.node_operator(layout, "GeometryNodeSetCurveRadius")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveTilt") self.node_operator(layout, "GeometryNodeSetCurveTilt")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveHandlePositions") self.node_operator(layout, "GeometryNodeSetCurveHandlePositions")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSetHandles") self.node_operator(layout, "GeometryNodeCurveSetHandles")
node_add_menu.add_node_type(layout, "GeometryNodeSetSplineCyclic") self.node_operator(layout, "GeometryNodeSetSplineCyclic")
node_add_menu.add_node_type(layout, "GeometryNodeSetSplineResolution") self.node_operator(layout, "GeometryNodeSetSplineResolution")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSplineType") self.node_operator(layout, "GeometryNodeCurveSplineType")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Write")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_OPERATIONS(Menu): class NODE_MT_gn_curve_operations_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_OPERATIONS"
bl_label = "Operations" bl_label = "Operations"
menu_path = "Curve/Operations"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveToMesh") self.node_operator(layout, "GeometryNodeCurveToMesh")
node_add_menu.add_node_type(layout, "GeometryNodeCurveToPoints") self.node_operator(layout, "GeometryNodeCurveToPoints")
node_add_menu.add_node_type(layout, "GeometryNodeCurvesToGreasePencil") self.node_operator(layout, "GeometryNodeCurvesToGreasePencil")
node_add_menu.add_node_type(layout, "GeometryNodeDeformCurvesOnSurface") self.node_operator(layout, "GeometryNodeDeformCurvesOnSurface")
node_add_menu.add_node_type(layout, "GeometryNodeFillCurve") self.node_operator(layout, "GeometryNodeFillCurve")
node_add_menu.add_node_type(layout, "GeometryNodeFilletCurve") self.node_operator(layout, "GeometryNodeFilletCurve")
node_add_menu.add_node_type(layout, "GeometryNodeInterpolateCurves") self.node_operator(layout, "GeometryNodeInterpolateCurves")
node_add_menu.add_node_type(layout, "GeometryNodeResampleCurve") self.node_operator(layout, "GeometryNodeResampleCurve")
node_add_menu.add_node_type(layout, "GeometryNodeReverseCurve") self.node_operator(layout, "GeometryNodeReverseCurve")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivideCurve") self.node_operator(layout, "GeometryNodeSubdivideCurve")
node_add_menu.add_node_type(layout, "GeometryNodeTrimCurve") self.node_operator(layout, "GeometryNodeTrimCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Operations")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE(Menu): class NODE_MT_gn_curve_primitives_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE"
bl_label = "Primitives" bl_label = "Primitives"
menu_path = "Curve/Primitives"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveArc") self.node_operator(layout, "GeometryNodeCurveArc")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveBezierSegment") self.node_operator(layout, "GeometryNodeCurvePrimitiveBezierSegment")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveCircle") self.node_operator(layout, "GeometryNodeCurvePrimitiveCircle")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveLine") self.node_operator(layout, "GeometryNodeCurvePrimitiveLine")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSpiral") self.node_operator(layout, "GeometryNodeCurveSpiral")
node_add_menu.add_node_type(layout, "GeometryNodeCurveQuadraticBezier") self.node_operator(layout, "GeometryNodeCurveQuadraticBezier")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveQuadrilateral") self.node_operator(layout, "GeometryNodeCurvePrimitiveQuadrilateral")
node_add_menu.add_node_type(layout, "GeometryNodeCurveStar") self.node_operator(layout, "GeometryNodeCurveStar")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Primitives")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_curve_topology(Menu): class NODE_MT_gn_curve_topology_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_curve_topology"
bl_label = "Topology" bl_label = "Topology"
menu_path = "Curve/Topology"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveOfPoint") self.node_operator(layout, "GeometryNodeCurveOfPoint")
node_add_menu.add_node_type(layout, "GeometryNodeOffsetPointInCurve") self.node_operator(layout, "GeometryNodeOffsetPointInCurve")
node_add_menu.add_node_type(layout, "GeometryNodePointsOfCurve") self.node_operator(layout, "GeometryNodePointsOfCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Topology")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_read(Menu): class NODE_MT_gn_grease_pencil_read_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_read"
bl_label = "Read" bl_label = "Read"
menu_path = "Grease Pencil/Read"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputNamedLayerSelection") self.node_operator(layout, "GeometryNodeInputNamedLayerSelection")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Read")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_write(Menu): class NODE_MT_gn_grease_pencil_write_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_write"
bl_label = "Write" bl_label = "Write"
menu_path = "Grease Pencil/Write"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilColor") self.node_operator(layout, "GeometryNodeSetGreasePencilColor")
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilDepth") self.node_operator(layout, "GeometryNodeSetGreasePencilDepth")
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilSoftness") self.node_operator(layout, "GeometryNodeSetGreasePencilSoftness")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Write")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_operations(Menu): class NODE_MT_gn_grease_pencil_operations_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_operations"
bl_label = "Operations" bl_label = "Operations"
menu_path = "Grease Pencil/Operations"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGreasePencilToCurves") self.node_operator(layout, "GeometryNodeGreasePencilToCurves")
node_add_menu.add_node_type(layout, "GeometryNodeMergeLayers") self.node_operator(layout, "GeometryNodeMergeLayers")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Operations")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil(Menu): class NODE_MT_gn_grease_pencil_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_grease_pencil"
bl_label = "Grease Pencil" bl_label = "Grease Pencil"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_grease_pencil_read") self.draw_menu(layout, path="Grease Pencil/Read")
layout.menu("NODE_MT_geometry_node_grease_pencil_write") self.draw_menu(layout, path="Grease Pencil/Write")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_grease_pencil_operations") self.draw_menu(layout, path="Grease Pencil/Operations")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_GEOMETRY(Menu): class NODE_MT_gn_geometry_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY"
bl_label = "Geometry" bl_label = "Geometry"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_READ") self.draw_menu(layout, path="Geometry/Read")
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE") self.draw_menu(layout, path="Geometry/Sample")
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_WRITE") self.draw_menu(layout, path="Geometry/Write")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS") self.draw_menu(layout, path="Geometry/Operations")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeGeometryToInstance") self.node_operator(layout, "GeometryNodeGeometryToInstance")
node_add_menu.add_node_type(layout, "GeometryNodeJoinGeometry", search_weight=1.0) self.node_operator(layout, "GeometryNodeJoinGeometry", search_weight=1.0)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_GEOMETRY_READ(Menu): class NODE_MT_gn_geometry_read_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_READ"
bl_label = "Read" bl_label = "Read"
menu_path = "Geometry/Read"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputID") self.node_operator(layout, "GeometryNodeInputID")
node_add_menu.add_node_type(layout, "GeometryNodeInputIndex") self.node_operator(layout, "GeometryNodeInputIndex")
node_add_menu.add_node_type(layout, "GeometryNodeInputNamedAttribute", search_weight=1.0) self.node_operator(layout, "GeometryNodeInputNamedAttribute", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeInputNormal") self.node_operator(layout, "GeometryNodeInputNormal")
node_add_menu.add_node_type(layout, "GeometryNodeInputPosition", search_weight=1.0) self.node_operator(layout, "GeometryNodeInputPosition", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeInputRadius") self.node_operator(layout, "GeometryNodeInputRadius")
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSelection") self.node_operator(layout, "GeometryNodeToolSelection")
node_add_menu.add_node_type(layout, "GeometryNodeToolActiveElement") self.node_operator(layout, "GeometryNodeToolActiveElement")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Read")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_WRITE(Menu): class NODE_MT_gn_geometry_write_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_WRITE"
bl_label = "Write" bl_label = "Write"
menu_path = "Geometry/Write"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetGeometryName") self.node_operator(layout, "GeometryNodeSetGeometryName")
node_add_menu.add_node_type(layout, "GeometryNodeSetID") self.node_operator(layout, "GeometryNodeSetID")
node_add_menu.add_node_type(layout, "GeometryNodeSetPosition", search_weight=1.0) self.node_operator(layout, "GeometryNodeSetPosition", search_weight=1.0)
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSetSelection") self.node_operator(layout, "GeometryNodeToolSetSelection")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Write")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS(Menu): class NODE_MT_gn_geometry_operations_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS"
bl_label = "Operations" bl_label = "Operations"
menu_path = "Geometry/Operations"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeBake") self.node_operator(layout, "GeometryNodeBake")
node_add_menu.add_node_type(layout, "GeometryNodeBoundBox") self.node_operator(layout, "GeometryNodeBoundBox")
node_add_menu.add_node_type(layout, "GeometryNodeConvexHull") self.node_operator(layout, "GeometryNodeConvexHull")
node_add_menu.add_node_type(layout, "GeometryNodeDeleteGeometry") self.node_operator(layout, "GeometryNodeDeleteGeometry")
node_add_menu.add_node_type(layout, "GeometryNodeDuplicateElements") self.node_operator(layout, "GeometryNodeDuplicateElements")
node_add_menu.add_node_type(layout, "GeometryNodeMergeByDistance") self.node_operator(layout, "GeometryNodeMergeByDistance")
node_add_menu.add_node_type(layout, "GeometryNodeSortElements") self.node_operator(layout, "GeometryNodeSortElements")
node_add_menu.add_node_type(layout, "GeometryNodeTransform", search_weight=1.0) self.node_operator(layout, "GeometryNodeTransform", search_weight=1.0)
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSeparateComponents") self.node_operator(layout, "GeometryNodeSeparateComponents")
node_add_menu.add_node_type(layout, "GeometryNodeSeparateGeometry") self.node_operator(layout, "GeometryNodeSeparateGeometry")
node_add_menu.add_node_type(layout, "GeometryNodeSplitToInstances") self.node_operator(layout, "GeometryNodeSplitToInstances")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Operations")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE(Menu): class NODE_MT_gn_geometry_sample_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE"
bl_label = "Sample" bl_label = "Sample"
menu_path = "Geometry/Sample"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeProximity") self.node_operator(layout, "GeometryNodeProximity")
node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest") self.node_operator(layout, "GeometryNodeIndexOfNearest")
node_add_menu.add_node_type(layout, "GeometryNodeRaycast") self.node_operator(layout, "GeometryNodeRaycast")
node_add_menu.add_node_type(layout, "GeometryNodeSampleIndex") self.node_operator(layout, "GeometryNodeSampleIndex")
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearest") self.node_operator(layout, "GeometryNodeSampleNearest")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Sample")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT(Menu): class NODE_MT_gn_input_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT"
bl_label = "Input" bl_label = "Input"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_INPUT_CONSTANT") self.draw_menu(layout, path="Input/Constant")
if context.space_data.node_tree_sub_type != 'TOOL': if context.space_data.node_tree_sub_type != 'TOOL':
layout.menu("NODE_MT_geometry_node_GEO_INPUT_GIZMO") self.draw_menu(layout, path="Input/Gizmo")
layout.menu("NODE_MT_geometry_node_GEO_INPUT_GROUP") self.draw_menu(layout, path="Input/Group")
layout.menu("NODE_MT_category_import") self.draw_menu(layout, path="Input/Import")
layout.menu("NODE_MT_geometry_node_GEO_INPUT_SCENE") self.draw_menu(layout, path="Input/Scene")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_INPUT_CONSTANT(Menu): class NODE_MT_gn_input_constant_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_CONSTANT"
bl_label = "Constant" bl_label = "Constant"
bl_translation_context = i18n_contexts.id_nodetree bl_translation_context = i18n_contexts.id_nodetree
menu_path = "Input/Constant"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeInputBool") self.node_operator(layout, "FunctionNodeInputBool")
node_add_menu.add_node_type(layout, "GeometryNodeInputCollection") self.node_operator(layout, "GeometryNodeInputCollection")
node_add_menu.add_node_type(layout, "FunctionNodeInputColor") self.node_operator(layout, "FunctionNodeInputColor")
node_add_menu.add_node_type(layout, "GeometryNodeInputImage") self.node_operator(layout, "GeometryNodeInputImage")
node_add_menu.add_node_type(layout, "FunctionNodeInputInt") self.node_operator(layout, "FunctionNodeInputInt")
node_add_menu.add_node_type(layout, "GeometryNodeInputMaterial") self.node_operator(layout, "GeometryNodeInputMaterial")
node_add_menu.add_node_type(layout, "GeometryNodeInputObject") self.node_operator(layout, "GeometryNodeInputObject")
node_add_menu.add_node_type(layout, "FunctionNodeInputRotation") self.node_operator(layout, "FunctionNodeInputRotation")
node_add_menu.add_node_type(layout, "FunctionNodeInputString") self.node_operator(layout, "FunctionNodeInputString")
node_add_menu.add_node_type(layout, "ShaderNodeValue") self.node_operator(layout, "ShaderNodeValue")
node_add_menu.add_node_type(layout, "FunctionNodeInputVector") self.node_operator(layout, "FunctionNodeInputVector")
node_add_menu.draw_assets_for_catalog(layout, "Input/Constant")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT_GROUP(Menu): class NODE_MT_gn_input_group_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_GROUP"
bl_label = "Group" bl_label = "Group"
menu_path = "Input/Group"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupInput") self.node_operator(layout, "NodeGroupInput")
node_add_menu.draw_assets_for_catalog(layout, "Input/Group")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu): class NODE_MT_gn_input_scene_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_SCENE"
bl_label = "Scene" bl_label = "Scene"
menu_path = "Input/Scene"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeTool3DCursor") self.node_operator(layout, "GeometryNodeTool3DCursor")
node_add_menu.add_node_type(layout, "GeometryNodeInputActiveCamera") self.node_operator(layout, "GeometryNodeInputActiveCamera")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, context,
layout, layout,
"GeometryNodeCameraInfo", "GeometryNodeCameraInfo",
@@ -355,655 +371,752 @@ class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu):
"Orthographic Scale", "Orthographic Scale",
], ],
) )
node_add_menu.add_node_type(layout, "GeometryNodeCollectionInfo") self.node_operator(layout, "GeometryNodeCollectionInfo")
node_add_menu.add_node_type(layout, "GeometryNodeImageInfo") self.node_operator(layout, "GeometryNodeImageInfo")
node_add_menu.add_node_type(layout, "GeometryNodeIsViewport") self.node_operator(layout, "GeometryNodeIsViewport")
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "GeometryNodeToolMousePosition", context, layout, "GeometryNodeToolMousePosition",
["Mouse X", "Mouse Y", "Region Width", "Region Height"], ["Mouse X", "Mouse Y", "Region Width", "Region Height"],
) )
node_add_menu.add_node_type(layout, "GeometryNodeObjectInfo") self.node_operator(layout, "GeometryNodeObjectInfo")
node_add_menu.add_node_type_with_outputs(context, layout, "GeometryNodeInputSceneTime", ["Frame", "Seconds"]) self.node_operator_with_outputs(context, layout, "GeometryNodeInputSceneTime", ["Frame", "Seconds"])
node_add_menu.add_node_type(layout, "GeometryNodeSelfObject") self.node_operator(layout, "GeometryNodeSelfObject")
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "GeometryNodeViewportTransform", context, layout, "GeometryNodeViewportTransform",
["Projection", "View", "Is Orthographic"], ["Projection", "View", "Is Orthographic"],
) )
node_add_menu.draw_assets_for_catalog(layout, "Input/Scene")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT_GIZMO(Menu): class NODE_MT_gn_input_gizmo_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_GIZMO"
bl_label = "Gizmo" bl_label = "Gizmo"
menu_path = "Input/Gizmo"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGizmoDial") self.node_operator(layout, "GeometryNodeGizmoDial")
node_add_menu.add_node_type(layout, "GeometryNodeGizmoLinear") self.node_operator(layout, "GeometryNodeGizmoLinear")
node_add_menu.add_node_type(layout, "GeometryNodeGizmoTransform") self.node_operator(layout, "GeometryNodeGizmoTransform")
node_add_menu.draw_assets_for_catalog(layout, "Input/Gizmo")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INSTANCE(Menu): class NODE_MT_gn_instance_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_INSTANCE"
bl_label = "Instances" bl_label = "Instances"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInstanceOnPoints", search_weight=2.0) self.node_operator(layout, "GeometryNodeInstanceOnPoints", search_weight=2.0)
node_add_menu.add_node_type(layout, "GeometryNodeInstancesToPoints") self.node_operator(layout, "GeometryNodeInstancesToPoints")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeRealizeInstances", search_weight=1.0) self.node_operator(layout, "GeometryNodeRealizeInstances", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeRotateInstances") self.node_operator(layout, "GeometryNodeRotateInstances")
node_add_menu.add_node_type(layout, "GeometryNodeScaleInstances") self.node_operator(layout, "GeometryNodeScaleInstances")
node_add_menu.add_node_type(layout, "GeometryNodeTranslateInstances") self.node_operator(layout, "GeometryNodeTranslateInstances")
node_add_menu.add_node_type(layout, "GeometryNodeSetInstanceTransform") self.node_operator(layout, "GeometryNodeSetInstanceTransform")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceBounds") self.node_operator(layout, "GeometryNodeInputInstanceBounds")
node_add_menu.add_node_type(layout, "GeometryNodeInstanceTransform") self.node_operator(layout, "GeometryNodeInstanceTransform")
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceRotation") self.node_operator(layout, "GeometryNodeInputInstanceRotation")
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceScale") self.node_operator(layout, "GeometryNodeInputInstanceScale")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MATERIAL(Menu): class NODE_MT_gn_material_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MATERIAL"
bl_label = "Material" bl_label = "Material"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeReplaceMaterial") self.node_operator(layout, "GeometryNodeReplaceMaterial")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeInputMaterialIndex") self.node_operator(layout, "GeometryNodeInputMaterialIndex")
node_add_menu.add_node_type(layout, "GeometryNodeMaterialSelection") self.node_operator(layout, "GeometryNodeMaterialSelection")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSetMaterial", search_weight=1.0) self.node_operator(layout, "GeometryNodeSetMaterial", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeSetMaterialIndex") self.node_operator(layout, "GeometryNodeSetMaterialIndex")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MESH(Menu): class NODE_MT_gn_mesh_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH"
bl_label = "Mesh" bl_label = "Mesh"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_MESH_READ") self.draw_menu(layout, path="Mesh/Read")
layout.menu("NODE_MT_geometry_node_GEO_MESH_SAMPLE") self.draw_menu(layout, path="Mesh/Sample")
layout.menu("NODE_MT_geometry_node_GEO_MESH_WRITE") self.draw_menu(layout, path="Mesh/Write")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_MESH_OPERATIONS") self.draw_menu(layout, path="Mesh/Operations")
layout.menu("NODE_MT_category_PRIMITIVES_MESH") self.draw_menu(layout, path="Mesh/Primitives")
layout.menu("NODE_MT_geometry_node_mesh_topology") self.draw_menu(layout, path="Mesh/Topology")
layout.menu("NODE_MT_category_GEO_UV") self.draw_menu(layout, path="Mesh/UV")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MESH_READ(Menu): class NODE_MT_gn_mesh_read_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_READ"
bl_label = "Read" bl_label = "Read"
menu_path = "Mesh/Read"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeAngle") self.node_operator(layout, "GeometryNodeInputMeshEdgeAngle")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeNeighbors") self.node_operator(layout, "GeometryNodeInputMeshEdgeNeighbors")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeVertices") self.node_operator(layout, "GeometryNodeInputMeshEdgeVertices")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesToFaceGroups") self.node_operator(layout, "GeometryNodeEdgesToFaceGroups")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceArea") self.node_operator(layout, "GeometryNodeInputMeshFaceArea")
node_add_menu.add_node_type(layout, "GeometryNodeMeshFaceSetBoundaries") self.node_operator(layout, "GeometryNodeMeshFaceSetBoundaries")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceNeighbors") self.node_operator(layout, "GeometryNodeInputMeshFaceNeighbors")
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolFaceSet") self.node_operator(layout, "GeometryNodeToolFaceSet")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceIsPlanar") self.node_operator(layout, "GeometryNodeInputMeshFaceIsPlanar")
node_add_menu.add_node_type(layout, "GeometryNodeInputShadeSmooth") self.node_operator(layout, "GeometryNodeInputShadeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputEdgeSmooth") self.node_operator(layout, "GeometryNodeInputEdgeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshIsland") self.node_operator(layout, "GeometryNodeInputMeshIsland")
node_add_menu.add_node_type(layout, "GeometryNodeInputShortestEdgePaths") self.node_operator(layout, "GeometryNodeInputShortestEdgePaths")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshVertexNeighbors") self.node_operator(layout, "GeometryNodeInputMeshVertexNeighbors")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Read")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_SAMPLE(Menu): class NODE_MT_gn_mesh_sample_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_SAMPLE"
bl_label = "Sample" bl_label = "Sample"
menu_path = "Mesh/Sample"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearestSurface") self.node_operator(layout, "GeometryNodeSampleNearestSurface")
node_add_menu.add_node_type(layout, "GeometryNodeSampleUVSurface") self.node_operator(layout, "GeometryNodeSampleUVSurface")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Sample")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_WRITE(Menu): class NODE_MT_gn_mesh_write_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_WRITE"
bl_label = "Write" bl_label = "Write"
menu_path = "Mesh/Write"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
if context.space_data.node_tree_sub_type == 'TOOL': if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSetFaceSet") self.node_operator(layout, "GeometryNodeToolSetFaceSet")
node_add_menu.add_node_type(layout, "GeometryNodeSetMeshNormal") self.node_operator(layout, "GeometryNodeSetMeshNormal")
node_add_menu.add_node_type(layout, "GeometryNodeSetShadeSmooth") self.node_operator(layout, "GeometryNodeSetShadeSmooth")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Write")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_OPERATIONS(Menu): class NODE_MT_gn_mesh_operations_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_OPERATIONS"
bl_label = "Operations" bl_label = "Operations"
menu_path = "Mesh/Operations"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeDualMesh") self.node_operator(layout, "GeometryNodeDualMesh")
node_add_menu.add_node_type(layout, "GeometryNodeEdgePathsToCurves") self.node_operator(layout, "GeometryNodeEdgePathsToCurves")
node_add_menu.add_node_type(layout, "GeometryNodeEdgePathsToSelection") self.node_operator(layout, "GeometryNodeEdgePathsToSelection")
node_add_menu.add_node_type(layout, "GeometryNodeExtrudeMesh") self.node_operator(layout, "GeometryNodeExtrudeMesh")
node_add_menu.add_node_type(layout, "GeometryNodeFlipFaces") self.node_operator(layout, "GeometryNodeFlipFaces")
node_add_menu.add_node_type(layout, "GeometryNodeMeshBoolean") self.node_operator(layout, "GeometryNodeMeshBoolean")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToCurve") self.node_operator(layout, "GeometryNodeMeshToCurve")
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeMeshToDensityGrid") self.node_operator(layout, "GeometryNodeMeshToDensityGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToPoints") self.node_operator(layout, "GeometryNodeMeshToPoints")
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeMeshToSDFGrid") self.node_operator(layout, "GeometryNodeMeshToSDFGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToVolume") self.node_operator(layout, "GeometryNodeMeshToVolume")
node_add_menu.add_node_type(layout, "GeometryNodeScaleElements") self.node_operator(layout, "GeometryNodeScaleElements")
node_add_menu.add_node_type(layout, "GeometryNodeSplitEdges") self.node_operator(layout, "GeometryNodeSplitEdges")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivideMesh") self.node_operator(layout, "GeometryNodeSubdivideMesh")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivisionSurface") self.node_operator(layout, "GeometryNodeSubdivisionSurface")
node_add_menu.add_node_type(layout, "GeometryNodeTriangulate") self.node_operator(layout, "GeometryNodeTriangulate")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Operations")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_PRIMITIVES_MESH(Menu): class NODE_MT_gn_mesh_primitives_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_PRIMITIVES_MESH"
bl_label = "Primitives" bl_label = "Primitives"
menu_path = "Mesh/Primitives"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeMeshCone") self.node_operator(layout, "GeometryNodeMeshCone")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCube") self.node_operator(layout, "GeometryNodeMeshCube")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCylinder") self.node_operator(layout, "GeometryNodeMeshCylinder")
node_add_menu.add_node_type(layout, "GeometryNodeMeshGrid") self.node_operator(layout, "GeometryNodeMeshGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshIcoSphere") self.node_operator(layout, "GeometryNodeMeshIcoSphere")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCircle") self.node_operator(layout, "GeometryNodeMeshCircle")
node_add_menu.add_node_type(layout, "GeometryNodeMeshLine") self.node_operator(layout, "GeometryNodeMeshLine")
node_add_menu.add_node_type(layout, "GeometryNodeMeshUVSphere") self.node_operator(layout, "GeometryNodeMeshUVSphere")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Primitives")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_import(Menu): class NODE_MT_gn_input_import_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_import"
bl_label = "Import" bl_label = "Import"
menu_path = "Input/Import"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeImportCSV", label="CSV (.csv)") self.node_operator(layout, "GeometryNodeImportCSV", label="CSV (.csv)")
node_add_menu.add_node_type(layout, "GeometryNodeImportOBJ", label="Wavefront (.obj)") self.node_operator(layout, "GeometryNodeImportOBJ", label="Wavefront (.obj)")
node_add_menu.add_node_type(layout, "GeometryNodeImportPLY", label="Stanford PLY (.ply)") self.node_operator(layout, "GeometryNodeImportPLY", label="Stanford PLY (.ply)")
node_add_menu.add_node_type(layout, "GeometryNodeImportSTL", label="STL (.stl)") self.node_operator(layout, "GeometryNodeImportSTL", label="STL (.stl)")
node_add_menu.add_node_type(layout, "GeometryNodeImportText", label="Text (.txt)") self.node_operator(layout, "GeometryNodeImportText", label="Text (.txt)")
node_add_menu.add_node_type(layout, "GeometryNodeImportVDB", label="OpenVDB (.vdb)") self.node_operator(layout, "GeometryNodeImportVDB", label="OpenVDB (.vdb)")
node_add_menu.draw_assets_for_catalog(layout, "Input/Import")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_mesh_topology(Menu): class NODE_MT_gn_mesh_topology_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_mesh_topology"
bl_label = "Topology" bl_label = "Topology"
menu_path = "Mesh/Topology"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfEdge") self.node_operator(layout, "GeometryNodeCornersOfEdge")
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfFace") self.node_operator(layout, "GeometryNodeCornersOfFace")
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfVertex") self.node_operator(layout, "GeometryNodeCornersOfVertex")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfCorner") self.node_operator(layout, "GeometryNodeEdgesOfCorner")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfVertex") self.node_operator(layout, "GeometryNodeEdgesOfVertex")
node_add_menu.add_node_type(layout, "GeometryNodeFaceOfCorner") self.node_operator(layout, "GeometryNodeFaceOfCorner")
node_add_menu.add_node_type(layout, "GeometryNodeOffsetCornerInFace") self.node_operator(layout, "GeometryNodeOffsetCornerInFace")
node_add_menu.add_node_type(layout, "GeometryNodeVertexOfCorner") self.node_operator(layout, "GeometryNodeVertexOfCorner")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Topology")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_OUTPUT(Menu): class NODE_MT_gn_output_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_OUTPUT"
bl_label = "Output" bl_label = "Output"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupOutput") self.node_operator(layout, "NodeGroupOutput")
node_add_menu.add_node_type(layout, "GeometryNodeViewer") self.node_operator(layout, "GeometryNodeViewer")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "GeometryNodeWarning", "warning_type") self.node_operator_with_searchable_enum(context, layout, "GeometryNodeWarning", "warning_type")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_POINT(Menu): class NODE_MT_gn_point_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_POINT"
bl_label = "Point" bl_label = "Point"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInVolume") self.node_operator(layout, "GeometryNodeDistributePointsInVolume")
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInGrid") self.node_operator(layout, "GeometryNodeDistributePointsInGrid")
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsOnFaces") self.node_operator(layout, "GeometryNodeDistributePointsOnFaces")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodePoints") self.node_operator(layout, "GeometryNodePoints")
node_add_menu.add_node_type(layout, "GeometryNodePointsToCurves") self.node_operator(layout, "GeometryNodePointsToCurves")
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodePointsToSDFGrid") self.node_operator(layout, "GeometryNodePointsToSDFGrid")
node_add_menu.add_node_type(layout, "GeometryNodePointsToVertices") self.node_operator(layout, "GeometryNodePointsToVertices")
node_add_menu.add_node_type(layout, "GeometryNodePointsToVolume") self.node_operator(layout, "GeometryNodePointsToVolume")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSetPointRadius") self.node_operator(layout, "GeometryNodeSetPointRadius")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_simulation(Menu): class NODE_MT_gn_simulation_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_simulation"
bl_label = "Simulation" bl_label = "Simulation"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_simulation_zone(layout, label="Simulation") self.simulation_zone(layout, label="Simulation")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_TEXT(Menu): class NODE_MT_gn_utilities_text_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_TEXT"
bl_label = "Text" bl_label = "Text"
menu_path = "Utilities/Text"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeFormatString") self.node_operator(layout, "FunctionNodeFormatString")
node_add_menu.add_node_type(layout, "GeometryNodeStringJoin") self.node_operator(layout, "GeometryNodeStringJoin")
node_add_menu.add_node_type(layout, "FunctionNodeMatchString") self.node_operator(layout, "FunctionNodeMatchString")
node_add_menu.add_node_type(layout, "FunctionNodeReplaceString") self.node_operator(layout, "FunctionNodeReplaceString")
node_add_menu.add_node_type(layout, "FunctionNodeSliceString") self.node_operator(layout, "FunctionNodeSliceString")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeFindInString") self.node_operator(layout, "FunctionNodeFindInString")
node_add_menu.add_node_type(layout, "FunctionNodeStringLength") self.node_operator(layout, "FunctionNodeStringLength")
node_add_menu.add_node_type(layout, "GeometryNodeStringToCurves") self.node_operator(layout, "GeometryNodeStringToCurves")
node_add_menu.add_node_type(layout, "FunctionNodeStringToValue") self.node_operator(layout, "FunctionNodeStringToValue")
node_add_menu.add_node_type(layout, "FunctionNodeValueToString") self.node_operator(layout, "FunctionNodeValueToString")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeInputSpecialCharacters") self.node_operator(layout, "FunctionNodeInputSpecialCharacters")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Text")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_TEXTURE(Menu): class NODE_MT_gn_texture_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_TEXTURE"
bl_label = "Texture" bl_label = "Texture"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick") self.node_operator(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker") self.node_operator(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor") self.node_operator(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient") self.node_operator(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "GeometryNodeImageTexture") self.node_operator(layout, "GeometryNodeImageTexture")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic") self.node_operator(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise") self.node_operator(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi") self.node_operator(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave") self.node_operator(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise") self.node_operator(layout, "ShaderNodeTexWhiteNoise")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_UTILITIES(Menu): class NODE_MT_gn_utilities_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UTILITIES"
bl_label = "Utilities" bl_label = "Utilities"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_COLOR") self.draw_menu(layout, path="Utilities/Color")
layout.menu("NODE_MT_category_GEO_TEXT") self.draw_menu(layout, path="Utilities/Text")
layout.menu("NODE_MT_category_GEO_VECTOR") self.draw_menu(layout, path="Utilities/Vector")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_utilities_bundle") self.draw_menu(layout, path="Utilities/Bundle")
layout.menu("NODE_MT_category_utilities_closure") self.draw_menu(layout, path="Utilities/Closure")
layout.menu("NODE_MT_category_GEO_UTILITIES_FIELD") self.draw_menu(layout, path="Utilities/Field")
self.draw_menu(layout, path="Utilities/Math")
if context.preferences.experimental.use_geometry_nodes_lists: if context.preferences.experimental.use_geometry_nodes_lists:
layout.menu("NODE_MT_category_utilities_list") self.draw_menu(layout, path="Utilities/List")
layout.menu("NODE_MT_category_GEO_UTILITIES_MATH") self.draw_menu(layout, path="Utilities/Matrix")
layout.menu("NODE_MT_category_utilities_matrix") self.draw_menu(layout, path="Utilities/Rotation")
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION") self.draw_menu(layout, path="Utilities/Deprecated")
layout.menu("NODE_MT_category_GEO_UTILITIES_DEPRECATED")
layout.separator() layout.separator()
node_add_menu.add_foreach_geometry_element_zone(layout, label="For Each Element") self.for_each_element_zone(layout, label="For Each Element")
node_add_menu.add_node_type(layout, "GeometryNodeIndexSwitch") self.node_operator(layout, "GeometryNodeIndexSwitch")
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch") self.node_operator(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue") self.node_operator(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat") self.repeat_zone(layout, label="Repeat")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch") self.node_operator(layout, "GeometryNodeSwitch")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_UTILITIES_DEPRECATED(Menu): class NODE_MT_gn_utilities_deprecated_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_DEPRECATED"
bl_label = "Deprecated" bl_label = "Deprecated"
menu_path = "Utilities/Deprecated"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeAlignEulerToVector") self.node_operator(layout, "FunctionNodeAlignEulerToVector")
node_add_menu.add_node_type(layout, "FunctionNodeRotateEuler") self.node_operator(layout, "FunctionNodeRotateEuler")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Deprecated")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_FIELD(Menu): class NODE_MT_gn_utilities_field_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_FIELD"
bl_label = "Field" bl_label = "Field"
menu_path = "Utilities/Field"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeAccumulateField") self.node_operator(layout, "GeometryNodeAccumulateField")
node_add_menu.add_node_type(layout, "GeometryNodeFieldAtIndex") self.node_operator(layout, "GeometryNodeFieldAtIndex")
node_add_menu.add_node_type(layout, "GeometryNodeFieldOnDomain") self.node_operator(layout, "GeometryNodeFieldOnDomain")
node_add_menu.add_node_type(layout, "GeometryNodeFieldAverage") self.node_operator(layout, "GeometryNodeFieldAverage")
node_add_menu.add_node_type(layout, "GeometryNodeFieldMinAndMax") self.node_operator(layout, "GeometryNodeFieldMinAndMax")
node_add_menu.add_node_type(layout, "GeometryNodeFieldVariance") self.node_operator(layout, "GeometryNodeFieldVariance")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Field")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_ROTATION(Menu): class NODE_MT_gn_utilities_rotation_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_ROTATION"
bl_label = "Rotation" bl_label = "Rotation"
menu_path = "Utilities/Rotation"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeAlignRotationToVector") self.node_operator(layout, "FunctionNodeAlignRotationToVector")
node_add_menu.add_node_type(layout, "FunctionNodeAxesToRotation") self.node_operator(layout, "FunctionNodeAxesToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeAxisAngleToRotation") self.node_operator(layout, "FunctionNodeAxisAngleToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeEulerToRotation") self.node_operator(layout, "FunctionNodeEulerToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeInvertRotation") self.node_operator(layout, "FunctionNodeInvertRotation")
node_add_menu.add_node_type(layout, "FunctionNodeRotateRotation") self.node_operator(layout, "FunctionNodeRotateRotation")
node_add_menu.add_node_type(layout, "FunctionNodeRotateVector") self.node_operator(layout, "FunctionNodeRotateVector")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToAxisAngle") self.node_operator(layout, "FunctionNodeRotationToAxisAngle")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToEuler") self.node_operator(layout, "FunctionNodeRotationToEuler")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToQuaternion") self.node_operator(layout, "FunctionNodeRotationToQuaternion")
node_add_menu.add_node_type(layout, "FunctionNodeQuaternionToRotation") self.node_operator(layout, "FunctionNodeQuaternionToRotation")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Rotation")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_matrix(Menu): class NODE_MT_gn_utilities_matrix_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_utilities_matrix"
bl_label = "Matrix" bl_label = "Matrix"
menu_path = "Utilities/Matrix"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeCombineMatrix") self.node_operator(layout, "FunctionNodeCombineMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeCombineTransform") self.node_operator(layout, "FunctionNodeCombineTransform")
node_add_menu.add_node_type(layout, "FunctionNodeMatrixDeterminant", label="Determinant") self.node_operator(layout, "FunctionNodeMatrixDeterminant", label="Determinant")
node_add_menu.add_node_type(layout, "FunctionNodeInvertMatrix") self.node_operator(layout, "FunctionNodeInvertMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeMatrixMultiply") self.node_operator(layout, "FunctionNodeMatrixMultiply")
node_add_menu.add_node_type(layout, "FunctionNodeProjectPoint") self.node_operator(layout, "FunctionNodeProjectPoint")
node_add_menu.add_node_type(layout, "FunctionNodeSeparateMatrix") self.node_operator(layout, "FunctionNodeSeparateMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeSeparateTransform") self.node_operator(layout, "FunctionNodeSeparateTransform")
node_add_menu.add_node_type(layout, "FunctionNodeTransformDirection") self.node_operator(layout, "FunctionNodeTransformDirection")
node_add_menu.add_node_type(layout, "FunctionNodeTransformPoint") self.node_operator(layout, "FunctionNodeTransformPoint")
node_add_menu.add_node_type(layout, "FunctionNodeTransposeMatrix") self.node_operator(layout, "FunctionNodeTransposeMatrix")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Matrix")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_bundle(Menu): class NODE_MT_category_utilities_bundle_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_utilities_bundle"
bl_label = "Bundle" bl_label = "Bundle"
menu_path = "Utilities/Bundle"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "NodeCombineBundle") self.node_operator(layout, "NodeCombineBundle")
node_add_menu.add_node_type(layout, "NodeSeparateBundle") self.node_operator(layout, "NodeSeparateBundle")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Bundle")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_closure(Menu): class NODE_MT_category_utilities_closure_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_utilities_closure"
bl_label = "Closure" bl_label = "Closure"
menu_path = "Utilities/Closure"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_closure_zone(layout, label="Closure") self.closure_zone(layout, label="Closure")
node_add_menu.add_node_type(layout, "NodeEvaluateClosure") self.node_operator(layout, "NodeEvaluateClosure")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Closure")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_list(Menu): class NODE_MT_gn_utilities_list_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_utilities_list"
bl_label = "List" bl_label = "List"
menu_path = "Utilities/List"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeList") self.node_operator(layout, "GeometryNodeList")
node_add_menu.add_node_type(layout, "GeometryNodeListGetItem") self.node_operator(layout, "GeometryNodeListGetItem")
node_add_menu.add_node_type(layout, "GeometryNodeListLength") self.node_operator(layout, "GeometryNodeListLength")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/List")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_MATH(Menu): class NODE_MT_gn_utilities_math_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_MATH"
bl_label = "Math" bl_label = "Math"
menu_path = "Utilities/Math"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type_with_searchable_enum( self.node_operator_with_searchable_enum(
context, layout, "FunctionNodeBitMath", "operation", search_weight=-1.0, context, layout, "FunctionNodeBitMath", "operation", search_weight=-1.0,
) )
node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeBooleanMath", "operation") self.node_operator_with_searchable_enum(context, layout, "FunctionNodeBooleanMath", "operation")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeIntegerMath", "operation") self.node_operator_with_searchable_enum(context, layout, "FunctionNodeIntegerMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeClamp") self.node_operator(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "FunctionNodeCompare") self.node_operator(layout, "FunctionNodeCompare")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve") self.node_operator(layout, "ShaderNodeFloatCurve")
node_add_menu.add_node_type(layout, "FunctionNodeFloatToInt") self.node_operator(layout, "FunctionNodeFloatToInt")
node_add_menu.add_node_type(layout, "FunctionNodeHashValue") self.node_operator(layout, "FunctionNodeHashValue")
node_add_menu.add_node_type(layout, "ShaderNodeMapRange") self.node_operator(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix") self.node_operator(layout, "ShaderNodeMix")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Math")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UV(Menu): class NODE_MT_gn_mesh_uv_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_UV"
bl_label = "UV" bl_label = "UV"
menu_path = "Mesh/UV"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeUVPackIslands") self.node_operator(layout, "GeometryNodeUVPackIslands")
node_add_menu.add_node_type(layout, "GeometryNodeUVUnwrap") self.node_operator(layout, "GeometryNodeUVUnwrap")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/UV")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_VECTOR(Menu): class NODE_MT_gn_utilities_vector_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_VECTOR"
bl_label = "Vector" bl_label = "Vector"
menu_path = "Utilities/Vector"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling") self.node_operator(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve") self.node_operator(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate") self.node_operator(layout, "ShaderNodeVectorRotate")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ") self.node_operator(layout, "ShaderNodeCombineXYZ")
props = node_add_menu.add_node_type(layout, "ShaderNodeMix", label="Mix Vector") props = self.node_operator(layout, "ShaderNodeMix", label="Mix Vector")
ops = props.settings.add() ops = props.settings.add()
ops.name = "data_type" ops.name = "data_type"
ops.value = "'VECTOR'" ops.value = "'VECTOR'"
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ") self.node_operator(layout, "ShaderNodeSeparateXYZ")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Vector")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_VOLUME(Menu): class NODE_MT_gn_volume_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_VOLUME"
bl_label = "Volume" bl_label = "Volume"
bl_translation_context = i18n_contexts.id_id bl_translation_context = i18n_contexts.id_id
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_READ") self.draw_menu(layout, path="Volume/Read")
layout.menu("NODE_MT_geometry_node_volume_sample") self.draw_menu(layout, path="Volume/Sample")
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_WRITE") self.draw_menu(layout, path="Volume/Write")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS") self.draw_menu(layout, path="Volume/Operations")
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES") self.draw_menu(layout, path="Volume/Primitives")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_VOLUME_READ(Menu): class NODE_MT_gn_volume_read_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_READ"
bl_label = "Read" bl_label = "Read"
menu_path = "Volume/Read"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGetNamedGrid") self.node_operator(layout, "GeometryNodeGetNamedGrid")
node_add_menu.add_node_type(layout, "GeometryNodeGridInfo") self.node_operator(layout, "GeometryNodeGridInfo")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Read")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_WRITE(Menu): class NODE_MT_gn_volume_write_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_WRITE"
bl_label = "Write" bl_label = "Write"
menu_path = "Volume/Write"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeStoreNamedGrid") self.node_operator(layout, "GeometryNodeStoreNamedGrid")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Write")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_volume_sample(Menu): class NODE_MT_gn_volume_sample_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_volume_sample"
bl_label = "Sample" bl_label = "Sample"
menu_path = "Volume/Sample"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleGrid") self.node_operator(layout, "GeometryNodeSampleGrid")
node_add_menu.add_node_type(layout, "GeometryNodeSampleGridIndex") self.node_operator(layout, "GeometryNodeSampleGridIndex")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Sample")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu): class NODE_MT_gn_volume_operations_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS"
bl_label = "Operations" bl_label = "Operations"
menu_path = "Volume/Operations"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeVolumeToMesh") self.node_operator(layout, "GeometryNodeVolumeToMesh")
if context.preferences.experimental.use_new_volume_nodes: if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeGridToMesh") self.node_operator(layout, "GeometryNodeGridToMesh")
node_add_menu.add_node_type(layout, "GeometryNodeSDFGridBoolean") self.node_operator(layout, "GeometryNodeSDFGridBoolean")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Operations")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES(Menu): class NODE_MT_gn_volume_primitives_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES"
bl_label = "Primitives" bl_label = "Primitives"
menu_path = "Volume/Primitives"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeVolumeCube") self.node_operator(layout, "GeometryNodeVolumeCube")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Primitives")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_GROUP(Menu): class NODE_MT_gn_all_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_GEO_GROUP"
bl_label = "Group"
def draw(self, context):
layout = self.layout
node_add_menu.draw_node_group_add_menu(context, layout)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_add_all(Menu):
bl_idname = "NODE_MT_geometry_node_add_all"
bl_label = "" bl_label = ""
menu_path = "Root"
# NOTE: Menus are looked up via their label, this is so that both the Add
# & Swap menus can share the same layout while each using their
# corresponding menus
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_ATTRIBUTE") self.draw_menu(layout, "Attribute")
layout.menu("NODE_MT_geometry_node_GEO_INPUT") self.draw_menu(layout, "Input")
layout.menu("NODE_MT_category_GEO_OUTPUT") self.draw_menu(layout, "Output")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY") self.draw_menu(layout, "Geometry")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_CURVE") self.draw_menu(layout, "Curve")
layout.menu("NODE_MT_geometry_node_grease_pencil") self.draw_menu(layout, "Grease Pencil")
layout.menu("NODE_MT_geometry_node_GEO_INSTANCE") self.draw_menu(layout, "Instances")
layout.menu("NODE_MT_geometry_node_GEO_MESH") self.draw_menu(layout, "Mesh")
layout.menu("NODE_MT_category_GEO_POINT") self.draw_menu(layout, "Point")
layout.menu("NODE_MT_category_GEO_VOLUME") self.draw_menu(layout, "Volume")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_simulation") self.draw_menu(layout, "Simulation")
layout.separator() layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_MATERIAL") self.draw_menu(layout, "Material")
layout.menu("NODE_MT_category_GEO_TEXTURE") self.draw_menu(layout, "Texture")
layout.menu("NODE_MT_category_GEO_UTILITIES") self.draw_menu(layout, "Utilities")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_GEO_GROUP") self.draw_menu(layout, "Group")
layout.menu("NODE_MT_category_layout") self.draw_menu(layout, "Layout")
node_add_menu.draw_root_assets(layout)
self.draw_root_assets(layout)
add_menus = {
# menu bl_idname: baseclass
"NODE_MT_geometry_node_GEO_ATTRIBUTE": NODE_MT_gn_attribute_base,
"NODE_MT_geometry_node_GEO_INPUT": NODE_MT_gn_input_base,
"NODE_MT_geometry_node_GEO_INPUT_CONSTANT": NODE_MT_gn_input_constant_base,
"NODE_MT_geometry_node_GEO_INPUT_GIZMO": NODE_MT_gn_input_gizmo_base,
"NODE_MT_geometry_node_GEO_INPUT_GROUP": NODE_MT_gn_input_group_base,
"NODE_MT_category_import": NODE_MT_gn_input_import_base,
"NODE_MT_geometry_node_GEO_INPUT_SCENE": NODE_MT_gn_input_scene_base,
"NODE_MT_category_GEO_OUTPUT": NODE_MT_gn_output_base,
"NODE_MT_geometry_node_GEO_CURVE": NODE_MT_gn_curve_base,
"NODE_MT_geometry_node_GEO_CURVE_READ": NODE_MT_gn_curve_read_base,
"NODE_MT_geometry_node_GEO_CURVE_SAMPLE": NODE_MT_gn_curve_sample_base,
"NODE_MT_geometry_node_GEO_CURVE_WRITE": NODE_MT_gn_curve_write_base,
"NODE_MT_geometry_node_GEO_CURVE_OPERATIONS": NODE_MT_gn_curve_operations_base,
"NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE": NODE_MT_gn_curve_primitives_base,
"NODE_MT_geometry_node_curve_topology": NODE_MT_gn_curve_topology_base,
"NODE_MT_geometry_node_grease_pencil": NODE_MT_gn_grease_pencil_base,
"NODE_MT_geometry_node_grease_pencil_read": NODE_MT_gn_grease_pencil_read_base,
"NODE_MT_geometry_node_grease_pencil_write": NODE_MT_gn_grease_pencil_write_base,
"NODE_MT_geometry_node_grease_pencil_operations": NODE_MT_gn_grease_pencil_operations_base,
"NODE_MT_geometry_node_GEO_GEOMETRY": NODE_MT_gn_geometry_base,
"NODE_MT_geometry_node_GEO_GEOMETRY_READ": NODE_MT_gn_geometry_read_base,
"NODE_MT_geometry_node_GEO_GEOMETRY_WRITE": NODE_MT_gn_geometry_write_base,
"NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS": NODE_MT_gn_geometry_operations_base,
"NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE": NODE_MT_gn_geometry_sample_base,
"NODE_MT_geometry_node_GEO_INSTANCE": NODE_MT_gn_instance_base,
"NODE_MT_geometry_node_GEO_MESH": NODE_MT_gn_mesh_base,
"NODE_MT_geometry_node_GEO_MESH_READ": NODE_MT_gn_mesh_read_base,
"NODE_MT_geometry_node_GEO_MESH_SAMPLE": NODE_MT_gn_mesh_sample_base,
"NODE_MT_geometry_node_GEO_MESH_WRITE": NODE_MT_gn_mesh_write_base,
"NODE_MT_geometry_node_GEO_MESH_OPERATIONS": NODE_MT_gn_mesh_operations_base,
"NODE_MT_category_PRIMITIVES_MESH": NODE_MT_gn_mesh_uv_base,
"NODE_MT_geometry_node_mesh_topology": NODE_MT_gn_mesh_topology_base,
"NODE_MT_category_GEO_UV": NODE_MT_gn_mesh_primitives_base,
"NODE_MT_category_GEO_POINT": NODE_MT_gn_point_base,
"NODE_MT_category_simulation": NODE_MT_gn_simulation_base,
"NODE_MT_category_GEO_VOLUME": NODE_MT_gn_volume_base,
"NODE_MT_geometry_node_GEO_VOLUME_READ": NODE_MT_gn_volume_read_base,
"NODE_MT_geometry_node_volume_sample": NODE_MT_gn_volume_sample_base,
"NODE_MT_geometry_node_GEO_VOLUME_WRITE": NODE_MT_gn_volume_write_base,
"NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS": NODE_MT_gn_volume_operations_base,
"NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES": NODE_MT_gn_volume_primitives_base,
"NODE_MT_geometry_node_GEO_MATERIAL": NODE_MT_gn_material_base,
"NODE_MT_category_GEO_TEXTURE": NODE_MT_gn_texture_base,
"NODE_MT_category_GEO_UTILITIES": NODE_MT_gn_utilities_base,
"NODE_MT_geometry_node_GEO_COLOR": NODE_MT_gn_utilities_color_base,
"NODE_MT_category_GEO_TEXT": NODE_MT_gn_utilities_text_base,
"NODE_MT_category_GEO_VECTOR": NODE_MT_gn_utilities_vector_base,
"NODE_MT_category_utilities_bundle": NODE_MT_category_utilities_bundle_base,
"NODE_MT_category_utilities_closure": NODE_MT_category_utilities_closure_base,
"NODE_MT_category_GEO_UTILITIES_FIELD": NODE_MT_gn_utilities_field_base,
"NODE_MT_category_GEO_UTILITIES_MATH": NODE_MT_gn_utilities_math_base,
"NODE_MT_category_GEO_UTILITIES_ROTATION": NODE_MT_gn_utilities_rotation_base,
"NODE_MT_category_utilities_list": NODE_MT_gn_utilities_list_base,
"NODE_MT_category_utilities_matrix": NODE_MT_gn_utilities_matrix_base,
"NODE_MT_category_GEO_UTILITIES_DEPRECATED": NODE_MT_gn_utilities_deprecated_base,
"NODE_MT_geometry_node_add_all": NODE_MT_gn_all_base,
}
add_menus = node_add_menu.generate_menus(
add_menus,
template=node_add_menu.AddNodeMenu,
base_dict=node_add_menu.add_base_pathing_dict
)
swap_menus = {
# menu bl_idname: baseclass
"NODE_MT_gn_attribute_swap": NODE_MT_gn_attribute_base,
"NODE_MT_gn_input_swap": NODE_MT_gn_input_base,
"NODE_MT_gn_input_constant_swap": NODE_MT_gn_input_constant_base,
"NODE_MT_gn_input_gizmo_swap": NODE_MT_gn_input_gizmo_base,
"NODE_MT_gn_input_group_swap": NODE_MT_gn_input_group_base,
"NODE_MT_gn_input_import_swap": NODE_MT_gn_input_import_base,
"NODE_MT_gn_input_scene_swap": NODE_MT_gn_input_scene_base,
"NODE_MT_gn_output_swap": NODE_MT_gn_output_base,
"NODE_MT_gn_curve_swap": NODE_MT_gn_curve_base,
"NODE_MT_gn_curve_read_swap": NODE_MT_gn_curve_read_base,
"NODE_MT_gn_curve_sample_swap": NODE_MT_gn_curve_sample_base,
"NODE_MT_gn_curve_write_swap": NODE_MT_gn_curve_write_base,
"NODE_MT_gn_curve_operations_swap": NODE_MT_gn_curve_operations_base,
"NODE_MT_gn_curve_primitives_swap": NODE_MT_gn_curve_primitives_base,
"NODE_MT_gn_curve_topology_swap": NODE_MT_gn_curve_topology_base,
"NODE_MT_gn_grease_pencil_swap": NODE_MT_gn_grease_pencil_base,
"NODE_MT_gn_grease_pencil_read_swap": NODE_MT_gn_grease_pencil_read_base,
"NODE_MT_gn_grease_pencil_write_swap": NODE_MT_gn_grease_pencil_write_base,
"NODE_MT_gn_grease_pencil_operations_swap": NODE_MT_gn_grease_pencil_operations_base,
"NODE_MT_gn_geometry_swap": NODE_MT_gn_geometry_base,
"NODE_MT_gn_geometry_read_swap": NODE_MT_gn_geometry_read_base,
"NODE_MT_gn_geometry_write_swap": NODE_MT_gn_geometry_write_base,
"NODE_MT_gn_geometry_operations_swap": NODE_MT_gn_geometry_operations_base,
"NODE_MT_gn_geometry_sample_swap": NODE_MT_gn_geometry_sample_base,
"NODE_MT_gn_instance_swap": NODE_MT_gn_instance_base,
"NODE_MT_gn_mesh_swap": NODE_MT_gn_mesh_base,
"NODE_MT_gn_mesh_read_swap": NODE_MT_gn_mesh_read_base,
"NODE_MT_gn_mesh_sample_swap": NODE_MT_gn_mesh_sample_base,
"NODE_MT_gn_mesh_write_swap": NODE_MT_gn_mesh_write_base,
"NODE_MT_gn_mesh_operations_swap": NODE_MT_gn_mesh_operations_base,
"NODE_MT_gn_mesh_uv_swap": NODE_MT_gn_mesh_uv_base,
"NODE_MT_gn_mesh_topology_swap": NODE_MT_gn_mesh_topology_base,
"NODE_MT_gn_mesh_primitives_swap": NODE_MT_gn_mesh_primitives_base,
"NODE_MT_gn_point_swap": NODE_MT_gn_point_base,
"NODE_MT_gn_simulation_swap": NODE_MT_gn_simulation_base,
"NODE_MT_gn_volume_swap": NODE_MT_gn_volume_base,
"NODE_MT_gn_volume_read_swap": NODE_MT_gn_volume_read_base,
"NODE_MT_gn_volume_sample_swap": NODE_MT_gn_volume_sample_base,
"NODE_MT_gn_volume_write_swap": NODE_MT_gn_volume_write_base,
"NODE_MT_gn_volume_operations_swap": NODE_MT_gn_volume_operations_base,
"NODE_MT_gn_volume_primitives_swap": NODE_MT_gn_volume_primitives_base,
"NODE_MT_gn_material_swap": NODE_MT_gn_material_base,
"NODE_MT_gn_texture_swap": NODE_MT_gn_texture_base,
"NODE_MT_gn_utilities_swap": NODE_MT_gn_utilities_base,
"NODE_MT_gn_utilities_color_swap": NODE_MT_gn_utilities_color_base,
"NODE_MT_gn_utilities_text_swap": NODE_MT_gn_utilities_text_base,
"NODE_MT_gn_utilities_vector_swap": NODE_MT_gn_utilities_vector_base,
"NODE_MT_gn_utilities_bundle_swap": NODE_MT_category_utilities_bundle_base,
"NODE_MT_gn_utilities_closure_swap": NODE_MT_category_utilities_closure_base,
"NODE_MT_gn_utilities_field_swap": NODE_MT_gn_utilities_field_base,
"NODE_MT_gn_utilities_math_swap": NODE_MT_gn_utilities_math_base,
"NODE_MT_gn_utilities_rotation_swap": NODE_MT_gn_utilities_rotation_base,
"NODE_MT_gn_utilities_list_swap": NODE_MT_gn_utilities_list_base,
"NODE_MT_gn_utilities_matrix_swap": NODE_MT_gn_utilities_matrix_base,
"NODE_MT_gn_utilities_deprecated_swap": NODE_MT_gn_utilities_deprecated_base,
"NODE_MT_geometry_node_swap_all": NODE_MT_gn_all_base,
}
swap_menus = node_add_menu.generate_menus(
swap_menus,
template=node_add_menu.SwapNodeMenu,
base_dict=node_add_menu.swap_base_pathing_dict
)
classes = ( classes = (
NODE_MT_geometry_node_add_all, *add_menus,
NODE_MT_geometry_node_GEO_ATTRIBUTE, *swap_menus,
NODE_MT_geometry_node_GEO_INPUT,
NODE_MT_geometry_node_GEO_INPUT_CONSTANT,
NODE_MT_geometry_node_GEO_INPUT_GROUP,
NODE_MT_geometry_node_GEO_INPUT_SCENE,
NODE_MT_category_GEO_OUTPUT,
NODE_MT_geometry_node_GEO_CURVE,
NODE_MT_geometry_node_GEO_CURVE_READ,
NODE_MT_geometry_node_GEO_CURVE_SAMPLE,
NODE_MT_geometry_node_GEO_CURVE_WRITE,
NODE_MT_geometry_node_GEO_CURVE_OPERATIONS,
NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE,
NODE_MT_geometry_node_curve_topology,
NODE_MT_geometry_node_grease_pencil,
NODE_MT_geometry_node_grease_pencil_read,
NODE_MT_geometry_node_grease_pencil_write,
NODE_MT_geometry_node_grease_pencil_operations,
NODE_MT_geometry_node_GEO_GEOMETRY,
NODE_MT_geometry_node_GEO_GEOMETRY_READ,
NODE_MT_geometry_node_GEO_GEOMETRY_WRITE,
NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS,
NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE,
NODE_MT_geometry_node_GEO_INSTANCE,
NODE_MT_geometry_node_GEO_MESH,
NODE_MT_geometry_node_GEO_MESH_READ,
NODE_MT_geometry_node_GEO_MESH_SAMPLE,
NODE_MT_geometry_node_GEO_MESH_WRITE,
NODE_MT_geometry_node_GEO_MESH_OPERATIONS,
NODE_MT_category_GEO_UV,
NODE_MT_category_PRIMITIVES_MESH,
NODE_MT_category_import,
NODE_MT_geometry_node_mesh_topology,
NODE_MT_category_GEO_POINT,
NODE_MT_category_simulation,
NODE_MT_category_GEO_VOLUME,
NODE_MT_geometry_node_GEO_VOLUME_READ,
NODE_MT_geometry_node_volume_sample,
NODE_MT_geometry_node_GEO_VOLUME_WRITE,
NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS,
NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES,
NODE_MT_geometry_node_GEO_MATERIAL,
NODE_MT_category_GEO_TEXTURE,
NODE_MT_category_GEO_UTILITIES,
NODE_MT_geometry_node_GEO_COLOR,
NODE_MT_category_GEO_TEXT,
NODE_MT_category_GEO_VECTOR,
NODE_MT_category_GEO_UTILITIES_FIELD,
NODE_MT_category_GEO_UTILITIES_MATH,
NODE_MT_category_GEO_UTILITIES_ROTATION,
NODE_MT_geometry_node_GEO_INPUT_GIZMO,
NODE_MT_category_utilities_list,
NODE_MT_category_utilities_matrix,
NODE_MT_category_utilities_bundle,
NODE_MT_category_utilities_closure,
NODE_MT_category_GEO_UTILITIES_DEPRECATED,
NODE_MT_category_GEO_GROUP,
) )
if __name__ == "__main__": # only for live edit. if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class from bpy.utils import register_class
for cls in classes: for cls in classes:

View File

@@ -48,27 +48,26 @@ def object_eevee_shader_nodes_poll(context):
eevee_shader_nodes_poll(context)) eevee_shader_nodes_poll(context))
class NODE_MT_category_shader_input(Menu): class NODE_MT_shader_node_input_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_input"
bl_label = "Input" bl_label = "Input"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeAmbientOcclusion") self.node_operator(layout, "ShaderNodeAmbientOcclusion")
node_add_menu.add_node_type(layout, "ShaderNodeAttribute") self.node_operator(layout, "ShaderNodeAttribute")
node_add_menu.add_node_type(layout, "ShaderNodeBevel") self.node_operator(layout, "ShaderNodeBevel")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeCameraData", context, layout, "ShaderNodeCameraData",
["View Vector", "View Z Depth", "View Distance"], ["View Vector", "View Z Depth", "View Distance"],
) )
node_add_menu.add_node_type(layout, "ShaderNodeVertexColor") self.node_operator(layout, "ShaderNodeVertexColor")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeHairInfo", context, layout, "ShaderNodeHairInfo",
["Is Strand", "Intercept", "Length", "Thickness", "Tangent Normal", "Random"], ["Is Strand", "Intercept", "Length", "Thickness", "Tangent Normal", "Random"],
) )
node_add_menu.add_node_type(layout, "ShaderNodeFresnel") self.node_operator(layout, "ShaderNodeFresnel")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, context,
layout, layout,
"ShaderNodeNewGeometry", "ShaderNodeNewGeometry",
@@ -84,8 +83,8 @@ class NODE_MT_category_shader_input(Menu):
"Random Per Island", "Random Per Island",
], ],
) )
node_add_menu.add_node_type(layout, "ShaderNodeLayerWeight") self.node_operator(layout, "ShaderNodeLayerWeight")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, context,
layout, layout,
"ShaderNodeLightPath", "ShaderNodeLightPath",
@@ -107,358 +106,374 @@ class NODE_MT_category_shader_input(Menu):
"Portal Depth" "Portal Depth"
], ],
) )
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeObjectInfo", context, layout, "ShaderNodeObjectInfo",
["Location", "Color", "Alpha", "Object Index", "Material Index", "Random"], ["Location", "Color", "Alpha", "Object Index", "Material Index", "Random"],
) )
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeParticleInfo", context, layout, "ShaderNodeParticleInfo",
["Index", "Random", "Age", "Lifetime", "Location", "Size", "Velocity", "Angular Velocity"], ["Index", "Random", "Age", "Lifetime", "Location", "Size", "Velocity", "Angular Velocity"],
) )
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodePointInfo", context, layout, "ShaderNodePointInfo",
["Position", "Radius", "Random"], ["Position", "Radius", "Random"],
) )
node_add_menu.add_node_type(layout, "ShaderNodeRGB") self.node_operator(layout, "ShaderNodeRGB")
node_add_menu.add_node_type(layout, "ShaderNodeTangent") self.node_operator(layout, "ShaderNodeTangent")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeTexCoord", context, layout, "ShaderNodeTexCoord",
["Normal", "UV", "Object", "Camera", "Window", "Reflection"], ["Normal", "UV", "Object", "Camera", "Window", "Reflection"],
) )
node_add_menu.add_node_type(layout, "ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll(context)) self.node_operator(layout, "ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll(context))
node_add_menu.add_node_type(layout, "ShaderNodeUVMap") self.node_operator(layout, "ShaderNodeUVMap")
node_add_menu.add_node_type(layout, "ShaderNodeValue") self.node_operator(layout, "ShaderNodeValue")
node_add_menu.add_node_type_with_outputs( self.node_operator_with_outputs(
context, layout, "ShaderNodeVolumeInfo", context, layout, "ShaderNodeVolumeInfo",
["Color", "Density", "Flame", "Temperature"], ["Color", "Density", "Flame", "Temperature"],
) )
node_add_menu.add_node_type(layout, "ShaderNodeWireframe") self.node_operator(layout, "ShaderNodeWireframe")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_output(Menu): class NODE_MT_shader_node_output_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_output"
bl_label = "Output" bl_label = "Output"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeOutputAOV", "ShaderNodeOutputAOV",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeOutputLight", "ShaderNodeOutputLight",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeOutputLineStyle", "ShaderNodeOutputLineStyle",
poll=line_style_shader_nodes_poll(context), poll=line_style_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeOutputMaterial", "ShaderNodeOutputMaterial",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeOutputWorld", "ShaderNodeOutputWorld",
poll=world_shader_nodes_poll(context), poll=world_shader_nodes_poll(context),
) )
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_shader(Menu): class NODE_MT_shader_node_shader_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_shader"
bl_label = "Shader" bl_label = "Shader"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeAddShader", "ShaderNodeAddShader",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBackground", "ShaderNodeBackground",
poll=world_shader_nodes_poll(context), poll=world_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfDiffuse", "ShaderNodeBsdfDiffuse",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeEmission", "ShaderNodeEmission",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfGlass", "ShaderNodeBsdfGlass",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfGlossy", "ShaderNodeBsdfGlossy",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfHair", "ShaderNodeBsdfHair",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeHoldout", "ShaderNodeHoldout",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfMetallic", "ShaderNodeBsdfMetallic",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeMixShader", "ShaderNodeMixShader",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfPrincipled", "ShaderNodeBsdfPrincipled",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfHairPrincipled", "ShaderNodeBsdfHairPrincipled",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeVolumePrincipled" "ShaderNodeVolumePrincipled"
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfRayPortal", "ShaderNodeBsdfRayPortal",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfRefraction", "ShaderNodeBsdfRefraction",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfSheen", "ShaderNodeBsdfSheen",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeEeveeSpecular", "ShaderNodeEeveeSpecular",
poll=object_eevee_shader_nodes_poll(context), poll=object_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeSubsurfaceScattering", "ShaderNodeSubsurfaceScattering",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfToon", "ShaderNodeBsdfToon",
poll=object_not_eevee_shader_nodes_poll(context), poll=object_not_eevee_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfTranslucent", "ShaderNodeBsdfTranslucent",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeBsdfTransparent", "ShaderNodeBsdfTransparent",
poll=object_shader_nodes_poll(context), poll=object_shader_nodes_poll(context),
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeVolumeAbsorption", "ShaderNodeVolumeAbsorption",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeVolumeScatter", "ShaderNodeVolumeScatter",
) )
node_add_menu.add_node_type( self.node_operator(
layout, layout,
"ShaderNodeVolumeCoefficients", "ShaderNodeVolumeCoefficients",
) )
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_color(Menu): class NODE_MT_shader_node_color_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_color"
bl_label = "Color" bl_label = "Color"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBrightContrast") self.node_operator(layout, "ShaderNodeBrightContrast")
node_add_menu.add_node_type(layout, "ShaderNodeGamma") self.node_operator(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "ShaderNodeHueSaturation") self.node_operator(layout, "ShaderNodeHueSaturation")
node_add_menu.add_node_type(layout, "ShaderNodeInvert") self.node_operator(layout, "ShaderNodeInvert")
node_add_menu.add_node_type(layout, "ShaderNodeLightFalloff") self.node_operator(layout, "ShaderNodeLightFalloff")
node_add_menu.add_color_mix_node(context, layout) self.color_mix_node(context, layout)
node_add_menu.add_node_type(layout, "ShaderNodeRGBCurve") self.node_operator(layout, "ShaderNodeRGBCurve")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_converter(Menu): class NODE_MT_shader_node_converter_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_converter"
bl_label = "Converter" bl_label = "Converter"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody") self.node_operator(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeClamp") self.node_operator(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB") self.node_operator(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "ShaderNodeCombineColor") self.node_operator(layout, "ShaderNodeCombineColor")
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ") self.node_operator(layout, "ShaderNodeCombineXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve") self.node_operator(layout, "ShaderNodeFloatCurve")
node_add_menu.add_node_type(layout, "ShaderNodeMapRange") self.node_operator(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix") self.node_operator(layout, "ShaderNodeMix")
node_add_menu.add_node_type(layout, "ShaderNodeRGBToBW") self.node_operator(layout, "ShaderNodeRGBToBW")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateColor") self.node_operator(layout, "ShaderNodeSeparateColor")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ") self.node_operator(layout, "ShaderNodeSeparateXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll(context)) self.node_operator(layout, "ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll(context))
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation") self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeWavelength") self.node_operator(layout, "ShaderNodeWavelength")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_texture(Menu): class NODE_MT_shader_node_texture_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_texture"
bl_label = "Texture" bl_label = "Texture"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick") self.node_operator(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker") self.node_operator(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexEnvironment") self.node_operator(layout, "ShaderNodeTexEnvironment")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor") self.node_operator(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient") self.node_operator(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "ShaderNodeTexIES") self.node_operator(layout, "ShaderNodeTexIES")
node_add_menu.add_node_type(layout, "ShaderNodeTexImage") self.node_operator(layout, "ShaderNodeTexImage")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic") self.node_operator(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise") self.node_operator(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexSky") self.node_operator(layout, "ShaderNodeTexSky")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi") self.node_operator(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave") self.node_operator(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise") self.node_operator(layout, "ShaderNodeTexWhiteNoise")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_vector(Menu): class NODE_MT_shader_node_vector_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_vector"
bl_label = "Vector" bl_label = "Vector"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBump") self.node_operator(layout, "ShaderNodeBump")
node_add_menu.add_node_type(layout, "ShaderNodeDisplacement") self.node_operator(layout, "ShaderNodeDisplacement")
node_add_menu.add_node_type(layout, "ShaderNodeMapping") self.node_operator(layout, "ShaderNodeMapping")
node_add_menu.add_node_type(layout, "ShaderNodeNormal") self.node_operator(layout, "ShaderNodeNormal")
node_add_menu.add_node_type(layout, "ShaderNodeNormalMap") self.node_operator(layout, "ShaderNodeNormalMap")
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling") self.node_operator(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve") self.node_operator(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type(layout, "ShaderNodeVectorDisplacement") self.node_operator(layout, "ShaderNodeVectorDisplacement")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate") self.node_operator(layout, "ShaderNodeVectorRotate")
node_add_menu.add_node_type(layout, "ShaderNodeVectorTransform") self.node_operator(layout, "ShaderNodeVectorTransform")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_script(Menu): class NODE_MT_shader_node_script_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_script"
bl_label = "Script" bl_label = "Script"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeScript") self.node_operator(layout, "ShaderNodeScript")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label) self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_group(Menu): class NODE_MT_shader_node_utilities_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_shader_group"
bl_label = "Group"
def draw(self, context):
layout = self.layout
node_add_menu.draw_node_group_add_menu(context, layout)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_shader_utilities(Menu):
bl_idname = "NODE_MT_category_shader_utilities"
bl_label = "Utilities" bl_label = "Utilities"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
node_add_menu.add_repeat_zone(layout, label="Repeat") self.repeat_zone(layout, label="Repeat")
layout.separator() layout.separator()
node_add_menu.add_closure_zone(layout, label="Closure") self.closure_zone(layout, label="Closure")
node_add_menu.add_node_type(layout, "NodeEvaluateClosure") self.node_operator(layout, "NodeEvaluateClosure")
node_add_menu.add_node_type(layout, "NodeCombineBundle") self.node_operator(layout, "NodeCombineBundle")
node_add_menu.add_node_type(layout, "NodeSeparateBundle") self.node_operator(layout, "NodeSeparateBundle")
class NODE_MT_shader_node_add_all(Menu): class NODE_MT_shader_node_all_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_shader_node_add_all" bl_label = ""
bl_label = "Add" menu_path = "Root"
bl_translation_context = i18n_contexts.operator_default bl_translation_context = i18n_contexts.operator_default
def draw(self, _context): # NOTE: Menus are looked up via their label, this is so that both the Add
# & Swap menus can share the same layout while each using their
# corresponding menus
def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_shader_input") self.draw_menu(layout, "Input")
layout.menu("NODE_MT_category_shader_output") self.draw_menu(layout, "Output")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_shader_color") self.draw_menu(layout, "Color")
layout.menu("NODE_MT_category_shader_converter") self.draw_menu(layout, "Converter")
layout.menu("NODE_MT_category_shader_shader") self.draw_menu(layout, "Shader")
layout.menu("NODE_MT_category_shader_texture") self.draw_menu(layout, "Texture")
layout.menu("NODE_MT_category_shader_vector") self.draw_menu(layout, "Vector")
layout.menu("NODE_MT_category_shader_utilities") self.draw_menu(layout, "Utilities")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_shader_script") self.draw_menu(layout, "Script")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_shader_group") self.draw_menu(layout, "Group")
layout.menu("NODE_MT_category_layout") self.draw_menu(layout, "Layout")
node_add_menu.draw_root_assets(layout) self.draw_root_assets(layout)
add_menus = {
# menu bl_idname: baseclass
"NODE_MT_category_shader_input": NODE_MT_shader_node_input_base,
"NODE_MT_category_shader_output": NODE_MT_shader_node_output_base,
"NODE_MT_category_shader_color": NODE_MT_shader_node_color_base,
"NODE_MT_category_shader_converter": NODE_MT_shader_node_converter_base,
"NODE_MT_category_shader_shader": NODE_MT_shader_node_shader_base,
"NODE_MT_category_shader_texture": NODE_MT_shader_node_texture_base,
"NODE_MT_category_shader_vector": NODE_MT_shader_node_vector_base,
"NODE_MT_category_shader_script": NODE_MT_shader_node_script_base,
"NODE_MT_category_shader_utilities": NODE_MT_shader_node_utilities_base,
"NODE_MT_shader_node_add_all": NODE_MT_shader_node_all_base,
}
add_menus = node_add_menu.generate_menus(
add_menus,
template=node_add_menu.AddNodeMenu,
base_dict=node_add_menu.add_base_pathing_dict
)
swap_menus = {
# menu bl_idname: baseclass
"NODE_MT_shader_node_input_swap": NODE_MT_shader_node_input_base,
"NODE_MT_shader_node_output_swap": NODE_MT_shader_node_output_base,
"NODE_MT_shader_node_color_swap": NODE_MT_shader_node_color_base,
"NODE_MT_shader_node_converter_swap": NODE_MT_shader_node_converter_base,
"NODE_MT_shader_node_shader_swap": NODE_MT_shader_node_shader_base,
"NODE_MT_shader_node_texture_swap": NODE_MT_shader_node_texture_base,
"NODE_MT_shader_node_vector_swap": NODE_MT_shader_node_vector_base,
"NODE_MT_shader_node_script_swap": NODE_MT_shader_node_script_base,
"NODE_MT_shader_node_utilities_swap": NODE_MT_shader_node_utilities_base,
"NODE_MT_shader_node_swap_all": NODE_MT_shader_node_all_base,
}
swap_menus = node_add_menu.generate_menus(
swap_menus,
template=node_add_menu.SwapNodeMenu,
base_dict=node_add_menu.swap_base_pathing_dict
)
classes = ( classes = (
NODE_MT_shader_node_add_all, *add_menus,
NODE_MT_category_shader_input, *swap_menus,
NODE_MT_category_shader_output,
NODE_MT_category_shader_color,
NODE_MT_category_shader_converter,
NODE_MT_category_shader_shader,
NODE_MT_category_shader_texture,
NODE_MT_category_shader_vector,
NODE_MT_category_shader_script,
NODE_MT_category_shader_group,
NODE_MT_category_shader_utilities,
) )

View File

@@ -9,136 +9,154 @@ from bpy.app.translations import (
from bl_ui import node_add_menu from bl_ui import node_add_menu
class NODE_MT_category_texture_input(Menu): class NODE_MT_texture_node_input_base(Menu):
bl_idname = "NODE_MT_category_texture_input"
bl_label = "Input" bl_label = "Input"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeCoordinates") self.node_operator(layout, "TextureNodeCoordinates")
node_add_menu.add_node_type(layout, "TextureNodeCurveTime") self.node_operator(layout, "TextureNodeCurveTime")
node_add_menu.add_node_type(layout, "TextureNodeImage") self.node_operator(layout, "TextureNodeImage")
node_add_menu.add_node_type(layout, "TextureNodeTexture") self.node_operator(layout, "TextureNodeTexture")
class NODE_MT_category_texture_output(Menu): class NODE_MT_texture_node_output_base(Menu):
bl_idname = "NODE_MT_category_texture_output"
bl_label = "Output" bl_label = "Output"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeOutput") self.node_operator(layout, "TextureNodeOutput")
node_add_menu.add_node_type(layout, "TextureNodeViewer") self.node_operator(layout, "TextureNodeViewer")
class NODE_MT_category_texture_color(Menu): class NODE_MT_texture_node_color_base(Menu):
bl_idname = "NODE_MT_category_texture_color"
bl_label = "Color" bl_label = "Color"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeHueSaturation") self.node_operator(layout, "TextureNodeHueSaturation")
node_add_menu.add_node_type(layout, "TextureNodeInvert") self.node_operator(layout, "TextureNodeInvert")
node_add_menu.add_node_type(layout, "TextureNodeMixRGB") self.node_operator(layout, "TextureNodeMixRGB")
node_add_menu.add_node_type(layout, "TextureNodeCurveRGB") self.node_operator(layout, "TextureNodeCurveRGB")
layout.separator() layout.separator()
node_add_menu.add_node_type(layout, "TextureNodeCombineColor") self.node_operator(layout, "TextureNodeCombineColor")
node_add_menu.add_node_type(layout, "TextureNodeSeparateColor") self.node_operator(layout, "TextureNodeSeparateColor")
class NODE_MT_category_texture_converter(Menu): class NODE_MT_texture_node_converter_base(Menu):
bl_idname = "NODE_MT_category_texture_converter"
bl_label = "Converter" bl_label = "Converter"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeValToRGB") self.node_operator(layout, "TextureNodeValToRGB")
node_add_menu.add_node_type(layout, "TextureNodeDistance") self.node_operator(layout, "TextureNodeDistance")
node_add_menu.add_node_type(layout, "TextureNodeMath") self.node_operator(layout, "TextureNodeMath")
node_add_menu.add_node_type(layout, "TextureNodeRGBToBW") self.node_operator(layout, "TextureNodeRGBToBW")
node_add_menu.add_node_type(layout, "TextureNodeValToNor") self.node_operator(layout, "TextureNodeValToNor")
class NODE_MT_category_texture_distort(Menu): class NODE_MT_texture_node_distort_base(Menu):
bl_idname = "NODE_MT_category_texture_distort"
bl_label = "Distort" bl_label = "Distort"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeAt") self.node_operator(layout, "TextureNodeAt")
node_add_menu.add_node_type(layout, "TextureNodeRotate") self.node_operator(layout, "TextureNodeRotate")
node_add_menu.add_node_type(layout, "TextureNodeScale") self.node_operator(layout, "TextureNodeScale")
node_add_menu.add_node_type(layout, "TextureNodeTranslate") self.node_operator(layout, "TextureNodeTranslate")
class NODE_MT_category_texture_pattern(Menu): class NODE_MT_texture_node_pattern_base(Menu):
bl_idname = "NODE_MT_category_texture_pattern"
bl_label = "Pattern" bl_label = "Pattern"
bl_translation_context = i18n_contexts.id_texture bl_translation_context = i18n_contexts.id_texture
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeBricks") self.node_operator(layout, "TextureNodeBricks")
node_add_menu.add_node_type(layout, "TextureNodeChecker") self.node_operator(layout, "TextureNodeChecker")
class NODE_MT_category_texture_texture(Menu): class NODE_MT_texture_node_texture_base(Menu):
bl_idname = "NODE_MT_category_texture_texture"
bl_label = "Texture" bl_label = "Texture"
def draw(self, _context): def draw(self, _context):
layout = self.layout layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeTexBlend") self.node_operator(layout, "TextureNodeTexBlend")
node_add_menu.add_node_type(layout, "TextureNodeTexClouds") self.node_operator(layout, "TextureNodeTexClouds")
node_add_menu.add_node_type(layout, "TextureNodeTexDistNoise") self.node_operator(layout, "TextureNodeTexDistNoise")
node_add_menu.add_node_type(layout, "TextureNodeTexMagic") self.node_operator(layout, "TextureNodeTexMagic")
node_add_menu.add_node_type(layout, "TextureNodeTexMarble") self.node_operator(layout, "TextureNodeTexMarble")
node_add_menu.add_node_type(layout, "TextureNodeTexMusgrave") self.node_operator(layout, "TextureNodeTexMusgrave")
node_add_menu.add_node_type(layout, "TextureNodeTexNoise") self.node_operator(layout, "TextureNodeTexNoise")
node_add_menu.add_node_type(layout, "TextureNodeTexStucci") self.node_operator(layout, "TextureNodeTexStucci")
node_add_menu.add_node_type(layout, "TextureNodeTexVoronoi") self.node_operator(layout, "TextureNodeTexVoronoi")
node_add_menu.add_node_type(layout, "TextureNodeTexWood") self.node_operator(layout, "TextureNodeTexWood")
class NODE_MT_category_texture_group(Menu): class NODE_MT_texture_node_all_base(node_add_menu.NodeMenu):
bl_idname = "NODE_MT_category_texture_group" bl_label = ""
bl_label = "Group" menu_path = "Root"
def draw(self, context):
layout = self.layout
node_add_menu.draw_node_group_add_menu(context, layout)
class NODE_MT_texture_node_add_all(Menu):
bl_idname = "NODE_MT_texture_node_add_all"
bl_label = "Add"
bl_translation_context = i18n_contexts.operator_default bl_translation_context = i18n_contexts.operator_default
def draw(self, _context): # NOTE: Menus are looked up via their label, this is so that both the Add
# & Swap menus can share the same layout while each using their
# corresponding menus
def draw(self, context):
layout = self.layout layout = self.layout
layout.menu("NODE_MT_category_texture_input") self.draw_menu(layout, "Input")
layout.menu("NODE_MT_category_texture_output") self.draw_menu(layout, "Output")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_texture_color") self.draw_menu(layout, "Color")
layout.menu("NODE_MT_category_texture_converter") self.draw_menu(layout, "Converter")
layout.menu("NODE_MT_category_texture_distort") self.draw_menu(layout, "Distort")
layout.menu("NODE_MT_category_texture_pattern") self.draw_menu(layout, "Pattern")
layout.menu("NODE_MT_category_texture_texture") self.draw_menu(layout, "Texture")
layout.separator() layout.separator()
layout.menu("NODE_MT_category_texture_group") self.draw_menu(layout, "Group")
layout.menu("NODE_MT_category_layout") self.draw_menu(layout, "Layout")
self.draw_root_assets(layout)
add_menus = {
# menu bl_idname: baseclass
"NODE_MT_category_texture_input": NODE_MT_texture_node_input_base,
"NODE_MT_category_texture_output": NODE_MT_texture_node_output_base,
"NODE_MT_category_texture_color": NODE_MT_texture_node_color_base,
"NODE_MT_category_texture_converter": NODE_MT_texture_node_converter_base,
"NODE_MT_category_texture_distort": NODE_MT_texture_node_distort_base,
"NODE_MT_category_texture_pattern": NODE_MT_texture_node_pattern_base,
"NODE_MT_category_texture_texture": NODE_MT_texture_node_texture_base,
"NODE_MT_texture_node_add_all": NODE_MT_texture_node_all_base,
}
add_menus = node_add_menu.generate_menus(
add_menus,
template=node_add_menu.AddNodeMenu,
base_dict=node_add_menu.add_base_pathing_dict
)
swap_menus = {
# menu bl_idname: baseclass
"NODE_MT_texture_node_input_swap": NODE_MT_texture_node_input_base,
"NODE_MT_texture_node_output_swap": NODE_MT_texture_node_output_base,
"NODE_MT_texture_node_color_swap": NODE_MT_texture_node_color_base,
"NODE_MT_texture_node_converter_swap": NODE_MT_texture_node_converter_base,
"NODE_MT_texture_node_distort_swap": NODE_MT_texture_node_distort_base,
"NODE_MT_texture_node_pattern_swap": NODE_MT_texture_node_pattern_base,
"NODE_MT_texture_node_texture_swap": NODE_MT_texture_node_texture_base,
"NODE_MT_texture_node_swap_all": NODE_MT_texture_node_all_base,
}
swap_menus = node_add_menu.generate_menus(
swap_menus,
template=node_add_menu.SwapNodeMenu,
base_dict=node_add_menu.swap_base_pathing_dict
)
classes = ( classes = (
NODE_MT_texture_node_add_all, *add_menus,
NODE_MT_category_texture_input, *swap_menus,
NODE_MT_category_texture_output,
NODE_MT_category_texture_color,
NODE_MT_category_texture_converter,
NODE_MT_category_texture_distort,
NODE_MT_category_texture_pattern,
NODE_MT_category_texture_texture,
NODE_MT_category_texture_group,
) )

View File

@@ -14,7 +14,7 @@ from bpy.app.translations import (
pgettext_iface as iface_, pgettext_iface as iface_,
contexts as i18n_contexts, contexts as i18n_contexts,
) )
from bl_ui import anim from bl_ui import anim, node_add_menu
from bl_ui.utils import PresetPanel from bl_ui.utils import PresetPanel
from bl_ui.properties_grease_pencil_common import ( from bl_ui.properties_grease_pencil_common import (
AnnotationDataPanel, AnnotationDataPanel,
@@ -263,10 +263,11 @@ class NODE_MT_editor_menus(Menu):
layout.menu("NODE_MT_view") layout.menu("NODE_MT_view")
layout.menu("NODE_MT_select") layout.menu("NODE_MT_select")
layout.menu("NODE_MT_add") layout.menu("NODE_MT_add")
layout.menu("NODE_MT_swap")
layout.menu("NODE_MT_node") layout.menu("NODE_MT_node")
class NODE_MT_add(Menu): class NODE_MT_add(node_add_menu.AddNodeMenu):
bl_space_type = 'NODE_EDITOR' bl_space_type = 'NODE_EDITOR'
bl_label = "Add" bl_label = "Add"
bl_translation_context = i18n_contexts.operator_default bl_translation_context = i18n_contexts.operator_default
@@ -298,6 +299,33 @@ class NODE_MT_add(Menu):
nodeitems_utils.draw_node_categories_menu(self, context) nodeitems_utils.draw_node_categories_menu(self, context)
class NODE_MT_swap(node_add_menu.SwapNodeMenu):
bl_space_type = 'NODE_EDITOR'
bl_label = "Swap"
bl_translation_context = i18n_contexts.operator_default
bl_options = {'SEARCH_ON_KEY_PRESS'}
def draw(self, context):
layout = self.layout
if layout.operator_context == 'EXEC_REGION_WIN':
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("WM_OT_search_single_menu", text="Search...", icon='VIEWZOOM').menu_idname = "NODE_MT_swap"
layout.separator()
layout.operator_context = 'INVOKE_REGION_WIN'
snode = context.space_data
if snode.tree_type == 'GeometryNodeTree':
layout.menu_contents("NODE_MT_geometry_node_swap_all")
elif snode.tree_type == 'CompositorNodeTree':
layout.menu_contents("NODE_MT_compositor_node_swap_all")
elif snode.tree_type == 'ShaderNodeTree':
layout.menu_contents("NODE_MT_shader_node_swap_all")
elif snode.tree_type == 'TextureNodeTree':
layout.menu_contents("NODE_MT_texture_node_swap_all")
class NODE_MT_view(Menu): class NODE_MT_view(Menu):
bl_label = "View" bl_label = "View"
@@ -1216,13 +1244,14 @@ classes = (
NODE_HT_header, NODE_HT_header,
NODE_MT_editor_menus, NODE_MT_editor_menus,
NODE_MT_add, NODE_MT_add,
NODE_MT_view, NODE_MT_swap,
NODE_MT_select, NODE_MT_select,
NODE_MT_node, NODE_MT_node,
NODE_MT_node_color_context_menu, NODE_MT_node_color_context_menu,
NODE_MT_context_menu_show_hide_menu, NODE_MT_context_menu_show_hide_menu,
NODE_MT_context_menu_select_menu, NODE_MT_context_menu_select_menu,
NODE_MT_context_menu, NODE_MT_context_menu,
NODE_MT_view,
NODE_MT_view_pie, NODE_MT_view_pie,
NODE_PT_material_slots, NODE_PT_material_slots,
NODE_PT_geometry_node_tool_object_types, NODE_PT_geometry_node_tool_object_types,

View File

@@ -132,14 +132,14 @@ class MyCustomNode(MyCustomTreeNode, Node):
return "I am a custom node" return "I am a custom node"
# Add custom nodes to the Add menu. # Add custom nodes to the Add & Swap menu.
def draw_add_menu(self, context): def draw_add_menu(self, context):
layout = self.layout layout = self.layout
if context.space_data.tree_type != MyCustomTree.bl_idname: if context.space_data.tree_type != MyCustomTree.bl_idname:
# Avoid adding nodes to built-in node tree # Avoid adding nodes to built-in node tree
return return
# Add nodes to the layout. Can use submenus, separators, etc. as in any other menu. # Add nodes to the layout. Can use submenus, separators, etc. as in any other menu.
node_add_menu.add_node_type(layout, "CustomNodeType") self.node_operator(layout, "CustomNodeType")
classes = ( classes = (
@@ -156,10 +156,12 @@ def register():
register_class(cls) register_class(cls)
bpy.types.NODE_MT_add.append(draw_add_menu) bpy.types.NODE_MT_add.append(draw_add_menu)
bpy.types.NODE_MT_swap.append(draw_add_menu)
def unregister(): def unregister():
bpy.types.NODE_MT_add.remove(draw_add_menu) bpy.types.NODE_MT_add.remove(draw_add_menu)
bpy.types.NODE_MT_swap.remove(draw_add_menu)
from bpy.utils import unregister_class from bpy.utils import unregister_class
for cls in reversed(classes): for cls in reversed(classes):

View File

@@ -152,4 +152,15 @@ void draw_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item,
col->menu(menu_name, IFACE_(item.get_name()), ICON_NONE); col->menu(menu_name, IFACE_(item.get_name()), ICON_NONE);
} }
void draw_node_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item,
const StringRefNull operator_id,
const StringRefNull menu_name,
uiLayout &layout)
{
uiLayout *col = &layout.column(false);
col->context_string_set("asset_catalog_path", item.catalog_path().c_str());
col->context_string_set("operator_id", operator_id);
col->menu(menu_name, IFACE_(item.get_name()), ICON_NONE);
}
} // namespace blender::ed::asset } // namespace blender::ed::asset

View File

@@ -29,6 +29,11 @@ void draw_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item,
StringRefNull menu_name, StringRefNull menu_name,
uiLayout &layout); uiLayout &layout);
void draw_node_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item,
StringRefNull operator_id,
StringRefNull menu_name,
uiLayout &layout);
void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset, void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset,
PointerRNA &ptr); PointerRNA &ptr);
bool operator_asset_reference_props_is_set(PointerRNA &ptr); bool operator_asset_reference_props_is_set(PointerRNA &ptr);

View File

@@ -20,6 +20,8 @@
#include "ED_node_c.hh" #include "ED_node_c.hh"
#include "UI_interface_layout.hh"
struct SpaceNode; struct SpaceNode;
struct ARegion; struct ARegion;
struct Main; struct Main;
@@ -136,7 +138,8 @@ bool node_editor_is_for_geometry_nodes_modifier(const SpaceNode &snode,
void ui_template_node_asset_menu_items(uiLayout &layout, void ui_template_node_asset_menu_items(uiLayout &layout,
const bContext &C, const bContext &C,
StringRef catalog_path); StringRef catalog_path,
const NodeAssetMenuOperatorType operator_type);
/** See #SpaceNode_Runtime::node_can_sync_states. */ /** See #SpaceNode_Runtime::node_can_sync_states. */
Map<int, bool> &node_can_sync_cache_get(SpaceNode &snode); Map<int, bool> &node_can_sync_cache_get(SpaceNode &snode);

View File

@@ -89,6 +89,11 @@ enum class LayoutSeparatorType : int8_t {
Line, Line,
}; };
enum class NodeAssetMenuOperatorType : int8_t {
Add,
Swap,
};
/** /**
* NOTE: `uiLayout` properties should be considered private outside `interface_layout.cc`, * NOTE: `uiLayout` properties should be considered private outside `interface_layout.cc`,
* incoming refactors would remove public access and add public read/write function methods. * incoming refactors would remove public access and add public read/write function methods.

View File

@@ -153,7 +153,7 @@ static Set<StringRef> get_builtin_menus(const int tree_type)
return {}; return {};
} }
static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) static void node_catalog_assets_draw(const bContext *C, Menu *menu)
{ {
SpaceNode &snode = *CTX_wm_space_node(C); SpaceNode &snode = *CTX_wm_space_node(C);
const bNodeTree *edit_tree = snode.edittree; const bNodeTree *edit_tree = snode.edittree;
@@ -172,6 +172,12 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
if (!menu_path) { if (!menu_path) {
return; return;
} }
const std::optional<blender::StringRefNull> operator_id = CTX_data_string_get(C, "operator_id");
if (!operator_id) {
return;
}
const Span<asset_system::AssetRepresentation *> assets = tree.assets_per_path.lookup( const Span<asset_system::AssetRepresentation *> assets = tree.assets_per_path.lookup(
menu_path->c_str()); menu_path->c_str());
const asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item( const asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item(
@@ -190,7 +196,7 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
layout->separator(); layout->separator();
add_separator = false; add_separator = false;
} }
PointerRNA op_ptr = layout->op("NODE_OT_add_group_asset", PointerRNA op_ptr = layout->op(*operator_id,
IFACE_(asset->get_name()), IFACE_(asset->get_name()),
ICON_NONE, ICON_NONE,
wm::OpCallContext::InvokeRegionWin, wm::OpCallContext::InvokeRegionWin,
@@ -208,17 +214,23 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
layout->separator(); layout->separator();
add_separator = false; add_separator = false;
} }
asset::draw_menu_for_catalog(item, "NODE_MT_node_add_catalog_assets", *layout); asset::draw_node_menu_for_catalog(item, *operator_id, "NODE_MT_node_catalog_assets", *layout);
}); });
} }
static void node_add_unassigned_assets_draw(const bContext *C, Menu *menu) static void node_unassigned_assets_draw(const bContext *C, Menu *menu)
{ {
SpaceNode &snode = *CTX_wm_space_node(C); SpaceNode &snode = *CTX_wm_space_node(C);
const bNodeTree *edit_tree = snode.edittree; const bNodeTree *edit_tree = snode.edittree;
if (!edit_tree) { if (!edit_tree) {
return; return;
} }
const std::optional<blender::StringRefNull> operator_id = CTX_data_string_get(C, "operator_id");
if (!operator_id) {
return;
}
if (!snode.runtime->assets_for_menu) { if (!snode.runtime->assets_for_menu) {
snode.runtime->assets_for_menu = std::make_shared<asset::AssetItemTree>( snode.runtime->assets_for_menu = std::make_shared<asset::AssetItemTree>(
build_catalog_tree(*C, *edit_tree)); build_catalog_tree(*C, *edit_tree));
@@ -226,7 +238,7 @@ static void node_add_unassigned_assets_draw(const bContext *C, Menu *menu)
} }
asset::AssetItemTree &tree = *snode.runtime->assets_for_menu; asset::AssetItemTree &tree = *snode.runtime->assets_for_menu;
for (const asset_system::AssetRepresentation *asset : tree.unassigned_assets) { for (const asset_system::AssetRepresentation *asset : tree.unassigned_assets) {
PointerRNA op_ptr = menu->layout->op("NODE_OT_add_group_asset", PointerRNA op_ptr = menu->layout->op(*operator_id,
IFACE_(asset->get_name()), IFACE_(asset->get_name()),
ICON_NONE, ICON_NONE,
wm::OpCallContext::InvokeRegionWin, wm::OpCallContext::InvokeRegionWin,
@@ -235,7 +247,7 @@ static void node_add_unassigned_assets_draw(const bContext *C, Menu *menu)
} }
} }
static void add_root_catalogs_draw(const bContext *C, Menu *menu) static void root_catalogs_draw(const bContext *C, Menu *menu, const StringRefNull operator_id)
{ {
SpaceNode &snode = *CTX_wm_space_node(C); SpaceNode &snode = *CTX_wm_space_node(C);
uiLayout *layout = menu->layout; uiLayout *layout = menu->layout;
@@ -264,33 +276,49 @@ static void add_root_catalogs_draw(const bContext *C, Menu *menu)
tree.catalogs.foreach_root_item([&](const asset_system::AssetCatalogTreeItem &item) { tree.catalogs.foreach_root_item([&](const asset_system::AssetCatalogTreeItem &item) {
if (!all_builtin_menus.contains_as(item.catalog_path().str())) { if (!all_builtin_menus.contains_as(item.catalog_path().str())) {
asset::draw_menu_for_catalog(item, "NODE_MT_node_add_catalog_assets", *layout); asset::draw_node_menu_for_catalog(item, operator_id, "NODE_MT_node_catalog_assets", *layout);
} }
}); });
if (!tree.unassigned_assets.is_empty()) { if (!tree.unassigned_assets.is_empty()) {
layout->separator(); layout->separator();
layout->menu("NODE_MT_node_add_unassigned_assets", IFACE_("Unassigned"), ICON_FILE_HIDDEN); layout->menu("NODE_MT_node_unassigned_assets", IFACE_("Unassigned"), ICON_FILE_HIDDEN);
} }
} }
MenuType add_catalog_assets_menu_type() static void add_root_catalogs_draw(const bContext *C, Menu *menu)
{
const StringRefNull operator_id = "NODE_OT_add_group_asset";
menu->layout->context_string_set("operator_id", operator_id);
root_catalogs_draw(C, menu, operator_id);
}
static void swap_root_catalogs_draw(const bContext *C, Menu *menu)
{
const StringRefNull operator_id = "NODE_OT_swap_group_asset";
menu->layout->context_string_set("operator_id", operator_id);
root_catalogs_draw(C, menu, operator_id);
}
MenuType catalog_assets_menu_type()
{ {
MenuType type{}; MenuType type{};
STRNCPY_UTF8(type.idname, "NODE_MT_node_add_catalog_assets"); STRNCPY_UTF8(type.idname, "NODE_MT_node_catalog_assets");
type.poll = node_add_menu_poll; type.poll = node_add_menu_poll;
type.draw = node_add_catalog_assets_draw; type.draw = node_catalog_assets_draw;
type.listener = asset::list::asset_reading_region_listen_fn; type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent; type.flag = MenuTypeFlag::ContextDependent;
return type; return type;
} }
MenuType add_unassigned_assets_menu_type() MenuType unassigned_assets_menu_type()
{ {
MenuType type{}; MenuType type{};
STRNCPY_UTF8(type.idname, "NODE_MT_node_add_unassigned_assets"); STRNCPY_UTF8(type.idname, "NODE_MT_node_unassigned_assets");
type.poll = node_add_menu_poll; type.poll = node_add_menu_poll;
type.draw = node_add_unassigned_assets_draw; type.draw = node_unassigned_assets_draw;
type.listener = asset::list::asset_reading_region_listen_fn; type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent; type.flag = MenuTypeFlag::ContextDependent;
type.description = N_( type.description = N_(
@@ -309,9 +337,20 @@ MenuType add_root_catalogs_menu_type()
return type; return type;
} }
MenuType swap_root_catalogs_menu_type()
{
MenuType type{};
STRNCPY_UTF8(type.idname, "NODE_MT_node_swap_root_catalogs");
type.poll = node_add_menu_poll;
type.draw = swap_root_catalogs_draw;
type.listener = asset::list::asset_reading_region_listen_fn;
return type;
}
void ui_template_node_asset_menu_items(uiLayout &layout, void ui_template_node_asset_menu_items(uiLayout &layout,
const bContext &C, const bContext &C,
const StringRef catalog_path) const StringRef catalog_path,
const NodeAssetMenuOperatorType operator_type)
{ {
SpaceNode &snode = *CTX_wm_space_node(&C); SpaceNode &snode = *CTX_wm_space_node(&C);
if (snode.runtime->assets_for_menu == nullptr) { if (snode.runtime->assets_for_menu == nullptr) {
@@ -322,9 +361,21 @@ void ui_template_node_asset_menu_items(uiLayout &layout,
if (!item) { if (!item) {
return; return;
} }
StringRef operator_id;
switch (operator_type) {
case NodeAssetMenuOperatorType::Swap:
operator_id = "NODE_OT_swap_group_asset";
break;
default:
operator_id = "NODE_OT_add_group_asset";
}
uiLayout *col = &layout.column(false); uiLayout *col = &layout.column(false);
col->context_string_set("asset_catalog_path", item->catalog_path().str()); col->context_string_set("asset_catalog_path", item->catalog_path().str());
col->menu_contents("NODE_MT_node_add_catalog_assets"); col->context_string_set("operator_id", operator_id);
col->menu_contents("NODE_MT_node_catalog_assets");
} }
} // namespace blender::ed::space_node } // namespace blender::ed::space_node

View File

@@ -361,6 +361,27 @@ static bool node_add_group_poll(bContext *C)
return true; return true;
} }
static bool node_swap_group_poll(bContext *C)
{
if (!ED_operator_node_editable(C)) {
return false;
}
const SpaceNode *snode = CTX_wm_space_node(C);
if (snode->edittree->type == NTREE_CUSTOM) {
CTX_wm_operator_poll_msg_set(
C, "Adding node groups isn't supported for custom (Python defined) node trees");
return false;
}
Vector<PointerRNA> selected_nodes;
selected_nodes = CTX_data_collection_get(C, "selected_nodes");
if (selected_nodes.size() <= 0) {
CTX_wm_operator_poll_msg_set(C, "No nodes selected.");
return false;
}
return true;
}
static wmOperatorStatus node_add_group_invoke(bContext *C, wmOperator *op, const wmEvent *event) static wmOperatorStatus node_add_group_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{ {
ARegion *region = CTX_wm_region(C); ARegion *region = CTX_wm_region(C);
@@ -490,6 +511,78 @@ static wmOperatorStatus node_add_group_asset_invoke(bContext *C,
return OPERATOR_FINISHED; return OPERATOR_FINISHED;
} }
static wmOperatorStatus node_swap_group_asset_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
ARegion &region = *CTX_wm_region(C);
Main &bmain = *CTX_data_main(C);
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &ntree = *snode.edittree;
const asset_system::AssetRepresentation *asset =
asset::operator_asset_reference_props_get_asset_from_all_library(*C, *op->ptr, op->reports);
if (!asset) {
return OPERATOR_CANCELLED;
}
bNodeTree *node_group = reinterpret_cast<bNodeTree *>(
asset::asset_local_id_ensure_imported(bmain, *asset));
/* Convert mouse coordinates to v2d space. */
UI_view2d_region_to_view(&region.v2d,
event->mval[0],
event->mval[1],
&snode.runtime->cursor[0],
&snode.runtime->cursor[1]);
snode.runtime->cursor /= UI_SCALE_FAC;
const StringRef node_idname = node_group_idname(C);
if (node_idname[0] == '\0') {
BKE_report(op->reports, RPT_WARNING, "Could not determine type of group node");
return OPERATOR_CANCELLED;
}
wmOperatorType *ot = WM_operatortype_find("NODE_OT_swap_node", true);
BLI_assert(ot);
PointerRNA ptr;
PointerRNA itemptr;
WM_operator_properties_create_ptr(&ptr, ot);
RNA_string_set(&ptr, "type", node_idname.data());
/* Assign node group via operator.settings. This needs to be done here so that NODE_OT_swap_node
* can preserve matching links */
/* Assigning it in the for-loop along with the other node group properties causes the links to
* not be preserved*/
RNA_collection_add(&ptr, "settings", &itemptr);
RNA_string_set(&itemptr, "name", "node_tree");
std::string setting_value = "bpy.data.node_groups[\"" +
std::string(BKE_id_name(node_group->id)) + "\"]";
RNA_string_set(&itemptr, "value", setting_value.c_str());
WM_operator_name_call_ptr(C, ot, wm::OpCallContext::InvokeDefault, &ptr, nullptr);
WM_operator_properties_free(&ptr);
for (bNode *group_node : get_selected_nodes(ntree)) {
STRNCPY_UTF8(group_node->name, BKE_id_name(node_group->id));
bke::node_unique_name(*snode.edittree, *group_node);
/* By default, don't show the data-block selector since it's not usually necessary for assets.
*/
group_node->flag &= ~NODE_OPTIONS;
group_node->width = node_group->default_group_node_width;
id_us_plus(group_node->id);
BKE_ntree_update_tag_node_property(&ntree, group_node);
}
BKE_main_ensure_invariants(bmain);
WM_event_add_notifier(C, NC_NODE | NA_ADDED, nullptr);
DEG_relations_tag_update(&bmain);
return OPERATOR_FINISHED;
}
static std::string node_add_group_asset_get_description(bContext *C, static std::string node_add_group_asset_get_description(bContext *C,
wmOperatorType * /*ot*/, wmOperatorType * /*ot*/,
PointerRNA *ptr) PointerRNA *ptr)
@@ -521,6 +614,21 @@ void NODE_OT_add_group_asset(wmOperatorType *ot)
asset::operator_asset_reference_props_register(*ot->srna); asset::operator_asset_reference_props_register(*ot->srna);
} }
void NODE_OT_swap_group_asset(wmOperatorType *ot)
{
ot->name = "Swap Node Group Asset";
ot->description = "Swap selected nodes with the specified node group asset";
ot->idname = "NODE_OT_swap_group_asset";
ot->invoke = node_swap_group_asset_invoke;
ot->poll = node_swap_group_poll;
ot->get_description = node_add_group_asset_get_description;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
asset::operator_asset_reference_props_register(*ot->srna);
}
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@@ -307,6 +307,7 @@ void NODE_OT_add_mask(wmOperatorType *ot);
void NODE_OT_add_material(wmOperatorType *ot); void NODE_OT_add_material(wmOperatorType *ot);
void NODE_OT_add_color(wmOperatorType *ot); void NODE_OT_add_color(wmOperatorType *ot);
void NODE_OT_add_import_node(wmOperatorType *ot); void NODE_OT_add_import_node(wmOperatorType *ot);
void NODE_OT_swap_group_asset(wmOperatorType *ot);
void NODE_OT_new_node_tree(wmOperatorType *ot); void NODE_OT_new_node_tree(wmOperatorType *ot);
void NODE_OT_new_compositing_node_group(wmOperatorType *ot); void NODE_OT_new_compositing_node_group(wmOperatorType *ot);
void NODE_OT_add_group_input_node(wmOperatorType *ot); void NODE_OT_add_group_input_node(wmOperatorType *ot);
@@ -444,10 +445,12 @@ void invoke_node_link_drag_add_menu(bContext &C,
/* `add_menu_assets.cc` */ /* `add_menu_assets.cc` */
MenuType add_catalog_assets_menu_type(); MenuType catalog_assets_menu_type();
MenuType add_unassigned_assets_menu_type(); MenuType unassigned_assets_menu_type();
MenuType add_root_catalogs_menu_type(); MenuType add_root_catalogs_menu_type();
MenuType swap_root_catalogs_menu_type();
/* `node_sync_sockets.cc` */ /* `node_sync_sockets.cc` */
void NODE_OT_sockets_sync(wmOperatorType *ot); void NODE_OT_sockets_sync(wmOperatorType *ot);

View File

@@ -91,6 +91,8 @@ void node_operatortypes()
WM_operatortype_append(NODE_OT_add_import_node); WM_operatortype_append(NODE_OT_add_import_node);
WM_operatortype_append(NODE_OT_add_group_input_node); WM_operatortype_append(NODE_OT_add_group_input_node);
WM_operatortype_append(NODE_OT_swap_group_asset);
WM_operatortype_append(NODE_OT_new_node_tree); WM_operatortype_append(NODE_OT_new_node_tree);
WM_operatortype_append(NODE_OT_new_compositing_node_group); WM_operatortype_append(NODE_OT_new_compositing_node_group);

View File

@@ -1884,9 +1884,10 @@ void ED_spacetype_node()
art->draw = node_toolbar_region_draw; art->draw = node_toolbar_region_draw;
BLI_addhead(&st->regiontypes, art); BLI_addhead(&st->regiontypes, art);
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_catalog_assets_menu_type())); WM_menutype_add(MEM_dupallocN<MenuType>(__func__, catalog_assets_menu_type()));
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_unassigned_assets_menu_type())); WM_menutype_add(MEM_dupallocN<MenuType>(__func__, unassigned_assets_menu_type()));
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_root_catalogs_menu_type())); WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_root_catalogs_menu_type()));
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, swap_root_catalogs_menu_type()));
BKE_spacetype_register(std::move(st)); BKE_spacetype_register(std::move(st));
} }

View File

@@ -898,10 +898,12 @@ void rna_uiLayoutPanel(uiLayout *layout,
static void rna_uiLayout_template_node_asset_menu_items(uiLayout *layout, static void rna_uiLayout_template_node_asset_menu_items(uiLayout *layout,
bContext *C, bContext *C,
const char *catalog_path) const char *catalog_path,
const int operator_type)
{ {
using namespace blender; using namespace blender;
ed::space_node::ui_template_node_asset_menu_items(*layout, *C, StringRef(catalog_path)); ed::space_node::ui_template_node_asset_menu_items(
*layout, *C, StringRef(catalog_path), NodeAssetMenuOperatorType(operator_type));
} }
static void rna_uiLayout_template_node_operator_asset_menu_items(uiLayout *layout, static void rna_uiLayout_template_node_operator_asset_menu_items(uiLayout *layout,
@@ -1201,6 +1203,20 @@ void RNA_api_ui_layout(StructRNA *srna)
{0, nullptr, 0, nullptr, nullptr}, {0, nullptr, 0, nullptr, nullptr},
}; };
static const EnumPropertyItem rna_enum_template_node_operator_type[] = {
{int(NodeAssetMenuOperatorType::Add),
"ADD",
0,
"Add Node",
"Add a node to the active tree."},
{int(NodeAssetMenuOperatorType::Swap),
"SWAP",
0,
"Swap Node",
"Replace the selected nodes with the specified type."},
{0, nullptr, 0, nullptr, nullptr},
};
static const float node_socket_color_default[] = {0.0f, 0.0f, 0.0f, 1.0f}; static const float node_socket_color_default[] = {0.0f, 0.0f, 0.0f, 1.0f};
/* simple layout specifiers */ /* simple layout specifiers */
@@ -2123,6 +2139,12 @@ void RNA_api_ui_layout(StructRNA *srna)
srna, "template_node_asset_menu_items", "rna_uiLayout_template_node_asset_menu_items"); srna, "template_node_asset_menu_items", "rna_uiLayout_template_node_asset_menu_items");
RNA_def_function_flag(func, FUNC_USE_CONTEXT); RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", ""); parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", "");
parm = RNA_def_enum(func,
"operator",
rna_enum_template_node_operator_type,
int(NodeAssetMenuOperatorType::Add),
"Operator",
"The operator the asset menu will use");
func = RNA_def_function(srna, func = RNA_def_function(srna,
"template_modifier_asset_menu_items", "template_modifier_asset_menu_items",