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")
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
#
@@ -499,7 +490,6 @@ classes = (
NWLinkUseNodeNameMenu,
NWLinkUseOutputsNamesMenu,
NWAttributeMenu,
NWSwitchNodeTypeMenu,
)

View File

@@ -357,8 +357,6 @@ kmi_defs = (
(('name', interface.NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
('wm.call_menu', 'C', 'PRESS', False, True, False,
(('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 = (

View File

@@ -2231,6 +2231,7 @@ def km_node_editor(params):
("node.link_make", {"type": 'J', "value": 'PRESS', "shift": True},
{"properties": [("replace", 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},
{"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", 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):
__slots__ = ()
@@ -39,14 +117,7 @@ class NodeSetting(PropertyGroup):
)
# Base class for node "Add" operators.
class NodeAddOperator:
use_transform: BoolProperty(
name="Use Transform",
description="Start transform operator after inserting the node",
default=False,
)
class NodeOperator:
settings: CollectionProperty(
name="Settings",
description="Settings to be applied on the newly created node",
@@ -54,6 +125,77 @@ class NodeAddOperator:
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
def store_mouse_cursor(context, event):
space = context.space_data
@@ -72,49 +214,6 @@ class NodeAddOperator:
else:
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
def poll(cls, context):
space = context.space_data
@@ -135,6 +234,176 @@ class NodeAddOperator:
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.
class NODE_OT_add_node(NodeAddOperator, Operator):
"""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"):
self.deselect_nodes(context)
if node := self.create_node(context, self.type):
self.apply_node_settings(node)
if self.visible_output:
for socket in node.outputs:
if socket.name != self.visible_output:
@@ -166,22 +436,91 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
else:
return {'CANCELLED'}
@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 ""
class NODE_OT_swap_node(NodeSwapOperator, Operator):
"""Replace the selected nodes with the specified type"""
bl_idname = "node.swap_node"
bl_label = "Swap Node"
bl_options = {"REGISTER", "UNDO"}
type: StringProperty(
name="Node Type",
description="Node type",
)
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):
@@ -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_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)
self.deselect_nodes(context)
node = self.create_node(context, node_tree_group_type[tree.bl_idname])
self.apply_node_settings(node)
node.node_tree = group
return {"FINISHED"}
@@ -213,7 +559,45 @@ class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator):
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(
name="Offset",
description="Offset of nodes from the cursor when added",
@@ -221,6 +605,25 @@ class NodeAddZoneOperator(NodeAddOperator):
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
def execute(self, context):
@@ -230,6 +633,10 @@ class NodeAddZoneOperator(NodeAddOperator):
self.deselect_nodes(context)
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'}
@@ -249,6 +656,145 @@ class NodeAddZoneOperator(NodeAddOperator):
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):
"""Add simulation zone input and output nodes to the active tree"""
bl_idname = "node.add_simulation_zone"
@@ -750,8 +1296,12 @@ classes = (
NODE_FH_image_node,
NODE_OT_add_empty_group,
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_repeat_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):
"""Add a node type to a menu."""
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(
"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")
return AddNodeMenu.node_operator(
layout,
node_type,
label=label,
poll=poll,
search_weight=search_weight,
translate=translate)
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)
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)
return AddNodeMenu.node_operator_with_searchable_enum(context, layout, node_idname, property_name, search_weight)
def add_node_type_with_searchable_enum_socket(
@@ -139,45 +47,32 @@ def add_node_type_with_searchable_enum_socket(
node_idname,
socket_identifier,
enum_names,
search_weight=0.0,
):
add_node_type(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 = add_node_type(
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)
search_weight=0.0):
return AddNodeMenu.node_operator_with_searchable_enum_socket(
context, layout, node_idname, socket_identifier, enum_names, search_weight)
def add_node_type_with_outputs(context, layout, node_type, subnames, *, label=None, search_weight=0.0):
return AddNodeMenu.node_operator_with_outputs(
context,
layout,
node_type,
subnames,
label=label,
search_weight=search_weight)
def add_color_mix_node(context, layout):
label = iface_("Mix Color")
props = node_add_menu.add_node_type(layout, "ShaderNodeMix", label=label, translate=False)
ops = props.settings.add()
ops.name = "data_type"
ops.value = "'RGBA'"
return AddNodeMenu.color_mix_node(context, layout)
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 = node_add_menu.add_node_type(
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)
def add_empty_group(layout):
return AddNodeMenu.new_empty_group(layout)
def draw_node_group_add_menu(context, layout):
"""Add items to the layout used for interacting with node groups."""
return AddNodeMenu.draw_group_menu(context, layout)
def add_simulation_zone(layout, label):
@@ -210,26 +105,385 @@ def add_closure_zone(layout, label):
return props
def add_empty_group(layout):
props = layout.operator("node.add_empty_group", text="New Group", text_ctxt=i18n_contexts.default)
props.use_transform = True
return props
class NodeMenu(Menu):
"""A baseclass defining the shared methods for AddNodeMenu and SwapNodeMenu"""
draw_assets: bool
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):
bl_idname = "NODE_MT_category_layout"
class AddNodeMenu(NodeMenu):
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"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "NodeFrame", search_weight=-1)
node_add_menu.add_node_type(layout, "NodeReroute")
self.node_operator(layout, "NodeFrame", search_weight=-1)
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 = (
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.

View File

@@ -9,373 +9,395 @@ from bpy.app.translations import (
)
class NODE_MT_category_compositor_input(Menu):
bl_idname = "NODE_MT_category_compositor_input"
class NODE_MT_compositor_node_input_base(node_add_menu.NodeMenu):
bl_label = "Input"
def draw(self, context):
del context
layout = self.layout
layout.menu("NODE_MT_category_compositor_input_constant")
self.draw_menu(layout, path="Input/Constant")
layout.separator()
node_add_menu.add_node_type(layout, "NodeGroupInput")
node_add_menu.add_node_type(layout, "CompositorNodeBokehImage")
node_add_menu.add_node_type(layout, "CompositorNodeImage")
node_add_menu.add_node_type(layout, "CompositorNodeImageInfo")
node_add_menu.add_node_type(layout, "CompositorNodeImageCoordinates")
node_add_menu.add_node_type(layout, "CompositorNodeMask")
node_add_menu.add_node_type(layout, "CompositorNodeMovieClip")
self.node_operator(layout, "NodeGroupInput")
self.node_operator(layout, "CompositorNodeBokehImage")
self.node_operator(layout, "CompositorNodeImage")
self.node_operator(layout, "CompositorNodeImageInfo")
self.node_operator(layout, "CompositorNodeImageCoordinates")
self.node_operator(layout, "CompositorNodeMask")
self.node_operator(layout, "CompositorNodeMovieClip")
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):
bl_idname = "NODE_MT_category_compositor_input_constant"
class NODE_MT_compositor_node_input_constant_base(node_add_menu.NodeMenu):
bl_label = "Constant"
menu_path = "Input/Constant"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRGB")
node_add_menu.add_node_type(layout, "ShaderNodeValue")
node_add_menu.add_node_type(layout, "CompositorNodeNormal")
self.node_operator(layout, "CompositorNodeRGB")
self.node_operator(layout, "ShaderNodeValue")
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):
bl_idname = "NODE_MT_category_compositor_input_scene"
class NODE_MT_compositor_node_input_scene_base(node_add_menu.NodeMenu):
bl_label = "Scene"
menu_path = "Input/Scene"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRLayers")
node_add_menu.add_node_type_with_outputs(context, layout, "CompositorNodeSceneTime", ["Frame", "Seconds"])
node_add_menu.add_node_type(layout, "CompositorNodeTime")
self.node_operator(layout, "CompositorNodeRLayers")
self.node_operator_with_outputs(context, layout, "CompositorNodeSceneTime", ["Frame", "Seconds"])
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):
bl_idname = "NODE_MT_category_compositor_output"
class NODE_MT_compositor_node_output_base(node_add_menu.NodeMenu):
bl_label = "Output"
def draw(self, context):
del context
layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupOutput")
node_add_menu.add_node_type(layout, "CompositorNodeViewer")
self.node_operator(layout, "NodeGroupOutput")
self.node_operator(layout, "CompositorNodeViewer")
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):
bl_idname = "NODE_MT_category_compositor_color"
class NODE_MT_compositor_node_color_base(node_add_menu.NodeMenu):
bl_label = "Color"
def draw(self, _context):
layout = self.layout
layout.menu("NODE_MT_category_compositor_color_adjust")
self.draw_menu(layout, path="Color/Adjust")
layout.separator()
layout.menu("NODE_MT_category_compositor_color_mix")
self.draw_menu(layout, path="Color/Mix")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodePremulKey")
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "CompositorNodeConvertColorSpace")
node_add_menu.add_node_type(layout, "CompositorNodeConvertToDisplay")
node_add_menu.add_node_type(layout, "CompositorNodeSetAlpha")
self.node_operator(layout, "CompositorNodePremulKey")
self.node_operator(layout, "ShaderNodeBlackbody")
self.node_operator(layout, "ShaderNodeValToRGB")
self.node_operator(layout, "CompositorNodeConvertColorSpace")
self.node_operator(layout, "CompositorNodeConvertToDisplay")
self.node_operator(layout, "CompositorNodeSetAlpha")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeInvert")
node_add_menu.add_node_type(layout, "CompositorNodeRGBToBW")
self.node_operator(layout, "CompositorNodeInvert")
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):
bl_idname = "NODE_MT_category_compositor_color_adjust"
class NODE_MT_compositor_node_color_adjust_base(node_add_menu.NodeMenu):
bl_label = "Adjust"
menu_path = "Color/Adjust"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeBrightContrast")
node_add_menu.add_node_type(layout, "CompositorNodeColorBalance")
node_add_menu.add_node_type(layout, "CompositorNodeColorCorrection")
node_add_menu.add_node_type(layout, "CompositorNodeExposure")
node_add_menu.add_node_type(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "CompositorNodeHueCorrect")
node_add_menu.add_node_type(layout, "CompositorNodeHueSat")
node_add_menu.add_node_type(layout, "CompositorNodeCurveRGB")
node_add_menu.add_node_type(layout, "CompositorNodeTonemap")
self.node_operator(layout, "CompositorNodeBrightContrast")
self.node_operator(layout, "CompositorNodeColorBalance")
self.node_operator(layout, "CompositorNodeColorCorrection")
self.node_operator(layout, "CompositorNodeExposure")
self.node_operator(layout, "ShaderNodeGamma")
self.node_operator(layout, "CompositorNodeHueCorrect")
self.node_operator(layout, "CompositorNodeHueSat")
self.node_operator(layout, "CompositorNodeCurveRGB")
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):
bl_idname = "NODE_MT_category_compositor_color_mix"
class NODE_MT_compositor_node_color_mix_base(node_add_menu.NodeMenu):
bl_label = "Mix"
menu_path = "Color/Mix"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeAlphaOver")
self.node_operator(layout, "CompositorNodeAlphaOver")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeCombineColor")
node_add_menu.add_node_type(layout, "CompositorNodeSeparateColor")
self.node_operator(layout, "CompositorNodeCombineColor")
self.node_operator(layout, "CompositorNodeSeparateColor")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeZcombine")
node_add_menu.add_color_mix_node(context, layout)
node_add_menu.draw_assets_for_catalog(layout, "Color/Mix")
self.node_operator(layout, "CompositorNodeZcombine")
self.color_mix_node(context, layout)
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_compositor_filter(Menu):
bl_idname = "NODE_MT_category_compositor_filter"
class NODE_MT_compositor_node_filter_base(node_add_menu.NodeMenu):
bl_label = "Filter"
def draw(self, context):
layout = self.layout
layout.menu("NODE_MT_category_compositor_filter_blur")
self.draw_menu(layout, path="Filter/Blur")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeAntiAliasing")
node_add_menu.add_node_type(layout, "CompositorNodeConvolve")
node_add_menu.add_node_type(layout, "CompositorNodeDenoise")
node_add_menu.add_node_type(layout, "CompositorNodeDespeckle")
self.node_operator(layout, "CompositorNodeAntiAliasing")
self.node_operator(layout, "CompositorNodeConvolve")
self.node_operator(layout, "CompositorNodeDenoise")
self.node_operator(layout, "CompositorNodeDespeckle")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDilateErode")
node_add_menu.add_node_type(layout, "CompositorNodeInpaint")
self.node_operator(layout, "CompositorNodeDilateErode")
self.node_operator(layout, "CompositorNodeInpaint")
layout.separator()
node_add_menu.add_node_type_with_searchable_enum_socket(
self.node_operator_with_searchable_enum_socket(
context, layout, "CompositorNodeFilter", "Type", [
"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", [
"Bloom", "Ghosts", "Streaks", "Fog Glow", "Simple Star", "Sun Beams", "Kernel"])
node_add_menu.add_node_type(layout, "CompositorNodeKuwahara")
node_add_menu.add_node_type(layout, "CompositorNodePixelate")
node_add_menu.add_node_type(layout, "CompositorNodePosterize")
self.node_operator(layout, "CompositorNodeKuwahara")
self.node_operator(layout, "CompositorNodePixelate")
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):
bl_idname = "NODE_MT_category_compositor_filter_blur"
class NODE_MT_compositor_node_filter_blur_base(node_add_menu.NodeMenu):
bl_label = "Blur"
menu_path = "Filter/Blur"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeBilateralblur")
node_add_menu.add_node_type(layout, "CompositorNodeBlur")
node_add_menu.add_node_type(layout, "CompositorNodeBokehBlur")
node_add_menu.add_node_type(layout, "CompositorNodeDefocus")
node_add_menu.add_node_type(layout, "CompositorNodeDBlur")
node_add_menu.add_node_type(layout, "CompositorNodeVecBlur")
self.node_operator(layout, "CompositorNodeBilateralblur")
self.node_operator(layout, "CompositorNodeBlur")
self.node_operator(layout, "CompositorNodeBokehBlur")
self.node_operator(layout, "CompositorNodeDefocus")
self.node_operator(layout, "CompositorNodeDBlur")
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):
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"
class NODE_MT_compositor_node_keying_base(node_add_menu.NodeMenu):
bl_label = "Keying"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeChannelMatte")
node_add_menu.add_node_type(layout, "CompositorNodeChromaMatte")
node_add_menu.add_node_type(layout, "CompositorNodeColorMatte")
node_add_menu.add_node_type(layout, "CompositorNodeColorSpill")
node_add_menu.add_node_type(layout, "CompositorNodeDiffMatte")
node_add_menu.add_node_type(layout, "CompositorNodeDistanceMatte")
node_add_menu.add_node_type(layout, "CompositorNodeKeying")
node_add_menu.add_node_type(layout, "CompositorNodeKeyingScreen")
node_add_menu.add_node_type(layout, "CompositorNodeLumaMatte")
self.node_operator(layout, "CompositorNodeChannelMatte")
self.node_operator(layout, "CompositorNodeChromaMatte")
self.node_operator(layout, "CompositorNodeColorMatte")
self.node_operator(layout, "CompositorNodeColorSpill")
self.node_operator(layout, "CompositorNodeDiffMatte")
self.node_operator(layout, "CompositorNodeDistanceMatte")
self.node_operator(layout, "CompositorNodeKeying")
self.node_operator(layout, "CompositorNodeKeyingScreen")
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):
bl_idname = "NODE_MT_category_compositor_mask"
class NODE_MT_compositor_node_mask_base(node_add_menu.NodeMenu):
bl_label = "Mask"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeCryptomatteV2")
node_add_menu.add_node_type(layout, "CompositorNodeCryptomatte")
self.node_operator(layout, "CompositorNodeCryptomatteV2")
self.node_operator(layout, "CompositorNodeCryptomatte")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeBoxMask")
node_add_menu.add_node_type(layout, "CompositorNodeEllipseMask")
self.node_operator(layout, "CompositorNodeBoxMask")
self.node_operator(layout, "CompositorNodeEllipseMask")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDoubleEdgeMask")
node_add_menu.add_node_type(layout, "CompositorNodeIDMask")
self.node_operator(layout, "CompositorNodeDoubleEdgeMask")
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):
bl_idname = "NODE_MT_category_compositor_tracking"
class NODE_MT_compositor_node_tracking_base(node_add_menu.NodeMenu):
bl_label = "Tracking"
bl_translation_context = i18n_contexts.id_movieclip
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodePlaneTrackDeform")
node_add_menu.add_node_type(layout, "CompositorNodeStabilize")
node_add_menu.add_node_type(layout, "CompositorNodeTrackPos")
self.node_operator(layout, "CompositorNodePlaneTrackDeform")
self.node_operator(layout, "CompositorNodeStabilize")
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):
bl_idname = "NODE_MT_category_compositor_transform"
class NODE_MT_compositor_node_transform_base(node_add_menu.NodeMenu):
bl_label = "Transform"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "CompositorNodeRotate")
node_add_menu.add_node_type(layout, "CompositorNodeScale")
node_add_menu.add_node_type(layout, "CompositorNodeTransform")
node_add_menu.add_node_type(layout, "CompositorNodeTranslate")
self.node_operator(layout, "CompositorNodeRotate")
self.node_operator(layout, "CompositorNodeScale")
self.node_operator(layout, "CompositorNodeTransform")
self.node_operator(layout, "CompositorNodeTranslate")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeCornerPin")
node_add_menu.add_node_type(layout, "CompositorNodeCrop")
self.node_operator(layout, "CompositorNodeCornerPin")
self.node_operator(layout, "CompositorNodeCrop")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeDisplace")
node_add_menu.add_node_type(layout, "CompositorNodeFlip")
node_add_menu.add_node_type(layout, "CompositorNodeMapUV")
self.node_operator(layout, "CompositorNodeDisplace")
self.node_operator(layout, "CompositorNodeFlip")
self.node_operator(layout, "CompositorNodeMapUV")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeLensdist")
node_add_menu.add_node_type(layout, "CompositorNodeMovieDistortion")
self.node_operator(layout, "CompositorNodeLensdist")
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):
bl_idname = "NODE_MT_category_compositor_texture"
class NODE_MT_compositor_node_texture_base(node_add_menu.NodeMenu):
bl_label = "Texture"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise")
self.node_operator(layout, "ShaderNodeTexBrick")
self.node_operator(layout, "ShaderNodeTexChecker")
self.node_operator(layout, "ShaderNodeTexGabor")
self.node_operator(layout, "ShaderNodeTexGradient")
self.node_operator(layout, "ShaderNodeTexMagic")
self.node_operator(layout, "ShaderNodeTexNoise")
self.node_operator(layout, "ShaderNodeTexVoronoi")
self.node_operator(layout, "ShaderNodeTexWave")
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):
bl_idname = "NODE_MT_category_compositor_utilities"
class NODE_MT_compositor_node_utilities_base(node_add_menu.NodeMenu):
bl_label = "Utilities"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix")
node_add_menu.add_node_type(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve")
self.node_operator(layout, "ShaderNodeMapRange")
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
self.node_operator(layout, "ShaderNodeMix")
self.node_operator(layout, "ShaderNodeClamp")
self.node_operator(layout, "ShaderNodeFloatCurve")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeLevels")
node_add_menu.add_node_type(layout, "CompositorNodeNormalize")
self.node_operator(layout, "CompositorNodeLevels")
self.node_operator(layout, "CompositorNodeNormalize")
layout.separator()
node_add_menu.add_node_type(layout, "CompositorNodeSplit")
node_add_menu.add_node_type(layout, "CompositorNodeSwitch")
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(
self.node_operator(layout, "CompositorNodeSplit")
self.node_operator(layout, "CompositorNodeSwitch")
self.node_operator(layout, "GeometryNodeMenuSwitch")
self.node_operator(
layout, "CompositorNodeSwitchView",
label="Switch Stereo View")
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):
bl_idname = "NODE_MT_category_compositor_vector"
class NODE_MT_compositor_node_vector_base(node_add_menu.NodeMenu):
bl_label = "Vector"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ")
self.node_operator(layout, "ShaderNodeCombineXYZ")
self.node_operator(layout, "ShaderNodeSeparateXYZ")
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.name = "data_type"
ops.value = "'VECTOR'"
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate")
self.node_operator(layout, "ShaderNodeRadialTiling")
self.node_operator(layout, "ShaderNodeVectorCurve")
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
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):
bl_idname = "NODE_MT_compositor_node_add_all"
class NODE_MT_compositor_node_all_base(node_add_menu.NodeMenu):
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):
layout = self.layout
layout.menu("NODE_MT_category_compositor_input")
layout.menu("NODE_MT_category_compositor_output")
self.draw_menu(layout, "Input")
self.draw_menu(layout, "Output")
layout.separator()
layout.menu("NODE_MT_category_compositor_color")
layout.menu("NODE_MT_category_compositor_filter")
self.draw_menu(layout, "Color")
self.draw_menu(layout, "Filter")
layout.separator()
layout.menu("NODE_MT_category_compositor_keying")
layout.menu("NODE_MT_category_compositor_mask")
self.draw_menu(layout, "Keying")
self.draw_menu(layout, "Mask")
layout.separator()
layout.menu("NODE_MT_category_compositor_tracking")
self.draw_menu(layout, "Tracking")
layout.separator()
layout.menu("NODE_MT_category_compositor_texture")
layout.menu("NODE_MT_category_compositor_transform")
layout.menu("NODE_MT_category_compositor_utilities")
layout.menu("NODE_MT_category_compositor_vector")
self.draw_menu(layout, "Texture")
self.draw_menu(layout, "Transform")
self.draw_menu(layout, "Utilities")
self.draw_menu(layout, "Vector")
layout.separator()
layout.menu("NODE_MT_category_compositor_group")
layout.menu("NODE_MT_category_layout")
self.draw_menu(layout, "Group")
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 = (
NODE_MT_compositor_node_add_all,
NODE_MT_category_compositor_input,
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,
*add_menus,
*swap_menus,
)
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):
bl_idname = "NODE_MT_geometry_node_GEO_ATTRIBUTE"
class NODE_MT_gn_attribute_base(node_add_menu.NodeMenu):
bl_label = "Attribute"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeAttributeStatistic")
node_add_menu.add_node_type(layout, "GeometryNodeAttributeDomainSize")
self.node_operator(layout, "GeometryNodeAttributeStatistic")
self.node_operator(layout, "GeometryNodeAttributeDomainSize")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeBlurAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeCaptureAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeRemoveAttribute")
node_add_menu.add_node_type(layout, "GeometryNodeStoreNamedAttribute", search_weight=1.0)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "GeometryNodeBlurAttribute")
self.node_operator(layout, "GeometryNodeCaptureAttribute")
self.node_operator(layout, "GeometryNodeRemoveAttribute")
self.node_operator(layout, "GeometryNodeStoreNamedAttribute", search_weight=1.0)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_COLOR(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_COLOR"
class NODE_MT_gn_utilities_color_base(node_add_menu.NodeMenu):
bl_label = "Color"
menu_path = "Utilities/Color"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "ShaderNodeRGBCurve")
self.node_operator(layout, "ShaderNodeBlackbody")
self.node_operator(layout, "ShaderNodeGamma")
self.node_operator(layout, "ShaderNodeValToRGB")
self.node_operator(layout, "ShaderNodeRGBCurve")
layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeCombineColor")
node_add_menu.add_color_mix_node(context, layout)
node_add_menu.add_node_type(layout, "FunctionNodeSeparateColor")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Color")
self.node_operator(layout, "FunctionNodeCombineColor")
self.color_mix_node(context, layout)
self.node_operator(layout, "FunctionNodeSeparateColor")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE"
class NODE_MT_gn_curve_base(node_add_menu.NodeMenu):
bl_label = "Curve"
def draw(self, _context):
layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_CURVE_READ")
layout.menu("NODE_MT_geometry_node_GEO_CURVE_SAMPLE")
layout.menu("NODE_MT_geometry_node_GEO_CURVE_WRITE")
self.draw_menu(layout, path="Curve/Read")
self.draw_menu(layout, path="Curve/Sample")
self.draw_menu(layout, path="Curve/Write")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_CURVE_OPERATIONS")
layout.menu("NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE")
layout.menu("NODE_MT_geometry_node_curve_topology")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_menu(layout, path="Curve/Operations")
self.draw_menu(layout, path="Curve/Primitives")
self.draw_menu(layout, path="Curve/Topology")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_CURVE_READ(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_READ"
class NODE_MT_gn_curve_read_base(node_add_menu.NodeMenu):
bl_label = "Read"
menu_path = "Curve/Read"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveHandlePositions")
node_add_menu.add_node_type(layout, "GeometryNodeCurveLength")
node_add_menu.add_node_type(layout, "GeometryNodeInputTangent")
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveTilt")
node_add_menu.add_node_type(layout, "GeometryNodeCurveEndpointSelection")
node_add_menu.add_node_type(layout, "GeometryNodeCurveHandleTypeSelection")
node_add_menu.add_node_type(layout, "GeometryNodeInputSplineCyclic")
node_add_menu.add_node_type(layout, "GeometryNodeSplineLength")
node_add_menu.add_node_type(layout, "GeometryNodeSplineParameter")
node_add_menu.add_node_type(layout, "GeometryNodeInputSplineResolution")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Read")
self.node_operator(layout, "GeometryNodeInputCurveHandlePositions")
self.node_operator(layout, "GeometryNodeCurveLength")
self.node_operator(layout, "GeometryNodeInputTangent")
self.node_operator(layout, "GeometryNodeInputCurveTilt")
self.node_operator(layout, "GeometryNodeCurveEndpointSelection")
self.node_operator(layout, "GeometryNodeCurveHandleTypeSelection")
self.node_operator(layout, "GeometryNodeInputSplineCyclic")
self.node_operator(layout, "GeometryNodeSplineLength")
self.node_operator(layout, "GeometryNodeSplineParameter")
self.node_operator(layout, "GeometryNodeInputSplineResolution")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_SAMPLE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_SAMPLE"
class NODE_MT_gn_curve_sample_base(node_add_menu.NodeMenu):
bl_label = "Sample"
menu_path = "Curve/Sample"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Sample")
self.node_operator(layout, "GeometryNodeSampleCurve")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_WRITE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_WRITE"
class NODE_MT_gn_curve_write_base(node_add_menu.NodeMenu):
bl_label = "Write"
menu_path = "Curve/Write"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveNormal")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveRadius")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveTilt")
node_add_menu.add_node_type(layout, "GeometryNodeSetCurveHandlePositions")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSetHandles")
node_add_menu.add_node_type(layout, "GeometryNodeSetSplineCyclic")
node_add_menu.add_node_type(layout, "GeometryNodeSetSplineResolution")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSplineType")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Write")
self.node_operator(layout, "GeometryNodeSetCurveNormal")
self.node_operator(layout, "GeometryNodeSetCurveRadius")
self.node_operator(layout, "GeometryNodeSetCurveTilt")
self.node_operator(layout, "GeometryNodeSetCurveHandlePositions")
self.node_operator(layout, "GeometryNodeCurveSetHandles")
self.node_operator(layout, "GeometryNodeSetSplineCyclic")
self.node_operator(layout, "GeometryNodeSetSplineResolution")
self.node_operator(layout, "GeometryNodeCurveSplineType")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_CURVE_OPERATIONS(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_CURVE_OPERATIONS"
class NODE_MT_gn_curve_operations_base(node_add_menu.NodeMenu):
bl_label = "Operations"
menu_path = "Curve/Operations"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveToMesh")
node_add_menu.add_node_type(layout, "GeometryNodeCurveToPoints")
node_add_menu.add_node_type(layout, "GeometryNodeCurvesToGreasePencil")
node_add_menu.add_node_type(layout, "GeometryNodeDeformCurvesOnSurface")
node_add_menu.add_node_type(layout, "GeometryNodeFillCurve")
node_add_menu.add_node_type(layout, "GeometryNodeFilletCurve")
node_add_menu.add_node_type(layout, "GeometryNodeInterpolateCurves")
node_add_menu.add_node_type(layout, "GeometryNodeResampleCurve")
node_add_menu.add_node_type(layout, "GeometryNodeReverseCurve")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivideCurve")
node_add_menu.add_node_type(layout, "GeometryNodeTrimCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Operations")
self.node_operator(layout, "GeometryNodeCurveToMesh")
self.node_operator(layout, "GeometryNodeCurveToPoints")
self.node_operator(layout, "GeometryNodeCurvesToGreasePencil")
self.node_operator(layout, "GeometryNodeDeformCurvesOnSurface")
self.node_operator(layout, "GeometryNodeFillCurve")
self.node_operator(layout, "GeometryNodeFilletCurve")
self.node_operator(layout, "GeometryNodeInterpolateCurves")
self.node_operator(layout, "GeometryNodeResampleCurve")
self.node_operator(layout, "GeometryNodeReverseCurve")
self.node_operator(layout, "GeometryNodeSubdivideCurve")
self.node_operator(layout, "GeometryNodeTrimCurve")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE"
class NODE_MT_gn_curve_primitives_base(node_add_menu.NodeMenu):
bl_label = "Primitives"
menu_path = "Curve/Primitives"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveArc")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveBezierSegment")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveCircle")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveLine")
node_add_menu.add_node_type(layout, "GeometryNodeCurveSpiral")
node_add_menu.add_node_type(layout, "GeometryNodeCurveQuadraticBezier")
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveQuadrilateral")
node_add_menu.add_node_type(layout, "GeometryNodeCurveStar")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Primitives")
self.node_operator(layout, "GeometryNodeCurveArc")
self.node_operator(layout, "GeometryNodeCurvePrimitiveBezierSegment")
self.node_operator(layout, "GeometryNodeCurvePrimitiveCircle")
self.node_operator(layout, "GeometryNodeCurvePrimitiveLine")
self.node_operator(layout, "GeometryNodeCurveSpiral")
self.node_operator(layout, "GeometryNodeCurveQuadraticBezier")
self.node_operator(layout, "GeometryNodeCurvePrimitiveQuadrilateral")
self.node_operator(layout, "GeometryNodeCurveStar")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_curve_topology(Menu):
bl_idname = "NODE_MT_geometry_node_curve_topology"
class NODE_MT_gn_curve_topology_base(node_add_menu.NodeMenu):
bl_label = "Topology"
menu_path = "Curve/Topology"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurveOfPoint")
node_add_menu.add_node_type(layout, "GeometryNodeOffsetPointInCurve")
node_add_menu.add_node_type(layout, "GeometryNodePointsOfCurve")
node_add_menu.draw_assets_for_catalog(layout, "Curve/Topology")
self.node_operator(layout, "GeometryNodeCurveOfPoint")
self.node_operator(layout, "GeometryNodeOffsetPointInCurve")
self.node_operator(layout, "GeometryNodePointsOfCurve")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_read(Menu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_read"
class NODE_MT_gn_grease_pencil_read_base(node_add_menu.NodeMenu):
bl_label = "Read"
menu_path = "Grease Pencil/Read"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputNamedLayerSelection")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Read")
self.node_operator(layout, "GeometryNodeInputNamedLayerSelection")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_write(Menu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_write"
class NODE_MT_gn_grease_pencil_write_base(node_add_menu.NodeMenu):
bl_label = "Write"
menu_path = "Grease Pencil/Write"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilColor")
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilDepth")
node_add_menu.add_node_type(layout, "GeometryNodeSetGreasePencilSoftness")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Write")
self.node_operator(layout, "GeometryNodeSetGreasePencilColor")
self.node_operator(layout, "GeometryNodeSetGreasePencilDepth")
self.node_operator(layout, "GeometryNodeSetGreasePencilSoftness")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil_operations(Menu):
bl_idname = "NODE_MT_geometry_node_grease_pencil_operations"
class NODE_MT_gn_grease_pencil_operations_base(node_add_menu.NodeMenu):
bl_label = "Operations"
menu_path = "Grease Pencil/Operations"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGreasePencilToCurves")
node_add_menu.add_node_type(layout, "GeometryNodeMergeLayers")
node_add_menu.draw_assets_for_catalog(layout, "Grease Pencil/Operations")
self.node_operator(layout, "GeometryNodeGreasePencilToCurves")
self.node_operator(layout, "GeometryNodeMergeLayers")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_grease_pencil(Menu):
bl_idname = "NODE_MT_geometry_node_grease_pencil"
class NODE_MT_gn_grease_pencil_base(node_add_menu.NodeMenu):
bl_label = "Grease Pencil"
def draw(self, _context):
layout = self.layout
layout.menu("NODE_MT_geometry_node_grease_pencil_read")
layout.menu("NODE_MT_geometry_node_grease_pencil_write")
self.draw_menu(layout, path="Grease Pencil/Read")
self.draw_menu(layout, path="Grease Pencil/Write")
layout.separator()
layout.menu("NODE_MT_geometry_node_grease_pencil_operations")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_menu(layout, path="Grease Pencil/Operations")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_GEOMETRY(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY"
class NODE_MT_gn_geometry_base(node_add_menu.NodeMenu):
bl_label = "Geometry"
def draw(self, _context):
layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_READ")
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE")
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_WRITE")
self.draw_menu(layout, path="Geometry/Read")
self.draw_menu(layout, path="Geometry/Sample")
self.draw_menu(layout, path="Geometry/Write")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS")
self.draw_menu(layout, path="Geometry/Operations")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeGeometryToInstance")
node_add_menu.add_node_type(layout, "GeometryNodeJoinGeometry", search_weight=1.0)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "GeometryNodeGeometryToInstance")
self.node_operator(layout, "GeometryNodeJoinGeometry", search_weight=1.0)
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_GEOMETRY_READ(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_READ"
class NODE_MT_gn_geometry_read_base(node_add_menu.NodeMenu):
bl_label = "Read"
menu_path = "Geometry/Read"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputID")
node_add_menu.add_node_type(layout, "GeometryNodeInputIndex")
node_add_menu.add_node_type(layout, "GeometryNodeInputNamedAttribute", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeInputNormal")
node_add_menu.add_node_type(layout, "GeometryNodeInputPosition", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeInputRadius")
self.node_operator(layout, "GeometryNodeInputID")
self.node_operator(layout, "GeometryNodeInputIndex")
self.node_operator(layout, "GeometryNodeInputNamedAttribute", search_weight=1.0)
self.node_operator(layout, "GeometryNodeInputNormal")
self.node_operator(layout, "GeometryNodeInputPosition", search_weight=1.0)
self.node_operator(layout, "GeometryNodeInputRadius")
if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSelection")
node_add_menu.add_node_type(layout, "GeometryNodeToolActiveElement")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Read")
self.node_operator(layout, "GeometryNodeToolSelection")
self.node_operator(layout, "GeometryNodeToolActiveElement")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_WRITE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_WRITE"
class NODE_MT_gn_geometry_write_base(node_add_menu.NodeMenu):
bl_label = "Write"
menu_path = "Geometry/Write"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSetGeometryName")
node_add_menu.add_node_type(layout, "GeometryNodeSetID")
node_add_menu.add_node_type(layout, "GeometryNodeSetPosition", search_weight=1.0)
self.node_operator(layout, "GeometryNodeSetGeometryName")
self.node_operator(layout, "GeometryNodeSetID")
self.node_operator(layout, "GeometryNodeSetPosition", search_weight=1.0)
if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSetSelection")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Write")
self.node_operator(layout, "GeometryNodeToolSetSelection")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS"
class NODE_MT_gn_geometry_operations_base(node_add_menu.NodeMenu):
bl_label = "Operations"
menu_path = "Geometry/Operations"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeBake")
node_add_menu.add_node_type(layout, "GeometryNodeBoundBox")
node_add_menu.add_node_type(layout, "GeometryNodeConvexHull")
node_add_menu.add_node_type(layout, "GeometryNodeDeleteGeometry")
node_add_menu.add_node_type(layout, "GeometryNodeDuplicateElements")
node_add_menu.add_node_type(layout, "GeometryNodeMergeByDistance")
node_add_menu.add_node_type(layout, "GeometryNodeSortElements")
node_add_menu.add_node_type(layout, "GeometryNodeTransform", search_weight=1.0)
self.node_operator(layout, "GeometryNodeBake")
self.node_operator(layout, "GeometryNodeBoundBox")
self.node_operator(layout, "GeometryNodeConvexHull")
self.node_operator(layout, "GeometryNodeDeleteGeometry")
self.node_operator(layout, "GeometryNodeDuplicateElements")
self.node_operator(layout, "GeometryNodeMergeByDistance")
self.node_operator(layout, "GeometryNodeSortElements")
self.node_operator(layout, "GeometryNodeTransform", search_weight=1.0)
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSeparateComponents")
node_add_menu.add_node_type(layout, "GeometryNodeSeparateGeometry")
node_add_menu.add_node_type(layout, "GeometryNodeSplitToInstances")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Operations")
self.node_operator(layout, "GeometryNodeSeparateComponents")
self.node_operator(layout, "GeometryNodeSeparateGeometry")
self.node_operator(layout, "GeometryNodeSplitToInstances")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE"
class NODE_MT_gn_geometry_sample_base(node_add_menu.NodeMenu):
bl_label = "Sample"
menu_path = "Geometry/Sample"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeProximity")
node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest")
node_add_menu.add_node_type(layout, "GeometryNodeRaycast")
node_add_menu.add_node_type(layout, "GeometryNodeSampleIndex")
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearest")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Sample")
self.node_operator(layout, "GeometryNodeProximity")
self.node_operator(layout, "GeometryNodeIndexOfNearest")
self.node_operator(layout, "GeometryNodeRaycast")
self.node_operator(layout, "GeometryNodeSampleIndex")
self.node_operator(layout, "GeometryNodeSampleNearest")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT"
class NODE_MT_gn_input_base(node_add_menu.NodeMenu):
bl_label = "Input"
def draw(self, context):
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':
layout.menu("NODE_MT_geometry_node_GEO_INPUT_GIZMO")
layout.menu("NODE_MT_geometry_node_GEO_INPUT_GROUP")
layout.menu("NODE_MT_category_import")
layout.menu("NODE_MT_geometry_node_GEO_INPUT_SCENE")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_menu(layout, path="Input/Gizmo")
self.draw_menu(layout, path="Input/Group")
self.draw_menu(layout, path="Input/Import")
self.draw_menu(layout, path="Input/Scene")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_INPUT_CONSTANT(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_CONSTANT"
class NODE_MT_gn_input_constant_base(node_add_menu.NodeMenu):
bl_label = "Constant"
bl_translation_context = i18n_contexts.id_nodetree
menu_path = "Input/Constant"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeInputBool")
node_add_menu.add_node_type(layout, "GeometryNodeInputCollection")
node_add_menu.add_node_type(layout, "FunctionNodeInputColor")
node_add_menu.add_node_type(layout, "GeometryNodeInputImage")
node_add_menu.add_node_type(layout, "FunctionNodeInputInt")
node_add_menu.add_node_type(layout, "GeometryNodeInputMaterial")
node_add_menu.add_node_type(layout, "GeometryNodeInputObject")
node_add_menu.add_node_type(layout, "FunctionNodeInputRotation")
node_add_menu.add_node_type(layout, "FunctionNodeInputString")
node_add_menu.add_node_type(layout, "ShaderNodeValue")
node_add_menu.add_node_type(layout, "FunctionNodeInputVector")
node_add_menu.draw_assets_for_catalog(layout, "Input/Constant")
self.node_operator(layout, "FunctionNodeInputBool")
self.node_operator(layout, "GeometryNodeInputCollection")
self.node_operator(layout, "FunctionNodeInputColor")
self.node_operator(layout, "GeometryNodeInputImage")
self.node_operator(layout, "FunctionNodeInputInt")
self.node_operator(layout, "GeometryNodeInputMaterial")
self.node_operator(layout, "GeometryNodeInputObject")
self.node_operator(layout, "FunctionNodeInputRotation")
self.node_operator(layout, "FunctionNodeInputString")
self.node_operator(layout, "ShaderNodeValue")
self.node_operator(layout, "FunctionNodeInputVector")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT_GROUP(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_GROUP"
class NODE_MT_gn_input_group_base(node_add_menu.NodeMenu):
bl_label = "Group"
menu_path = "Input/Group"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupInput")
node_add_menu.draw_assets_for_catalog(layout, "Input/Group")
self.node_operator(layout, "NodeGroupInput")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_SCENE"
class NODE_MT_gn_input_scene_base(node_add_menu.NodeMenu):
bl_label = "Scene"
menu_path = "Input/Scene"
def draw(self, context):
layout = self.layout
if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeTool3DCursor")
node_add_menu.add_node_type(layout, "GeometryNodeInputActiveCamera")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "GeometryNodeTool3DCursor")
self.node_operator(layout, "GeometryNodeInputActiveCamera")
self.node_operator_with_outputs(
context,
layout,
"GeometryNodeCameraInfo",
@@ -355,655 +371,752 @@ class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu):
"Orthographic Scale",
],
)
node_add_menu.add_node_type(layout, "GeometryNodeCollectionInfo")
node_add_menu.add_node_type(layout, "GeometryNodeImageInfo")
node_add_menu.add_node_type(layout, "GeometryNodeIsViewport")
self.node_operator(layout, "GeometryNodeCollectionInfo")
self.node_operator(layout, "GeometryNodeImageInfo")
self.node_operator(layout, "GeometryNodeIsViewport")
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",
["Mouse X", "Mouse Y", "Region Width", "Region Height"],
)
node_add_menu.add_node_type(layout, "GeometryNodeObjectInfo")
node_add_menu.add_node_type_with_outputs(context, layout, "GeometryNodeInputSceneTime", ["Frame", "Seconds"])
node_add_menu.add_node_type(layout, "GeometryNodeSelfObject")
self.node_operator(layout, "GeometryNodeObjectInfo")
self.node_operator_with_outputs(context, layout, "GeometryNodeInputSceneTime", ["Frame", "Seconds"])
self.node_operator(layout, "GeometryNodeSelfObject")
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",
["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):
bl_idname = "NODE_MT_geometry_node_GEO_INPUT_GIZMO"
class NODE_MT_gn_input_gizmo_base(node_add_menu.NodeMenu):
bl_label = "Gizmo"
menu_path = "Input/Gizmo"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGizmoDial")
node_add_menu.add_node_type(layout, "GeometryNodeGizmoLinear")
node_add_menu.add_node_type(layout, "GeometryNodeGizmoTransform")
node_add_menu.draw_assets_for_catalog(layout, "Input/Gizmo")
self.node_operator(layout, "GeometryNodeGizmoDial")
self.node_operator(layout, "GeometryNodeGizmoLinear")
self.node_operator(layout, "GeometryNodeGizmoTransform")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_INSTANCE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_INSTANCE"
class NODE_MT_gn_instance_base(node_add_menu.NodeMenu):
bl_label = "Instances"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInstanceOnPoints", search_weight=2.0)
node_add_menu.add_node_type(layout, "GeometryNodeInstancesToPoints")
self.node_operator(layout, "GeometryNodeInstanceOnPoints", search_weight=2.0)
self.node_operator(layout, "GeometryNodeInstancesToPoints")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeRealizeInstances", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeRotateInstances")
node_add_menu.add_node_type(layout, "GeometryNodeScaleInstances")
node_add_menu.add_node_type(layout, "GeometryNodeTranslateInstances")
node_add_menu.add_node_type(layout, "GeometryNodeSetInstanceTransform")
self.node_operator(layout, "GeometryNodeRealizeInstances", search_weight=1.0)
self.node_operator(layout, "GeometryNodeRotateInstances")
self.node_operator(layout, "GeometryNodeScaleInstances")
self.node_operator(layout, "GeometryNodeTranslateInstances")
self.node_operator(layout, "GeometryNodeSetInstanceTransform")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceBounds")
node_add_menu.add_node_type(layout, "GeometryNodeInstanceTransform")
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceRotation")
node_add_menu.add_node_type(layout, "GeometryNodeInputInstanceScale")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "GeometryNodeInputInstanceBounds")
self.node_operator(layout, "GeometryNodeInstanceTransform")
self.node_operator(layout, "GeometryNodeInputInstanceRotation")
self.node_operator(layout, "GeometryNodeInputInstanceScale")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MATERIAL(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MATERIAL"
class NODE_MT_gn_material_base(node_add_menu.NodeMenu):
bl_label = "Material"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeReplaceMaterial")
self.node_operator(layout, "GeometryNodeReplaceMaterial")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeInputMaterialIndex")
node_add_menu.add_node_type(layout, "GeometryNodeMaterialSelection")
self.node_operator(layout, "GeometryNodeInputMaterialIndex")
self.node_operator(layout, "GeometryNodeMaterialSelection")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSetMaterial", search_weight=1.0)
node_add_menu.add_node_type(layout, "GeometryNodeSetMaterialIndex")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "GeometryNodeSetMaterial", search_weight=1.0)
self.node_operator(layout, "GeometryNodeSetMaterialIndex")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MESH(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH"
class NODE_MT_gn_mesh_base(node_add_menu.NodeMenu):
bl_label = "Mesh"
def draw(self, _context):
layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_MESH_READ")
layout.menu("NODE_MT_geometry_node_GEO_MESH_SAMPLE")
layout.menu("NODE_MT_geometry_node_GEO_MESH_WRITE")
self.draw_menu(layout, path="Mesh/Read")
self.draw_menu(layout, path="Mesh/Sample")
self.draw_menu(layout, path="Mesh/Write")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_MESH_OPERATIONS")
layout.menu("NODE_MT_category_PRIMITIVES_MESH")
layout.menu("NODE_MT_geometry_node_mesh_topology")
layout.menu("NODE_MT_category_GEO_UV")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_menu(layout, path="Mesh/Operations")
self.draw_menu(layout, path="Mesh/Primitives")
self.draw_menu(layout, path="Mesh/Topology")
self.draw_menu(layout, path="Mesh/UV")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_MESH_READ(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_READ"
class NODE_MT_gn_mesh_read_base(node_add_menu.NodeMenu):
bl_label = "Read"
menu_path = "Mesh/Read"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeAngle")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeNeighbors")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeVertices")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesToFaceGroups")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceArea")
node_add_menu.add_node_type(layout, "GeometryNodeMeshFaceSetBoundaries")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceNeighbors")
self.node_operator(layout, "GeometryNodeInputMeshEdgeAngle")
self.node_operator(layout, "GeometryNodeInputMeshEdgeNeighbors")
self.node_operator(layout, "GeometryNodeInputMeshEdgeVertices")
self.node_operator(layout, "GeometryNodeEdgesToFaceGroups")
self.node_operator(layout, "GeometryNodeInputMeshFaceArea")
self.node_operator(layout, "GeometryNodeMeshFaceSetBoundaries")
self.node_operator(layout, "GeometryNodeInputMeshFaceNeighbors")
if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolFaceSet")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceIsPlanar")
node_add_menu.add_node_type(layout, "GeometryNodeInputShadeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputEdgeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshIsland")
node_add_menu.add_node_type(layout, "GeometryNodeInputShortestEdgePaths")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshVertexNeighbors")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Read")
self.node_operator(layout, "GeometryNodeToolFaceSet")
self.node_operator(layout, "GeometryNodeInputMeshFaceIsPlanar")
self.node_operator(layout, "GeometryNodeInputShadeSmooth")
self.node_operator(layout, "GeometryNodeInputEdgeSmooth")
self.node_operator(layout, "GeometryNodeInputMeshIsland")
self.node_operator(layout, "GeometryNodeInputShortestEdgePaths")
self.node_operator(layout, "GeometryNodeInputMeshVertexNeighbors")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_SAMPLE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_SAMPLE"
class NODE_MT_gn_mesh_sample_base(node_add_menu.NodeMenu):
bl_label = "Sample"
menu_path = "Mesh/Sample"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearestSurface")
node_add_menu.add_node_type(layout, "GeometryNodeSampleUVSurface")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Sample")
self.node_operator(layout, "GeometryNodeSampleNearestSurface")
self.node_operator(layout, "GeometryNodeSampleUVSurface")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_WRITE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_WRITE"
class NODE_MT_gn_mesh_write_base(node_add_menu.NodeMenu):
bl_label = "Write"
menu_path = "Mesh/Write"
def draw(self, context):
layout = self.layout
if context.space_data.node_tree_sub_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSetFaceSet")
node_add_menu.add_node_type(layout, "GeometryNodeSetMeshNormal")
node_add_menu.add_node_type(layout, "GeometryNodeSetShadeSmooth")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Write")
self.node_operator(layout, "GeometryNodeToolSetFaceSet")
self.node_operator(layout, "GeometryNodeSetMeshNormal")
self.node_operator(layout, "GeometryNodeSetShadeSmooth")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_MESH_OPERATIONS(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_MESH_OPERATIONS"
class NODE_MT_gn_mesh_operations_base(node_add_menu.NodeMenu):
bl_label = "Operations"
menu_path = "Mesh/Operations"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeDualMesh")
node_add_menu.add_node_type(layout, "GeometryNodeEdgePathsToCurves")
node_add_menu.add_node_type(layout, "GeometryNodeEdgePathsToSelection")
node_add_menu.add_node_type(layout, "GeometryNodeExtrudeMesh")
node_add_menu.add_node_type(layout, "GeometryNodeFlipFaces")
node_add_menu.add_node_type(layout, "GeometryNodeMeshBoolean")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToCurve")
self.node_operator(layout, "GeometryNodeDualMesh")
self.node_operator(layout, "GeometryNodeEdgePathsToCurves")
self.node_operator(layout, "GeometryNodeEdgePathsToSelection")
self.node_operator(layout, "GeometryNodeExtrudeMesh")
self.node_operator(layout, "GeometryNodeFlipFaces")
self.node_operator(layout, "GeometryNodeMeshBoolean")
self.node_operator(layout, "GeometryNodeMeshToCurve")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeMeshToDensityGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToPoints")
self.node_operator(layout, "GeometryNodeMeshToDensityGrid")
self.node_operator(layout, "GeometryNodeMeshToPoints")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeMeshToSDFGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshToVolume")
node_add_menu.add_node_type(layout, "GeometryNodeScaleElements")
node_add_menu.add_node_type(layout, "GeometryNodeSplitEdges")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivideMesh")
node_add_menu.add_node_type(layout, "GeometryNodeSubdivisionSurface")
node_add_menu.add_node_type(layout, "GeometryNodeTriangulate")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Operations")
self.node_operator(layout, "GeometryNodeMeshToSDFGrid")
self.node_operator(layout, "GeometryNodeMeshToVolume")
self.node_operator(layout, "GeometryNodeScaleElements")
self.node_operator(layout, "GeometryNodeSplitEdges")
self.node_operator(layout, "GeometryNodeSubdivideMesh")
self.node_operator(layout, "GeometryNodeSubdivisionSurface")
self.node_operator(layout, "GeometryNodeTriangulate")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_PRIMITIVES_MESH(Menu):
bl_idname = "NODE_MT_category_PRIMITIVES_MESH"
class NODE_MT_gn_mesh_primitives_base(node_add_menu.NodeMenu):
bl_label = "Primitives"
menu_path = "Mesh/Primitives"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeMeshCone")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCube")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCylinder")
node_add_menu.add_node_type(layout, "GeometryNodeMeshGrid")
node_add_menu.add_node_type(layout, "GeometryNodeMeshIcoSphere")
node_add_menu.add_node_type(layout, "GeometryNodeMeshCircle")
node_add_menu.add_node_type(layout, "GeometryNodeMeshLine")
node_add_menu.add_node_type(layout, "GeometryNodeMeshUVSphere")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Primitives")
self.node_operator(layout, "GeometryNodeMeshCone")
self.node_operator(layout, "GeometryNodeMeshCube")
self.node_operator(layout, "GeometryNodeMeshCylinder")
self.node_operator(layout, "GeometryNodeMeshGrid")
self.node_operator(layout, "GeometryNodeMeshIcoSphere")
self.node_operator(layout, "GeometryNodeMeshCircle")
self.node_operator(layout, "GeometryNodeMeshLine")
self.node_operator(layout, "GeometryNodeMeshUVSphere")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_import(Menu):
bl_idname = "NODE_MT_category_import"
class NODE_MT_gn_input_import_base(node_add_menu.NodeMenu):
bl_label = "Import"
menu_path = "Input/Import"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeImportCSV", label="CSV (.csv)")
node_add_menu.add_node_type(layout, "GeometryNodeImportOBJ", label="Wavefront (.obj)")
node_add_menu.add_node_type(layout, "GeometryNodeImportPLY", label="Stanford PLY (.ply)")
node_add_menu.add_node_type(layout, "GeometryNodeImportSTL", label="STL (.stl)")
node_add_menu.add_node_type(layout, "GeometryNodeImportText", label="Text (.txt)")
node_add_menu.add_node_type(layout, "GeometryNodeImportVDB", label="OpenVDB (.vdb)")
node_add_menu.draw_assets_for_catalog(layout, "Input/Import")
self.node_operator(layout, "GeometryNodeImportCSV", label="CSV (.csv)")
self.node_operator(layout, "GeometryNodeImportOBJ", label="Wavefront (.obj)")
self.node_operator(layout, "GeometryNodeImportPLY", label="Stanford PLY (.ply)")
self.node_operator(layout, "GeometryNodeImportSTL", label="STL (.stl)")
self.node_operator(layout, "GeometryNodeImportText", label="Text (.txt)")
self.node_operator(layout, "GeometryNodeImportVDB", label="OpenVDB (.vdb)")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_mesh_topology(Menu):
bl_idname = "NODE_MT_geometry_node_mesh_topology"
class NODE_MT_gn_mesh_topology_base(node_add_menu.NodeMenu):
bl_label = "Topology"
menu_path = "Mesh/Topology"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfEdge")
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfFace")
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfVertex")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfCorner")
node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfVertex")
node_add_menu.add_node_type(layout, "GeometryNodeFaceOfCorner")
node_add_menu.add_node_type(layout, "GeometryNodeOffsetCornerInFace")
node_add_menu.add_node_type(layout, "GeometryNodeVertexOfCorner")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/Topology")
self.node_operator(layout, "GeometryNodeCornersOfEdge")
self.node_operator(layout, "GeometryNodeCornersOfFace")
self.node_operator(layout, "GeometryNodeCornersOfVertex")
self.node_operator(layout, "GeometryNodeEdgesOfCorner")
self.node_operator(layout, "GeometryNodeEdgesOfVertex")
self.node_operator(layout, "GeometryNodeFaceOfCorner")
self.node_operator(layout, "GeometryNodeOffsetCornerInFace")
self.node_operator(layout, "GeometryNodeVertexOfCorner")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_OUTPUT(Menu):
bl_idname = "NODE_MT_category_GEO_OUTPUT"
class NODE_MT_gn_output_base(node_add_menu.NodeMenu):
bl_label = "Output"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "NodeGroupOutput")
node_add_menu.add_node_type(layout, "GeometryNodeViewer")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "GeometryNodeWarning", "warning_type")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "NodeGroupOutput")
self.node_operator(layout, "GeometryNodeViewer")
self.node_operator_with_searchable_enum(context, layout, "GeometryNodeWarning", "warning_type")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_POINT(Menu):
bl_idname = "NODE_MT_category_GEO_POINT"
class NODE_MT_gn_point_base(node_add_menu.NodeMenu):
bl_label = "Point"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInVolume")
self.node_operator(layout, "GeometryNodeDistributePointsInVolume")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInGrid")
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsOnFaces")
self.node_operator(layout, "GeometryNodeDistributePointsInGrid")
self.node_operator(layout, "GeometryNodeDistributePointsOnFaces")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodePoints")
node_add_menu.add_node_type(layout, "GeometryNodePointsToCurves")
self.node_operator(layout, "GeometryNodePoints")
self.node_operator(layout, "GeometryNodePointsToCurves")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodePointsToSDFGrid")
node_add_menu.add_node_type(layout, "GeometryNodePointsToVertices")
node_add_menu.add_node_type(layout, "GeometryNodePointsToVolume")
self.node_operator(layout, "GeometryNodePointsToSDFGrid")
self.node_operator(layout, "GeometryNodePointsToVertices")
self.node_operator(layout, "GeometryNodePointsToVolume")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeSetPointRadius")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "GeometryNodeSetPointRadius")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_simulation(Menu):
bl_idname = "NODE_MT_category_simulation"
class NODE_MT_gn_simulation_base(node_add_menu.NodeMenu):
bl_label = "Simulation"
def draw(self, _context):
layout = self.layout
node_add_menu.add_simulation_zone(layout, label="Simulation")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.simulation_zone(layout, label="Simulation")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_TEXT(Menu):
bl_idname = "NODE_MT_category_GEO_TEXT"
class NODE_MT_gn_utilities_text_base(node_add_menu.NodeMenu):
bl_label = "Text"
menu_path = "Utilities/Text"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeFormatString")
node_add_menu.add_node_type(layout, "GeometryNodeStringJoin")
node_add_menu.add_node_type(layout, "FunctionNodeMatchString")
node_add_menu.add_node_type(layout, "FunctionNodeReplaceString")
node_add_menu.add_node_type(layout, "FunctionNodeSliceString")
self.node_operator(layout, "FunctionNodeFormatString")
self.node_operator(layout, "GeometryNodeStringJoin")
self.node_operator(layout, "FunctionNodeMatchString")
self.node_operator(layout, "FunctionNodeReplaceString")
self.node_operator(layout, "FunctionNodeSliceString")
layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeFindInString")
node_add_menu.add_node_type(layout, "FunctionNodeStringLength")
node_add_menu.add_node_type(layout, "GeometryNodeStringToCurves")
node_add_menu.add_node_type(layout, "FunctionNodeStringToValue")
node_add_menu.add_node_type(layout, "FunctionNodeValueToString")
self.node_operator(layout, "FunctionNodeFindInString")
self.node_operator(layout, "FunctionNodeStringLength")
self.node_operator(layout, "GeometryNodeStringToCurves")
self.node_operator(layout, "FunctionNodeStringToValue")
self.node_operator(layout, "FunctionNodeValueToString")
layout.separator()
node_add_menu.add_node_type(layout, "FunctionNodeInputSpecialCharacters")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Text")
self.node_operator(layout, "FunctionNodeInputSpecialCharacters")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_TEXTURE(Menu):
bl_idname = "NODE_MT_category_GEO_TEXTURE"
class NODE_MT_gn_texture_base(node_add_menu.NodeMenu):
bl_label = "Texture"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "GeometryNodeImageTexture")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.node_operator(layout, "ShaderNodeTexBrick")
self.node_operator(layout, "ShaderNodeTexChecker")
self.node_operator(layout, "ShaderNodeTexGabor")
self.node_operator(layout, "ShaderNodeTexGradient")
self.node_operator(layout, "GeometryNodeImageTexture")
self.node_operator(layout, "ShaderNodeTexMagic")
self.node_operator(layout, "ShaderNodeTexNoise")
self.node_operator(layout, "ShaderNodeTexVoronoi")
self.node_operator(layout, "ShaderNodeTexWave")
self.node_operator(layout, "ShaderNodeTexWhiteNoise")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_UTILITIES(Menu):
bl_idname = "NODE_MT_category_GEO_UTILITIES"
class NODE_MT_gn_utilities_base(node_add_menu.NodeMenu):
bl_label = "Utilities"
def draw(self, context):
layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_COLOR")
layout.menu("NODE_MT_category_GEO_TEXT")
layout.menu("NODE_MT_category_GEO_VECTOR")
self.draw_menu(layout, path="Utilities/Color")
self.draw_menu(layout, path="Utilities/Text")
self.draw_menu(layout, path="Utilities/Vector")
layout.separator()
layout.menu("NODE_MT_category_utilities_bundle")
layout.menu("NODE_MT_category_utilities_closure")
layout.menu("NODE_MT_category_GEO_UTILITIES_FIELD")
self.draw_menu(layout, path="Utilities/Bundle")
self.draw_menu(layout, path="Utilities/Closure")
self.draw_menu(layout, path="Utilities/Field")
self.draw_menu(layout, path="Utilities/Math")
if context.preferences.experimental.use_geometry_nodes_lists:
layout.menu("NODE_MT_category_utilities_list")
layout.menu("NODE_MT_category_GEO_UTILITIES_MATH")
layout.menu("NODE_MT_category_utilities_matrix")
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.menu("NODE_MT_category_GEO_UTILITIES_DEPRECATED")
self.draw_menu(layout, path="Utilities/List")
self.draw_menu(layout, path="Utilities/Matrix")
self.draw_menu(layout, path="Utilities/Rotation")
self.draw_menu(layout, path="Utilities/Deprecated")
layout.separator()
node_add_menu.add_foreach_geometry_element_zone(layout, label="For Each Element")
node_add_menu.add_node_type(layout, "GeometryNodeIndexSwitch")
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.for_each_element_zone(layout, label="For Each Element")
self.node_operator(layout, "GeometryNodeIndexSwitch")
self.node_operator(layout, "GeometryNodeMenuSwitch")
self.node_operator(layout, "FunctionNodeRandomValue")
self.repeat_zone(layout, label="Repeat")
self.node_operator(layout, "GeometryNodeSwitch")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_category_GEO_UTILITIES_DEPRECATED(Menu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_DEPRECATED"
class NODE_MT_gn_utilities_deprecated_base(node_add_menu.NodeMenu):
bl_label = "Deprecated"
menu_path = "Utilities/Deprecated"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeAlignEulerToVector")
node_add_menu.add_node_type(layout, "FunctionNodeRotateEuler")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Deprecated")
self.node_operator(layout, "FunctionNodeAlignEulerToVector")
self.node_operator(layout, "FunctionNodeRotateEuler")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_FIELD(Menu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_FIELD"
class NODE_MT_gn_utilities_field_base(node_add_menu.NodeMenu):
bl_label = "Field"
menu_path = "Utilities/Field"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeAccumulateField")
node_add_menu.add_node_type(layout, "GeometryNodeFieldAtIndex")
node_add_menu.add_node_type(layout, "GeometryNodeFieldOnDomain")
node_add_menu.add_node_type(layout, "GeometryNodeFieldAverage")
node_add_menu.add_node_type(layout, "GeometryNodeFieldMinAndMax")
node_add_menu.add_node_type(layout, "GeometryNodeFieldVariance")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Field")
self.node_operator(layout, "GeometryNodeAccumulateField")
self.node_operator(layout, "GeometryNodeFieldAtIndex")
self.node_operator(layout, "GeometryNodeFieldOnDomain")
self.node_operator(layout, "GeometryNodeFieldAverage")
self.node_operator(layout, "GeometryNodeFieldMinAndMax")
self.node_operator(layout, "GeometryNodeFieldVariance")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_ROTATION(Menu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_ROTATION"
class NODE_MT_gn_utilities_rotation_base(node_add_menu.NodeMenu):
bl_label = "Rotation"
menu_path = "Utilities/Rotation"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeAlignRotationToVector")
node_add_menu.add_node_type(layout, "FunctionNodeAxesToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeAxisAngleToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeEulerToRotation")
node_add_menu.add_node_type(layout, "FunctionNodeInvertRotation")
node_add_menu.add_node_type(layout, "FunctionNodeRotateRotation")
node_add_menu.add_node_type(layout, "FunctionNodeRotateVector")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToAxisAngle")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToEuler")
node_add_menu.add_node_type(layout, "FunctionNodeRotationToQuaternion")
node_add_menu.add_node_type(layout, "FunctionNodeQuaternionToRotation")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Rotation")
self.node_operator(layout, "FunctionNodeAlignRotationToVector")
self.node_operator(layout, "FunctionNodeAxesToRotation")
self.node_operator(layout, "FunctionNodeAxisAngleToRotation")
self.node_operator(layout, "FunctionNodeEulerToRotation")
self.node_operator(layout, "FunctionNodeInvertRotation")
self.node_operator(layout, "FunctionNodeRotateRotation")
self.node_operator(layout, "FunctionNodeRotateVector")
self.node_operator(layout, "FunctionNodeRotationToAxisAngle")
self.node_operator(layout, "FunctionNodeRotationToEuler")
self.node_operator(layout, "FunctionNodeRotationToQuaternion")
self.node_operator(layout, "FunctionNodeQuaternionToRotation")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_matrix(Menu):
bl_idname = "NODE_MT_category_utilities_matrix"
class NODE_MT_gn_utilities_matrix_base(node_add_menu.NodeMenu):
bl_label = "Matrix"
menu_path = "Utilities/Matrix"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "FunctionNodeCombineMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeCombineTransform")
node_add_menu.add_node_type(layout, "FunctionNodeMatrixDeterminant", label="Determinant")
node_add_menu.add_node_type(layout, "FunctionNodeInvertMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeMatrixMultiply")
node_add_menu.add_node_type(layout, "FunctionNodeProjectPoint")
node_add_menu.add_node_type(layout, "FunctionNodeSeparateMatrix")
node_add_menu.add_node_type(layout, "FunctionNodeSeparateTransform")
node_add_menu.add_node_type(layout, "FunctionNodeTransformDirection")
node_add_menu.add_node_type(layout, "FunctionNodeTransformPoint")
node_add_menu.add_node_type(layout, "FunctionNodeTransposeMatrix")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Matrix")
self.node_operator(layout, "FunctionNodeCombineMatrix")
self.node_operator(layout, "FunctionNodeCombineTransform")
self.node_operator(layout, "FunctionNodeMatrixDeterminant", label="Determinant")
self.node_operator(layout, "FunctionNodeInvertMatrix")
self.node_operator(layout, "FunctionNodeMatrixMultiply")
self.node_operator(layout, "FunctionNodeProjectPoint")
self.node_operator(layout, "FunctionNodeSeparateMatrix")
self.node_operator(layout, "FunctionNodeSeparateTransform")
self.node_operator(layout, "FunctionNodeTransformDirection")
self.node_operator(layout, "FunctionNodeTransformPoint")
self.node_operator(layout, "FunctionNodeTransposeMatrix")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_bundle(Menu):
bl_idname = "NODE_MT_category_utilities_bundle"
class NODE_MT_category_utilities_bundle_base(node_add_menu.NodeMenu):
bl_label = "Bundle"
menu_path = "Utilities/Bundle"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "NodeCombineBundle")
node_add_menu.add_node_type(layout, "NodeSeparateBundle")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Bundle")
self.node_operator(layout, "NodeCombineBundle")
self.node_operator(layout, "NodeSeparateBundle")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_closure(Menu):
bl_idname = "NODE_MT_category_utilities_closure"
class NODE_MT_category_utilities_closure_base(node_add_menu.NodeMenu):
bl_label = "Closure"
menu_path = "Utilities/Closure"
def draw(self, context):
layout = self.layout
node_add_menu.add_closure_zone(layout, label="Closure")
node_add_menu.add_node_type(layout, "NodeEvaluateClosure")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Closure")
self.closure_zone(layout, label="Closure")
self.node_operator(layout, "NodeEvaluateClosure")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_utilities_list(Menu):
bl_idname = "NODE_MT_category_utilities_list"
class NODE_MT_gn_utilities_list_base(node_add_menu.NodeMenu):
bl_label = "List"
menu_path = "Utilities/List"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeList")
node_add_menu.add_node_type(layout, "GeometryNodeListGetItem")
node_add_menu.add_node_type(layout, "GeometryNodeListLength")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/List")
self.node_operator(layout, "GeometryNodeList")
self.node_operator(layout, "GeometryNodeListGetItem")
self.node_operator(layout, "GeometryNodeListLength")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UTILITIES_MATH(Menu):
bl_idname = "NODE_MT_category_GEO_UTILITIES_MATH"
class NODE_MT_gn_utilities_math_base(node_add_menu.NodeMenu):
bl_label = "Math"
menu_path = "Utilities/Math"
def draw(self, context):
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,
)
node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeBooleanMath", "operation")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "FunctionNodeIntegerMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "FunctionNodeCompare")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve")
node_add_menu.add_node_type(layout, "FunctionNodeFloatToInt")
node_add_menu.add_node_type(layout, "FunctionNodeHashValue")
node_add_menu.add_node_type(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Math")
self.node_operator_with_searchable_enum(context, layout, "FunctionNodeBooleanMath", "operation")
self.node_operator_with_searchable_enum(context, layout, "FunctionNodeIntegerMath", "operation")
self.node_operator(layout, "ShaderNodeClamp")
self.node_operator(layout, "FunctionNodeCompare")
self.node_operator(layout, "ShaderNodeFloatCurve")
self.node_operator(layout, "FunctionNodeFloatToInt")
self.node_operator(layout, "FunctionNodeHashValue")
self.node_operator(layout, "ShaderNodeMapRange")
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
self.node_operator(layout, "ShaderNodeMix")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_UV(Menu):
bl_idname = "NODE_MT_category_GEO_UV"
class NODE_MT_gn_mesh_uv_base(node_add_menu.NodeMenu):
bl_label = "UV"
menu_path = "Mesh/UV"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeUVPackIslands")
node_add_menu.add_node_type(layout, "GeometryNodeUVUnwrap")
node_add_menu.draw_assets_for_catalog(layout, "Mesh/UV")
self.node_operator(layout, "GeometryNodeUVPackIslands")
self.node_operator(layout, "GeometryNodeUVUnwrap")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_VECTOR(Menu):
bl_idname = "NODE_MT_category_GEO_VECTOR"
class NODE_MT_gn_utilities_vector_base(node_add_menu.NodeMenu):
bl_label = "Vector"
menu_path = "Utilities/Vector"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate")
self.node_operator(layout, "ShaderNodeRadialTiling")
self.node_operator(layout, "ShaderNodeVectorCurve")
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
self.node_operator(layout, "ShaderNodeVectorRotate")
layout.separator()
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ")
props = node_add_menu.add_node_type(layout, "ShaderNodeMix", label="Mix Vector")
self.node_operator(layout, "ShaderNodeCombineXYZ")
props = self.node_operator(layout, "ShaderNodeMix", label="Mix Vector")
ops = props.settings.add()
ops.name = "data_type"
ops.value = "'VECTOR'"
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ")
node_add_menu.draw_assets_for_catalog(layout, "Utilities/Vector")
self.node_operator(layout, "ShaderNodeSeparateXYZ")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_VOLUME(Menu):
bl_idname = "NODE_MT_category_GEO_VOLUME"
class NODE_MT_gn_volume_base(node_add_menu.NodeMenu):
bl_label = "Volume"
bl_translation_context = i18n_contexts.id_id
def draw(self, context):
layout = self.layout
if context.preferences.experimental.use_new_volume_nodes:
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_READ")
layout.menu("NODE_MT_geometry_node_volume_sample")
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_WRITE")
self.draw_menu(layout, path="Volume/Read")
self.draw_menu(layout, path="Volume/Sample")
self.draw_menu(layout, path="Volume/Write")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS")
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)
self.draw_menu(layout, path="Volume/Operations")
self.draw_menu(layout, path="Volume/Primitives")
self.draw_assets_for_catalog(layout, self.bl_label)
class NODE_MT_geometry_node_GEO_VOLUME_READ(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_READ"
class NODE_MT_gn_volume_read_base(node_add_menu.NodeMenu):
bl_label = "Read"
menu_path = "Volume/Read"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeGetNamedGrid")
node_add_menu.add_node_type(layout, "GeometryNodeGridInfo")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Read")
self.node_operator(layout, "GeometryNodeGetNamedGrid")
self.node_operator(layout, "GeometryNodeGridInfo")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_WRITE(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_WRITE"
class NODE_MT_gn_volume_write_base(node_add_menu.NodeMenu):
bl_label = "Write"
menu_path = "Volume/Write"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeStoreNamedGrid")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Write")
self.node_operator(layout, "GeometryNodeStoreNamedGrid")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_volume_sample(Menu):
bl_idname = "NODE_MT_geometry_node_volume_sample"
class NODE_MT_gn_volume_sample_base(node_add_menu.NodeMenu):
bl_label = "Sample"
menu_path = "Volume/Sample"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleGrid")
node_add_menu.add_node_type(layout, "GeometryNodeSampleGridIndex")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Sample")
self.node_operator(layout, "GeometryNodeSampleGrid")
self.node_operator(layout, "GeometryNodeSampleGridIndex")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS"
class NODE_MT_gn_volume_operations_base(node_add_menu.NodeMenu):
bl_label = "Operations"
menu_path = "Volume/Operations"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeVolumeToMesh")
self.node_operator(layout, "GeometryNodeVolumeToMesh")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeGridToMesh")
node_add_menu.add_node_type(layout, "GeometryNodeSDFGridBoolean")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Operations")
self.node_operator(layout, "GeometryNodeGridToMesh")
self.node_operator(layout, "GeometryNodeSDFGridBoolean")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES"
class NODE_MT_gn_volume_primitives_base(node_add_menu.NodeMenu):
bl_label = "Primitives"
menu_path = "Volume/Primitives"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeVolumeCube")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Primitives")
self.node_operator(layout, "GeometryNodeVolumeCube")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_category_GEO_GROUP(Menu):
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"
class NODE_MT_gn_all_base(node_add_menu.NodeMenu):
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):
layout = self.layout
layout.menu("NODE_MT_geometry_node_GEO_ATTRIBUTE")
layout.menu("NODE_MT_geometry_node_GEO_INPUT")
layout.menu("NODE_MT_category_GEO_OUTPUT")
self.draw_menu(layout, "Attribute")
self.draw_menu(layout, "Input")
self.draw_menu(layout, "Output")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY")
self.draw_menu(layout, "Geometry")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_CURVE")
layout.menu("NODE_MT_geometry_node_grease_pencil")
layout.menu("NODE_MT_geometry_node_GEO_INSTANCE")
layout.menu("NODE_MT_geometry_node_GEO_MESH")
layout.menu("NODE_MT_category_GEO_POINT")
layout.menu("NODE_MT_category_GEO_VOLUME")
self.draw_menu(layout, "Curve")
self.draw_menu(layout, "Grease Pencil")
self.draw_menu(layout, "Instances")
self.draw_menu(layout, "Mesh")
self.draw_menu(layout, "Point")
self.draw_menu(layout, "Volume")
layout.separator()
layout.menu("NODE_MT_category_simulation")
self.draw_menu(layout, "Simulation")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_MATERIAL")
layout.menu("NODE_MT_category_GEO_TEXTURE")
layout.menu("NODE_MT_category_GEO_UTILITIES")
self.draw_menu(layout, "Material")
self.draw_menu(layout, "Texture")
self.draw_menu(layout, "Utilities")
layout.separator()
layout.menu("NODE_MT_category_GEO_GROUP")
layout.menu("NODE_MT_category_layout")
node_add_menu.draw_root_assets(layout)
self.draw_menu(layout, "Group")
self.draw_menu(layout, "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 = (
NODE_MT_geometry_node_add_all,
NODE_MT_geometry_node_GEO_ATTRIBUTE,
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,
*add_menus,
*swap_menus,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:

View File

@@ -48,27 +48,26 @@ def object_eevee_shader_nodes_poll(context):
eevee_shader_nodes_poll(context))
class NODE_MT_category_shader_input(Menu):
bl_idname = "NODE_MT_category_shader_input"
class NODE_MT_shader_node_input_base(node_add_menu.NodeMenu):
bl_label = "Input"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeAmbientOcclusion")
node_add_menu.add_node_type(layout, "ShaderNodeAttribute")
node_add_menu.add_node_type(layout, "ShaderNodeBevel")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeAmbientOcclusion")
self.node_operator(layout, "ShaderNodeAttribute")
self.node_operator(layout, "ShaderNodeBevel")
self.node_operator_with_outputs(
context, layout, "ShaderNodeCameraData",
["View Vector", "View Z Depth", "View Distance"],
)
node_add_menu.add_node_type(layout, "ShaderNodeVertexColor")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeVertexColor")
self.node_operator_with_outputs(
context, layout, "ShaderNodeHairInfo",
["Is Strand", "Intercept", "Length", "Thickness", "Tangent Normal", "Random"],
)
node_add_menu.add_node_type(layout, "ShaderNodeFresnel")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeFresnel")
self.node_operator_with_outputs(
context,
layout,
"ShaderNodeNewGeometry",
@@ -84,8 +83,8 @@ class NODE_MT_category_shader_input(Menu):
"Random Per Island",
],
)
node_add_menu.add_node_type(layout, "ShaderNodeLayerWeight")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeLayerWeight")
self.node_operator_with_outputs(
context,
layout,
"ShaderNodeLightPath",
@@ -107,358 +106,374 @@ class NODE_MT_category_shader_input(Menu):
"Portal Depth"
],
)
node_add_menu.add_node_type_with_outputs(
self.node_operator_with_outputs(
context, layout, "ShaderNodeObjectInfo",
["Location", "Color", "Alpha", "Object Index", "Material Index", "Random"],
)
node_add_menu.add_node_type_with_outputs(
self.node_operator_with_outputs(
context, layout, "ShaderNodeParticleInfo",
["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",
["Position", "Radius", "Random"],
)
node_add_menu.add_node_type(layout, "ShaderNodeRGB")
node_add_menu.add_node_type(layout, "ShaderNodeTangent")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeRGB")
self.node_operator(layout, "ShaderNodeTangent")
self.node_operator_with_outputs(
context, layout, "ShaderNodeTexCoord",
["Normal", "UV", "Object", "Camera", "Window", "Reflection"],
)
node_add_menu.add_node_type(layout, "ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll(context))
node_add_menu.add_node_type(layout, "ShaderNodeUVMap")
node_add_menu.add_node_type(layout, "ShaderNodeValue")
node_add_menu.add_node_type_with_outputs(
self.node_operator(layout, "ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll(context))
self.node_operator(layout, "ShaderNodeUVMap")
self.node_operator(layout, "ShaderNodeValue")
self.node_operator_with_outputs(
context, layout, "ShaderNodeVolumeInfo",
["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):
bl_idname = "NODE_MT_category_shader_output"
class NODE_MT_shader_node_output_base(node_add_menu.NodeMenu):
bl_label = "Output"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeOutputAOV",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeOutputLight",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeOutputLineStyle",
poll=line_style_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeOutputMaterial",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeOutputWorld",
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):
bl_idname = "NODE_MT_category_shader_shader"
class NODE_MT_shader_node_shader_base(node_add_menu.NodeMenu):
bl_label = "Shader"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeAddShader",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBackground",
poll=world_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfDiffuse",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeEmission",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfGlass",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfGlossy",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfHair",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeHoldout",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfMetallic",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeMixShader",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfPrincipled",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfHairPrincipled",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeVolumePrincipled"
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfRayPortal",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfRefraction",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfSheen",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeEeveeSpecular",
poll=object_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeSubsurfaceScattering",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfToon",
poll=object_not_eevee_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfTranslucent",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeBsdfTransparent",
poll=object_shader_nodes_poll(context),
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeVolumeAbsorption",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"ShaderNodeVolumeScatter",
)
node_add_menu.add_node_type(
self.node_operator(
layout,
"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):
bl_idname = "NODE_MT_category_shader_color"
class NODE_MT_shader_node_color_base(node_add_menu.NodeMenu):
bl_label = "Color"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBrightContrast")
node_add_menu.add_node_type(layout, "ShaderNodeGamma")
node_add_menu.add_node_type(layout, "ShaderNodeHueSaturation")
node_add_menu.add_node_type(layout, "ShaderNodeInvert")
node_add_menu.add_node_type(layout, "ShaderNodeLightFalloff")
node_add_menu.add_color_mix_node(context, layout)
node_add_menu.add_node_type(layout, "ShaderNodeRGBCurve")
self.node_operator(layout, "ShaderNodeBrightContrast")
self.node_operator(layout, "ShaderNodeGamma")
self.node_operator(layout, "ShaderNodeHueSaturation")
self.node_operator(layout, "ShaderNodeInvert")
self.node_operator(layout, "ShaderNodeLightFalloff")
self.color_mix_node(context, layout)
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):
bl_idname = "NODE_MT_category_shader_converter"
class NODE_MT_shader_node_converter_base(node_add_menu.NodeMenu):
bl_label = "Converter"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBlackbody")
node_add_menu.add_node_type(layout, "ShaderNodeClamp")
node_add_menu.add_node_type(layout, "ShaderNodeValToRGB")
node_add_menu.add_node_type(layout, "ShaderNodeCombineColor")
node_add_menu.add_node_type(layout, "ShaderNodeCombineXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve")
node_add_menu.add_node_type(layout, "ShaderNodeMapRange")
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeMix")
node_add_menu.add_node_type(layout, "ShaderNodeRGBToBW")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateColor")
node_add_menu.add_node_type(layout, "ShaderNodeSeparateXYZ")
node_add_menu.add_node_type(layout, "ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll(context))
node_add_menu.add_node_type_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
node_add_menu.add_node_type(layout, "ShaderNodeWavelength")
self.node_operator(layout, "ShaderNodeBlackbody")
self.node_operator(layout, "ShaderNodeClamp")
self.node_operator(layout, "ShaderNodeValToRGB")
self.node_operator(layout, "ShaderNodeCombineColor")
self.node_operator(layout, "ShaderNodeCombineXYZ")
self.node_operator(layout, "ShaderNodeFloatCurve")
self.node_operator(layout, "ShaderNodeMapRange")
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeMath", "operation")
self.node_operator(layout, "ShaderNodeMix")
self.node_operator(layout, "ShaderNodeRGBToBW")
self.node_operator(layout, "ShaderNodeSeparateColor")
self.node_operator(layout, "ShaderNodeSeparateXYZ")
self.node_operator(layout, "ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll(context))
self.node_operator_with_searchable_enum(context, layout, "ShaderNodeVectorMath", "operation")
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):
bl_idname = "NODE_MT_category_shader_texture"
class NODE_MT_shader_node_texture_base(node_add_menu.NodeMenu):
bl_label = "Texture"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexEnvironment")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "ShaderNodeTexIES")
node_add_menu.add_node_type(layout, "ShaderNodeTexImage")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic")
node_add_menu.add_node_type(layout, "ShaderNodeTexNoise")
node_add_menu.add_node_type(layout, "ShaderNodeTexSky")
node_add_menu.add_node_type(layout, "ShaderNodeTexVoronoi")
node_add_menu.add_node_type(layout, "ShaderNodeTexWave")
node_add_menu.add_node_type(layout, "ShaderNodeTexWhiteNoise")
self.node_operator(layout, "ShaderNodeTexBrick")
self.node_operator(layout, "ShaderNodeTexChecker")
self.node_operator(layout, "ShaderNodeTexEnvironment")
self.node_operator(layout, "ShaderNodeTexGabor")
self.node_operator(layout, "ShaderNodeTexGradient")
self.node_operator(layout, "ShaderNodeTexIES")
self.node_operator(layout, "ShaderNodeTexImage")
self.node_operator(layout, "ShaderNodeTexMagic")
self.node_operator(layout, "ShaderNodeTexNoise")
self.node_operator(layout, "ShaderNodeTexSky")
self.node_operator(layout, "ShaderNodeTexVoronoi")
self.node_operator(layout, "ShaderNodeTexWave")
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):
bl_idname = "NODE_MT_category_shader_vector"
class NODE_MT_shader_node_vector_base(node_add_menu.NodeMenu):
bl_label = "Vector"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeBump")
node_add_menu.add_node_type(layout, "ShaderNodeDisplacement")
node_add_menu.add_node_type(layout, "ShaderNodeMapping")
node_add_menu.add_node_type(layout, "ShaderNodeNormal")
node_add_menu.add_node_type(layout, "ShaderNodeNormalMap")
node_add_menu.add_node_type(layout, "ShaderNodeRadialTiling")
node_add_menu.add_node_type(layout, "ShaderNodeVectorCurve")
node_add_menu.add_node_type(layout, "ShaderNodeVectorDisplacement")
node_add_menu.add_node_type(layout, "ShaderNodeVectorRotate")
node_add_menu.add_node_type(layout, "ShaderNodeVectorTransform")
self.node_operator(layout, "ShaderNodeBump")
self.node_operator(layout, "ShaderNodeDisplacement")
self.node_operator(layout, "ShaderNodeMapping")
self.node_operator(layout, "ShaderNodeNormal")
self.node_operator(layout, "ShaderNodeNormalMap")
self.node_operator(layout, "ShaderNodeRadialTiling")
self.node_operator(layout, "ShaderNodeVectorCurve")
self.node_operator(layout, "ShaderNodeVectorDisplacement")
self.node_operator(layout, "ShaderNodeVectorRotate")
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):
bl_idname = "NODE_MT_category_shader_script"
class NODE_MT_shader_node_script_base(node_add_menu.NodeMenu):
bl_label = "Script"
def draw(self, _context):
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):
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"
class NODE_MT_shader_node_utilities_base(node_add_menu.NodeMenu):
bl_label = "Utilities"
def draw(self, context):
layout = self.layout
node_add_menu.add_repeat_zone(layout, label="Repeat")
self.repeat_zone(layout, label="Repeat")
layout.separator()
node_add_menu.add_closure_zone(layout, label="Closure")
node_add_menu.add_node_type(layout, "NodeEvaluateClosure")
node_add_menu.add_node_type(layout, "NodeCombineBundle")
node_add_menu.add_node_type(layout, "NodeSeparateBundle")
self.closure_zone(layout, label="Closure")
self.node_operator(layout, "NodeEvaluateClosure")
self.node_operator(layout, "NodeCombineBundle")
self.node_operator(layout, "NodeSeparateBundle")
class NODE_MT_shader_node_add_all(Menu):
bl_idname = "NODE_MT_shader_node_add_all"
bl_label = "Add"
class NODE_MT_shader_node_all_base(node_add_menu.NodeMenu):
bl_label = ""
menu_path = "Root"
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.menu("NODE_MT_category_shader_input")
layout.menu("NODE_MT_category_shader_output")
self.draw_menu(layout, "Input")
self.draw_menu(layout, "Output")
layout.separator()
layout.menu("NODE_MT_category_shader_color")
layout.menu("NODE_MT_category_shader_converter")
layout.menu("NODE_MT_category_shader_shader")
layout.menu("NODE_MT_category_shader_texture")
layout.menu("NODE_MT_category_shader_vector")
layout.menu("NODE_MT_category_shader_utilities")
self.draw_menu(layout, "Color")
self.draw_menu(layout, "Converter")
self.draw_menu(layout, "Shader")
self.draw_menu(layout, "Texture")
self.draw_menu(layout, "Vector")
self.draw_menu(layout, "Utilities")
layout.separator()
layout.menu("NODE_MT_category_shader_script")
self.draw_menu(layout, "Script")
layout.separator()
layout.menu("NODE_MT_category_shader_group")
layout.menu("NODE_MT_category_layout")
self.draw_menu(layout, "Group")
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 = (
NODE_MT_shader_node_add_all,
NODE_MT_category_shader_input,
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,
*add_menus,
*swap_menus,
)

View File

@@ -9,136 +9,154 @@ from bpy.app.translations import (
from bl_ui import node_add_menu
class NODE_MT_category_texture_input(Menu):
bl_idname = "NODE_MT_category_texture_input"
class NODE_MT_texture_node_input_base(Menu):
bl_label = "Input"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeCoordinates")
node_add_menu.add_node_type(layout, "TextureNodeCurveTime")
node_add_menu.add_node_type(layout, "TextureNodeImage")
node_add_menu.add_node_type(layout, "TextureNodeTexture")
self.node_operator(layout, "TextureNodeCoordinates")
self.node_operator(layout, "TextureNodeCurveTime")
self.node_operator(layout, "TextureNodeImage")
self.node_operator(layout, "TextureNodeTexture")
class NODE_MT_category_texture_output(Menu):
bl_idname = "NODE_MT_category_texture_output"
class NODE_MT_texture_node_output_base(Menu):
bl_label = "Output"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeOutput")
node_add_menu.add_node_type(layout, "TextureNodeViewer")
self.node_operator(layout, "TextureNodeOutput")
self.node_operator(layout, "TextureNodeViewer")
class NODE_MT_category_texture_color(Menu):
bl_idname = "NODE_MT_category_texture_color"
class NODE_MT_texture_node_color_base(Menu):
bl_label = "Color"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeHueSaturation")
node_add_menu.add_node_type(layout, "TextureNodeInvert")
node_add_menu.add_node_type(layout, "TextureNodeMixRGB")
node_add_menu.add_node_type(layout, "TextureNodeCurveRGB")
self.node_operator(layout, "TextureNodeHueSaturation")
self.node_operator(layout, "TextureNodeInvert")
self.node_operator(layout, "TextureNodeMixRGB")
self.node_operator(layout, "TextureNodeCurveRGB")
layout.separator()
node_add_menu.add_node_type(layout, "TextureNodeCombineColor")
node_add_menu.add_node_type(layout, "TextureNodeSeparateColor")
self.node_operator(layout, "TextureNodeCombineColor")
self.node_operator(layout, "TextureNodeSeparateColor")
class NODE_MT_category_texture_converter(Menu):
bl_idname = "NODE_MT_category_texture_converter"
class NODE_MT_texture_node_converter_base(Menu):
bl_label = "Converter"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeValToRGB")
node_add_menu.add_node_type(layout, "TextureNodeDistance")
node_add_menu.add_node_type(layout, "TextureNodeMath")
node_add_menu.add_node_type(layout, "TextureNodeRGBToBW")
node_add_menu.add_node_type(layout, "TextureNodeValToNor")
self.node_operator(layout, "TextureNodeValToRGB")
self.node_operator(layout, "TextureNodeDistance")
self.node_operator(layout, "TextureNodeMath")
self.node_operator(layout, "TextureNodeRGBToBW")
self.node_operator(layout, "TextureNodeValToNor")
class NODE_MT_category_texture_distort(Menu):
bl_idname = "NODE_MT_category_texture_distort"
class NODE_MT_texture_node_distort_base(Menu):
bl_label = "Distort"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeAt")
node_add_menu.add_node_type(layout, "TextureNodeRotate")
node_add_menu.add_node_type(layout, "TextureNodeScale")
node_add_menu.add_node_type(layout, "TextureNodeTranslate")
self.node_operator(layout, "TextureNodeAt")
self.node_operator(layout, "TextureNodeRotate")
self.node_operator(layout, "TextureNodeScale")
self.node_operator(layout, "TextureNodeTranslate")
class NODE_MT_category_texture_pattern(Menu):
bl_idname = "NODE_MT_category_texture_pattern"
class NODE_MT_texture_node_pattern_base(Menu):
bl_label = "Pattern"
bl_translation_context = i18n_contexts.id_texture
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeBricks")
node_add_menu.add_node_type(layout, "TextureNodeChecker")
self.node_operator(layout, "TextureNodeBricks")
self.node_operator(layout, "TextureNodeChecker")
class NODE_MT_category_texture_texture(Menu):
bl_idname = "NODE_MT_category_texture_texture"
class NODE_MT_texture_node_texture_base(Menu):
bl_label = "Texture"
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "TextureNodeTexBlend")
node_add_menu.add_node_type(layout, "TextureNodeTexClouds")
node_add_menu.add_node_type(layout, "TextureNodeTexDistNoise")
node_add_menu.add_node_type(layout, "TextureNodeTexMagic")
node_add_menu.add_node_type(layout, "TextureNodeTexMarble")
node_add_menu.add_node_type(layout, "TextureNodeTexMusgrave")
node_add_menu.add_node_type(layout, "TextureNodeTexNoise")
node_add_menu.add_node_type(layout, "TextureNodeTexStucci")
node_add_menu.add_node_type(layout, "TextureNodeTexVoronoi")
node_add_menu.add_node_type(layout, "TextureNodeTexWood")
self.node_operator(layout, "TextureNodeTexBlend")
self.node_operator(layout, "TextureNodeTexClouds")
self.node_operator(layout, "TextureNodeTexDistNoise")
self.node_operator(layout, "TextureNodeTexMagic")
self.node_operator(layout, "TextureNodeTexMarble")
self.node_operator(layout, "TextureNodeTexMusgrave")
self.node_operator(layout, "TextureNodeTexNoise")
self.node_operator(layout, "TextureNodeTexStucci")
self.node_operator(layout, "TextureNodeTexVoronoi")
self.node_operator(layout, "TextureNodeTexWood")
class NODE_MT_category_texture_group(Menu):
bl_idname = "NODE_MT_category_texture_group"
bl_label = "Group"
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"
class NODE_MT_texture_node_all_base(node_add_menu.NodeMenu):
bl_label = ""
menu_path = "Root"
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.menu("NODE_MT_category_texture_input")
layout.menu("NODE_MT_category_texture_output")
self.draw_menu(layout, "Input")
self.draw_menu(layout, "Output")
layout.separator()
layout.menu("NODE_MT_category_texture_color")
layout.menu("NODE_MT_category_texture_converter")
layout.menu("NODE_MT_category_texture_distort")
layout.menu("NODE_MT_category_texture_pattern")
layout.menu("NODE_MT_category_texture_texture")
self.draw_menu(layout, "Color")
self.draw_menu(layout, "Converter")
self.draw_menu(layout, "Distort")
self.draw_menu(layout, "Pattern")
self.draw_menu(layout, "Texture")
layout.separator()
layout.menu("NODE_MT_category_texture_group")
layout.menu("NODE_MT_category_layout")
self.draw_menu(layout, "Group")
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 = (
NODE_MT_texture_node_add_all,
NODE_MT_category_texture_input,
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,
*add_menus,
*swap_menus,
)

View File

@@ -14,7 +14,7 @@ from bpy.app.translations import (
pgettext_iface as iface_,
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.properties_grease_pencil_common import (
AnnotationDataPanel,
@@ -263,10 +263,11 @@ class NODE_MT_editor_menus(Menu):
layout.menu("NODE_MT_view")
layout.menu("NODE_MT_select")
layout.menu("NODE_MT_add")
layout.menu("NODE_MT_swap")
layout.menu("NODE_MT_node")
class NODE_MT_add(Menu):
class NODE_MT_add(node_add_menu.AddNodeMenu):
bl_space_type = 'NODE_EDITOR'
bl_label = "Add"
bl_translation_context = i18n_contexts.operator_default
@@ -298,6 +299,33 @@ class NODE_MT_add(Menu):
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):
bl_label = "View"
@@ -1216,13 +1244,14 @@ classes = (
NODE_HT_header,
NODE_MT_editor_menus,
NODE_MT_add,
NODE_MT_view,
NODE_MT_swap,
NODE_MT_select,
NODE_MT_node,
NODE_MT_node_color_context_menu,
NODE_MT_context_menu_show_hide_menu,
NODE_MT_context_menu_select_menu,
NODE_MT_context_menu,
NODE_MT_view,
NODE_MT_view_pie,
NODE_PT_material_slots,
NODE_PT_geometry_node_tool_object_types,

View File

@@ -132,14 +132,14 @@ class MyCustomNode(MyCustomTreeNode, 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):
layout = self.layout
if context.space_data.tree_type != MyCustomTree.bl_idname:
# Avoid adding nodes to built-in node tree
return
# 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 = (
@@ -156,10 +156,12 @@ def register():
register_class(cls)
bpy.types.NODE_MT_add.append(draw_add_menu)
bpy.types.NODE_MT_swap.append(draw_add_menu)
def unregister():
bpy.types.NODE_MT_add.remove(draw_add_menu)
bpy.types.NODE_MT_swap.remove(draw_add_menu)
from bpy.utils import unregister_class
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);
}
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

View File

@@ -29,6 +29,11 @@ void draw_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item,
StringRefNull menu_name,
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,
PointerRNA &ptr);
bool operator_asset_reference_props_is_set(PointerRNA &ptr);

View File

@@ -20,6 +20,8 @@
#include "ED_node_c.hh"
#include "UI_interface_layout.hh"
struct SpaceNode;
struct ARegion;
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,
const bContext &C,
StringRef catalog_path);
StringRef catalog_path,
const NodeAssetMenuOperatorType operator_type);
/** See #SpaceNode_Runtime::node_can_sync_states. */
Map<int, bool> &node_can_sync_cache_get(SpaceNode &snode);

View File

@@ -89,6 +89,11 @@ enum class LayoutSeparatorType : int8_t {
Line,
};
enum class NodeAssetMenuOperatorType : int8_t {
Add,
Swap,
};
/**
* NOTE: `uiLayout` properties should be considered private outside `interface_layout.cc`,
* 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 {};
}
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);
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) {
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(
menu_path->c_str());
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();
add_separator = false;
}
PointerRNA op_ptr = layout->op("NODE_OT_add_group_asset",
PointerRNA op_ptr = layout->op(*operator_id,
IFACE_(asset->get_name()),
ICON_NONE,
wm::OpCallContext::InvokeRegionWin,
@@ -208,17 +214,23 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
layout->separator();
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);
const bNodeTree *edit_tree = snode.edittree;
if (!edit_tree) {
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) {
snode.runtime->assets_for_menu = std::make_shared<asset::AssetItemTree>(
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;
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()),
ICON_NONE,
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);
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) {
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()) {
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{};
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.draw = node_add_catalog_assets_draw;
type.draw = node_catalog_assets_draw;
type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent;
return type;
}
MenuType add_unassigned_assets_menu_type()
MenuType unassigned_assets_menu_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.draw = node_add_unassigned_assets_draw;
type.draw = node_unassigned_assets_draw;
type.listener = asset::list::asset_reading_region_listen_fn;
type.flag = MenuTypeFlag::ContextDependent;
type.description = N_(
@@ -309,9 +337,20 @@ MenuType add_root_catalogs_menu_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,
const bContext &C,
const StringRef catalog_path)
const StringRef catalog_path,
const NodeAssetMenuOperatorType operator_type)
{
SpaceNode &snode = *CTX_wm_space_node(&C);
if (snode.runtime->assets_for_menu == nullptr) {
@@ -322,9 +361,21 @@ void ui_template_node_asset_menu_items(uiLayout &layout,
if (!item) {
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);
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

View File

@@ -361,6 +361,27 @@ static bool node_add_group_poll(bContext *C)
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)
{
ARegion *region = CTX_wm_region(C);
@@ -490,6 +511,78 @@ static wmOperatorStatus node_add_group_asset_invoke(bContext *C,
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,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
@@ -521,6 +614,21 @@ void NODE_OT_add_group_asset(wmOperatorType *ot)
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_color(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_compositing_node_group(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` */
MenuType add_catalog_assets_menu_type();
MenuType add_unassigned_assets_menu_type();
MenuType catalog_assets_menu_type();
MenuType unassigned_assets_menu_type();
MenuType add_root_catalogs_menu_type();
MenuType swap_root_catalogs_menu_type();
/* `node_sync_sockets.cc` */
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_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_compositing_node_group);

View File

@@ -1884,9 +1884,10 @@ void ED_spacetype_node()
art->draw = node_toolbar_region_draw;
BLI_addhead(&st->regiontypes, art);
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_catalog_assets_menu_type()));
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, add_unassigned_assets_menu_type()));
WM_menutype_add(MEM_dupallocN<MenuType>(__func__, catalog_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__, swap_root_catalogs_menu_type()));
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,
bContext *C,
const char *catalog_path)
const char *catalog_path,
const int operator_type)
{
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,
@@ -1201,6 +1203,20 @@ void RNA_api_ui_layout(StructRNA *srna)
{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};
/* 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");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
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,
"template_modifier_asset_menu_items",