Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWannes Malfait <Wannes>2021-06-01 15:34:02 +0300
committerJacques Lucke <mail@jlucke.com>2021-06-01 15:34:02 +0300
commit26c4357e0bfaf2b9f148efdeab18b6447fd565d9 (patch)
tree7d9a55482ea60981882a33659931a17208e0fa38
parentd55fa2e02a195275807a967989d13b99ee1bdc52 (diff)
Node Wrangler: update for Geometry Nodesv2.93.1v2.93.0
Node Wrangler can now be used in the Geometry Nodes editor. Summary of the changes for Geometry Nodes: * Pressing ctrl+shift+LMB will now connect the geometry socket of the node (if it exists) to the output socket of the modifier. This is similar to the functionality already present for shader and compositor nodes. * Nodes can be merged using a Join Geometry (default) or Boolean node. * `LinkToOutput` and `ReloadImages` have been disabled, because they don't make sense for geometry nodes. * Other operators work like they do for the other node tree types. In D9748 and in chat it was accepted that this patch can be merged into the 2.93 release branch, even though we are quite late in the release process already. Differential Revision: https://developer.blender.org/D9748
-rw-r--r--node_wrangler.py757
1 files changed, 534 insertions, 223 deletions
diff --git a/node_wrangler.py b/node_wrangler.py
index e9ba5e74..118408c4 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -19,8 +19,8 @@
bl_info = {
"name": "Node Wrangler",
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
- "version": (3, 37),
- "blender": (2, 83, 0),
+ "version": (3, 38),
+ "blender": (2, 93, 0),
"location": "Node Editor Toolbar or Shift-W",
"description": "Various tools to enhance and speed up node-based workflow",
"warning": "",
@@ -43,6 +43,7 @@ from bpy.props import (
from bpy_extras.io_utils import ImportHelper, ExportHelper
from gpu_extras.batch import batch_for_shader
from mathutils import Vector
+from nodeitems_utils import node_categories_iter
from math import cos, sin, pi, hypot
from os import path
from glob import glob
@@ -54,7 +55,7 @@ from collections import namedtuple
#################
# rl_outputs:
# list of outputs of Input Render Layer
-# with attributes determinig if pass is used,
+# with attributes determining if pass is used,
# and MultiLayer EXR outputs names and corresponding render engines
#
# rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_eevee, in_cycles)
@@ -90,7 +91,7 @@ rl_outputs = (
# shader nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_input_nodes_props = (
('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
@@ -115,7 +116,7 @@ shaders_input_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_output_nodes_props = (
('ShaderNodeOutputAOV', 'OUTPUT_AOV', 'AOV Output'),
('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
@@ -124,7 +125,7 @@ shaders_output_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_shader_nodes_props = (
('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
@@ -149,7 +150,7 @@ shaders_shader_nodes_props = (
('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
)
# (rna_type.identifier, type, rna_type.name)
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
shaders_texture_nodes_props = (
('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
@@ -169,7 +170,7 @@ shaders_texture_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_color_nodes_props = (
('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
('ShaderNodeGamma', 'GAMMA', 'Gamma'),
@@ -181,7 +182,7 @@ shaders_color_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_vector_nodes_props = (
('ShaderNodeBump', 'BUMP', 'Bump'),
('ShaderNodeDisplacement', 'DISPLACEMENT', 'Displacement'),
@@ -194,7 +195,7 @@ shaders_vector_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_converter_nodes_props = (
('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
('ShaderNodeClamp', 'CLAMP', 'Clamp'),
@@ -213,7 +214,7 @@ shaders_converter_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
shaders_layout_nodes_props = (
('NodeFrame', 'FRAME', 'Frame'),
('NodeReroute', 'REROUTE', 'Reroute'),
@@ -222,7 +223,7 @@ shaders_layout_nodes_props = (
# compositing nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_input_nodes_props = (
('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
('CompositorNodeImage', 'IMAGE', 'Image'),
@@ -237,7 +238,7 @@ compo_input_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_output_nodes_props = (
('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
@@ -247,7 +248,7 @@ compo_output_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_color_nodes_props = (
('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
@@ -264,7 +265,7 @@ compo_color_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_converter_nodes_props = (
('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
@@ -284,7 +285,7 @@ compo_converter_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_filter_nodes_props = (
('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
('CompositorNodeBlur', 'BLUR', 'Blur'),
@@ -303,7 +304,7 @@ compo_filter_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_vector_nodes_props = (
('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
@@ -313,7 +314,7 @@ compo_vector_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_matte_nodes_props = (
('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
@@ -331,7 +332,7 @@ compo_matte_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_distort_nodes_props = (
('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
('CompositorNodeCrop', 'CROP', 'Crop'),
@@ -349,7 +350,7 @@ compo_distort_nodes_props = (
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
-# Keeping things in alphabetical orde so we don't need to sort later.
+# Keeping things in alphabetical order so we don't need to sort later.
compo_layout_nodes_props = (
('NodeFrame', 'FRAME', 'Frame'),
('NodeReroute', 'REROUTE', 'Reroute'),
@@ -529,7 +530,7 @@ operations = [
('COSH', 'Hyperbolic Cosine', 'Hyperbolic Cosine Mode'),
('TANH', 'Hyperbolic Tangent', 'Hyperbolic Tangent Mode'),
('POWER', 'Power', 'Power Mode'),
- ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
+ ('LOGARITHM', 'Logarithm', 'Logarithm Mode'),
('SQRT', 'Square Root', 'Square Root Mode'),
('INVERSE_SQRT', 'Inverse Square Root', 'Inverse Square Root Mode'),
('EXPONENT', 'Exponent', 'Exponent Mode'),
@@ -555,6 +556,14 @@ operations = [
('DEGREES', 'To Degrees', 'To Degrees Mode'),
]
+# Operations used by the geometry boolean node and join geometry node
+geo_combine_operations = [
+ ('JOIN', 'Join Geometry', 'Join Geometry Mode'),
+ ('INTERSECT', 'Intersect', 'Intersect Mode'),
+ ('UNION', 'Union', 'Union Mode'),
+ ('DIFFERENCE', 'Difference', 'Difference Mode'),
+]
+
# in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
# used list, not tuple for easy merging with other lists.
navs = [
@@ -598,6 +607,11 @@ draw_color_sets = {
viewer_socket_name = "tmp_viewer"
+def get_nodes_from_category(category_name, context):
+ for category in node_categories_iter(context):
+ if category.name == category_name:
+ return sorted(category.items(context), key=lambda node: node.label)
+
def is_visible_socket(socket):
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
@@ -680,40 +694,41 @@ def node_mid_pt(node, axis):
def autolink(node1, node2, links):
link_made = False
-
- for outp in node1.outputs:
- for inp in node2.inputs:
+ available_inputs = [inp for inp in node2.inputs if inp.enabled]
+ available_outputs = [outp for outp in node1.outputs if outp.enabled]
+ for outp in available_outputs:
+ for inp in available_inputs:
if not inp.is_linked and inp.name == outp.name:
link_made = True
links.new(outp, inp)
return True
- for outp in node1.outputs:
- for inp in node2.inputs:
+ for outp in available_outputs:
+ for inp in available_inputs:
if not inp.is_linked and inp.type == outp.type:
link_made = True
links.new(outp, inp)
return True
# force some connection even if the type doesn't match
- for outp in node1.outputs:
- for inp in node2.inputs:
+ if available_outputs:
+ for inp in available_inputs:
if not inp.is_linked:
link_made = True
- links.new(outp, inp)
+ links.new(available_outputs[0], inp)
return True
# even if no sockets are open, force one of matching type
- for outp in node1.outputs:
- for inp in node2.inputs:
+ for outp in available_outputs:
+ for inp in available_inputs:
if inp.type == outp.type:
link_made = True
links.new(outp, inp)
return True
# do something!
- for outp in node1.outputs:
- for inp in node2.inputs:
+ for outp in available_outputs:
+ for inp in available_inputs:
link_made = True
links.new(outp, inp)
return True
@@ -1236,7 +1251,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
def nw_check(context):
space = context.space_data
- valid_trees = ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
+ valid_trees = ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree", "GeometryNodeTree"]
valid = False
if space.type == 'NODE_EDITOR' and space.node_tree is not None and space.tree_type in valid_trees:
@@ -1598,14 +1613,17 @@ class NWSwapLinks(Operator, NWBase):
# Swap Inputs
elif len(selected_nodes) == 1:
+ if n1.inputs and n1.inputs[0].is_multi_input:
+ self.report({'WARNING'}, "Can't swap inputs of a multi input socket!")
+ return {'FINISHED'}
if n1.inputs:
types = []
i=0
for i1 in n1.inputs:
- if i1.is_linked:
+ if i1.is_linked and not i1.is_multi_input:
similar_types = 0
for i2 in n1.inputs:
- if i1.type == i2.type and i2.is_linked:
+ if i1.type == i2.type and i2.is_linked and not i2.is_multi_input:
similar_types += 1
types.append ([i1, similar_types, i])
i += 1
@@ -1686,10 +1704,10 @@ class NWAddAttrNode(Operator, NWBase):
nodes.active.attribute_name = self.attr_name
return {'FINISHED'}
-class NWEmissionViewer(Operator, NWBase):
- bl_idname = "node.nw_emission_viewer"
- bl_label = "Emission Viewer"
- bl_description = "Connect active node to Emission Shader for shadeless previews"
+class NWPreviewNode(Operator, NWBase):
+ bl_idname = "node.nw_preview_node"
+ bl_label = "Preview Node"
+ bl_description = "Connect active node to Emission Shader for shadeless previews, or to the geometry node tree's output"
bl_options = {'REGISTER', 'UNDO'}
def __init__(self):
@@ -1701,7 +1719,7 @@ class NWEmissionViewer(Operator, NWBase):
def poll(cls, context):
if nw_check(context):
space = context.space_data
- if space.tree_type == 'ShaderNodeTree':
+ if space.tree_type == 'ShaderNodeTree' or space.tree_type == 'GeometryNodeTree':
if context.active_node:
if context.active_node.type != "OUTPUT_MATERIAL" or context.active_node.type != "OUTPUT_WORLD":
return True
@@ -1772,11 +1790,13 @@ class NWEmissionViewer(Operator, NWBase):
groupout.location.x = loc_x
groupout.location.y = loc_y
groupout.select = False
+ # So that we don't keep on adding new group outputs
+ groupout.is_active_output = True
return groupout
@classmethod
def search_sockets(cls, node, sockets, index=None):
- #recursevley scan nodes for viewer sockets and store in list
+ # recursively scan nodes for viewer sockets and store in list
for i, input_socket in enumerate(node.inputs):
if index and i != index:
continue
@@ -1855,6 +1875,98 @@ class NWEmissionViewer(Operator, NWBase):
nodes, links = active_tree.nodes, active_tree.links
base_node_tree = space.node_tree
active = nodes.active
+
+ # For geometry node trees we just connect to the group output,
+ # because there is no "viewer node" yet.
+ if space.tree_type == "GeometryNodeTree":
+ valid = False
+ if active:
+ for out in active.outputs:
+ if is_visible_socket(out):
+ valid = True
+ break
+ # Exit early
+ if not valid:
+ return {'FINISHED'}
+
+ delete_sockets = []
+
+ # Scan through all nodes in tree including nodes inside of groups to find viewer sockets
+ self.scan_nodes(base_node_tree, delete_sockets)
+
+ # Find (or create if needed) the output of this node tree
+ geometryoutput = self.ensure_group_output(base_node_tree)
+
+ # Analyze outputs, make links
+ out_i = None
+ valid_outputs = []
+ for i, out in enumerate(active.outputs):
+ if is_visible_socket(out) and out.type == 'GEOMETRY':
+ valid_outputs.append(i)
+ if valid_outputs:
+ out_i = valid_outputs[0] # Start index of node's outputs
+ for i, valid_i in enumerate(valid_outputs):
+ for out_link in active.outputs[valid_i].links:
+ if is_viewer_link(out_link, geometryoutput):
+ if nodes == base_node_tree.nodes or self.link_leads_to_used_socket(out_link):
+ if i < len(valid_outputs) - 1:
+ out_i = valid_outputs[i + 1]
+ else:
+ out_i = valid_outputs[0]
+
+ make_links = [] # store sockets for new links
+ delete_nodes = [] # store unused nodes to delete in the end
+ if active.outputs:
+ # If there is no 'GEOMETRY' output type - We can't preview the node
+ if out_i is None:
+ return {'FINISHED'}
+ socket_type = 'GEOMETRY'
+ # Find an input socket of the output of type geometry
+ geometryoutindex = None
+ for i,inp in enumerate(geometryoutput.inputs):
+ if inp.type == socket_type:
+ geometryoutindex = i
+ break
+ if geometryoutindex is None:
+ # Create geometry socket
+ geometryoutput.inputs.new(socket_type, 'Geometry')
+ geometryoutindex = len(geometryoutput.inputs) - 1
+
+ make_links.append((active.outputs[out_i], geometryoutput.inputs[geometryoutindex]))
+ output_socket = geometryoutput.inputs[geometryoutindex]
+ for li_from, li_to in make_links:
+ base_node_tree.links.new(li_from, li_to)
+ tree = base_node_tree
+ link_end = output_socket
+ while tree.nodes.active != active:
+ node = tree.nodes.active
+ index = self.ensure_viewer_socket(node,'NodeSocketGeometry', connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None)
+ link_start = node.outputs[index]
+ node_socket = node.node_tree.outputs[index]
+ if node_socket in delete_sockets:
+ delete_sockets.remove(node_socket)
+ tree.links.new(link_start, link_end)
+ # Iterate
+ link_end = self.ensure_group_output(node.node_tree).inputs[index]
+ tree = tree.nodes.active.node_tree
+ tree.links.new(active.outputs[out_i], link_end)
+
+ # Delete sockets
+ for socket in delete_sockets:
+ tree = socket.id_data
+ tree.outputs.remove(socket)
+
+ # Delete nodes
+ for tree, node in delete_nodes:
+ tree.nodes.remove(node)
+
+ nodes.active = active
+ active.select = True
+ force_update(context)
+ return {'FINISHED'}
+
+
+ # What follows is code for the shader editor
output_types = [x[1] for x in shaders_output_nodes_props]
valid = False
if active:
@@ -2021,11 +2133,22 @@ class NWFrameSelected(Operator, NWBase):
return {'FINISHED'}
-class NWReloadImages(Operator, NWBase):
+class NWReloadImages(Operator):
bl_idname = "node.nw_reload_images"
bl_label = "Reload Images"
bl_description = "Update all the image nodes to match their files on disk"
+ @classmethod
+ def poll(cls, context):
+ valid = False
+ if nw_check(context) and context.space_data.tree_type != 'GeometryNodeTree':
+ if context.active_node is not None:
+ for out in context.active_node.outputs:
+ if is_visible_socket(out):
+ valid = True
+ break
+ return valid
+
def execute(self, context):
nodes, links = get_nodes_links(context)
image_types = ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
@@ -2094,9 +2217,16 @@ class NWSwitchNodeType(Operator, NWBase):
list(texture_layout_nodes_props)
)
+ geo_to_type: StringProperty(
+ name="Switch to type",
+ default = '',
+ )
+
def execute(self, context):
nodes, links = get_nodes_links(context)
to_type = self.to_type
+ if self.geo_to_type != '':
+ to_type = self.geo_to_type
# Those types of nodes will not swap.
src_excludes = ('NodeFrame')
# Those attributes of nodes will be copied if possible
@@ -2275,8 +2405,8 @@ class NWMergeNodes(Operator, NWBase):
mode: EnumProperty(
name="mode",
- description="All possible blend types and math operations",
- items=blend_types + [op for op in operations if op not in blend_types],
+ description="All possible blend types, boolean operations and math operations",
+ items= blend_types + [op for op in geo_combine_operations if op not in blend_types] + [op for op in operations if op not in blend_types],
)
merge_type: EnumProperty(
name="merge type",
@@ -2284,6 +2414,7 @@ class NWMergeNodes(Operator, NWBase):
items=(
('AUTO', 'Auto', 'Automatic Output Type Detection'),
('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
+ ('GEOMETRY', 'Geometry', 'Merge using Boolean or Join Geometry Node'),
('MIX', 'Mix Node', 'Merge using Mix Nodes'),
('MATH', 'Math Node', 'Merge using Math Nodes'),
('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
@@ -2291,6 +2422,74 @@ class NWMergeNodes(Operator, NWBase):
),
)
+ # Check if the link connects to a node that is in selected_nodes
+ # If not, then check recursively for each link in the nodes outputs.
+ # If yes, return True. If the recursion stops without finding a node
+ # in selected_nodes, it returns False. The depth is used to prevent
+ # getting stuck in a loop because of an already present cycle.
+ @staticmethod
+ def link_creates_cycle(link, selected_nodes, depth=0)->bool:
+ if depth > 255:
+ # We're stuck in a cycle, but that cycle was already present,
+ # so we return False.
+ # NOTE: The number 255 is arbitrary, but seems to work well.
+ return False
+ node = link.to_node
+ if node in selected_nodes:
+ return True
+ if not node.outputs:
+ return False
+ for output in node.outputs:
+ if output.is_linked:
+ for olink in output.links:
+ if NWMergeNodes.link_creates_cycle(olink, selected_nodes, depth+1):
+ return True
+ # None of the outputs found a node in selected_nodes, so there is no cycle.
+ return False
+
+ # Merge the nodes in `nodes_list` with a node of type `node_name` that has a multi_input socket.
+ # The parameters `socket_indices` gives the indices of the node sockets in the order that they should
+ # be connected. The last one is assumed to be a multi input socket.
+ # For convenience the node is returned.
+ @staticmethod
+ def merge_with_multi_input(nodes_list, merge_position,do_hide, loc_x, links, nodes, node_name, socket_indices):
+ # The y-location of the last node
+ loc_y = nodes_list[-1][2]
+ if merge_position == 'CENTER':
+ # Average the y-location
+ for i in range(len(nodes_list)-1):
+ loc_y += nodes_list[i][2]
+ loc_y = loc_y/len(nodes_list)
+ new_node = nodes.new(node_name)
+ new_node.hide = do_hide
+ new_node.location.x = loc_x
+ new_node.location.y = loc_y
+ selected_nodes = [nodes[node_info[0]] for node_info in nodes_list]
+ prev_links = []
+ outputs_for_multi_input = []
+ for i,node in enumerate(selected_nodes):
+ node.select = False
+ # Search for the first node which had output links that do not create
+ # a cycle, which we can then reconnect afterwards.
+ if prev_links == [] and node.outputs[0].is_linked:
+ prev_links = [link for link in node.outputs[0].links if not NWMergeNodes.link_creates_cycle(link, selected_nodes)]
+ # Get the index of the socket, the last one is a multi input, and is thus used repeatedly
+ # To get the placement to look right we need to reverse the order in which we connect the
+ # outputs to the multi input socket.
+ if i < len(socket_indices) - 1:
+ ind = socket_indices[i]
+ links.new(node.outputs[0], new_node.inputs[ind])
+ else:
+ outputs_for_multi_input.insert(0, node.outputs[0])
+ if outputs_for_multi_input != []:
+ ind = socket_indices[-1]
+ for output in outputs_for_multi_input:
+ links.new(output, new_node.inputs[ind])
+ if prev_links != []:
+ for link in prev_links:
+ links.new(new_node.outputs[0], link.to_node.inputs[0])
+ return new_node
+
def execute(self, context):
settings = context.preferences.addons[__name__].preferences
merge_hide = settings.merge_hide
@@ -2305,6 +2504,8 @@ class NWMergeNodes(Operator, NWBase):
do_hide = True
tree_type = context.space_data.node_tree.type
+ if tree_type == 'GEOMETRY':
+ node_type = 'GeometryNode'
if tree_type == 'COMPOSITING':
node_type = 'CompositorNode'
elif tree_type == 'SHADER':
@@ -2320,9 +2521,16 @@ class NWMergeNodes(Operator, NWBase):
if (merge_type == 'ZCOMBINE' or merge_type == 'ALPHAOVER') and tree_type != 'COMPOSITING':
merge_type = 'MIX'
mode = 'MIX'
+ if (merge_type != 'MATH' and merge_type != 'GEOMETRY') and tree_type == 'GEOMETRY':
+ merge_type = 'AUTO'
+ # The math nodes used for geometry nodes are of type 'ShaderNode'
+ if merge_type == 'MATH' and tree_type == 'GEOMETRY':
+ node_type = 'ShaderNode'
selected_mix = [] # entry = [index, loc]
selected_shader = [] # entry = [index, loc]
+ selected_geometry = [] # entry = [index, loc]
selected_math = [] # entry = [index, loc]
+ selected_vector = [] # entry = [index, loc]
selected_z = [] # entry = [index, loc]
selected_alphaover = [] # entry = [index, loc]
@@ -2331,17 +2539,29 @@ class NWMergeNodes(Operator, NWBase):
if merge_type == 'AUTO':
for (type, types_list, dst) in (
('SHADER', ('MIX', 'ADD'), selected_shader),
+ ('GEOMETRY', [t[0] for t in geo_combine_operations], selected_geometry),
('RGBA', [t[0] for t in blend_types], selected_mix),
('VALUE', [t[0] for t in operations], selected_math),
+ ('VECTOR', [], selected_vector),
):
output_type = node.outputs[0].type
valid_mode = mode in types_list
+ # When mode is 'MIX' we have to cheat since the mix node is not used in
+ # geometry nodes.
+ if tree_type == 'GEOMETRY':
+ if mode == 'MIX':
+ if output_type == 'VALUE' and type == 'VALUE':
+ valid_mode = True
+ elif output_type == 'VECTOR' and type == 'VECTOR':
+ valid_mode = True
+ elif type == 'GEOMETRY':
+ valid_mode = True
# When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
# Cheat that output type is 'RGBA',
# and that 'MIX' exists in math operations list.
# This way when selected_mix list is analyzed:
# Node data will be appended even though it doesn't meet requirements.
- if output_type != 'SHADER' and mode == 'MIX':
+ elif output_type != 'SHADER' and mode == 'MIX':
output_type = 'RGBA'
valid_mode = True
if output_type == type and valid_mode:
@@ -2349,6 +2569,7 @@ class NWMergeNodes(Operator, NWBase):
else:
for (type, types_list, dst) in (
('SHADER', ('MIX', 'ADD'), selected_shader),
+ ('GEOMETRY', [t[0] for t in geo_combine_operations], selected_geometry),
('MIX', [t[0] for t in blend_types], selected_mix),
('MATH', [t[0] for t in operations], selected_math),
('ZCOMBINE', ('MIX', ), selected_z),
@@ -2362,158 +2583,191 @@ class NWMergeNodes(Operator, NWBase):
if selected_mix and selected_math and merge_type == 'AUTO':
selected_mix += selected_math
selected_math = []
-
- for nodes_list in [selected_mix, selected_shader, selected_math, selected_z, selected_alphaover]:
- if nodes_list:
- count_before = len(nodes)
- # sort list by loc_x - reversed
- nodes_list.sort(key=lambda k: k[1], reverse=True)
- # get maximum loc_x
- loc_x = nodes_list[0][1] + nodes_list[0][3] + 70
- nodes_list.sort(key=lambda k: k[2], reverse=True)
- if merge_position == 'CENTER':
- loc_y = ((nodes_list[len(nodes_list) - 1][2]) + (nodes_list[len(nodes_list) - 2][2])) / 2 # average yloc of last two nodes (lowest two)
- if nodes_list[len(nodes_list) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
- if do_hide:
- loc_y += 40
- else:
- loc_y += 80
+ for nodes_list in [selected_mix, selected_shader, selected_geometry, selected_math, selected_vector, selected_z, selected_alphaover]:
+ if not nodes_list:
+ continue
+ count_before = len(nodes)
+ # sort list by loc_x - reversed
+ nodes_list.sort(key=lambda k: k[1], reverse=True)
+ # get maximum loc_x
+ loc_x = nodes_list[0][1] + nodes_list[0][3] + 70
+ nodes_list.sort(key=lambda k: k[2], reverse=True)
+
+ # Change the node type for math nodes in a geometry node tree.
+ if tree_type == 'GEOMETRY':
+ if nodes_list is selected_math or nodes_list is selected_vector:
+ node_type = 'ShaderNode'
+ if mode == 'MIX':
+ mode = 'ADD'
else:
- loc_y = nodes_list[len(nodes_list) - 1][2]
- offset_y = 100
- if not do_hide:
- offset_y = 200
- if nodes_list == selected_shader and not do_hide_shader:
- offset_y = 150.0
- the_range = len(nodes_list) - 1
- if len(nodes_list) == 1:
- the_range = 1
- for i in range(the_range):
- if nodes_list == selected_mix:
- add_type = node_type + 'MixRGB'
+ node_type = 'GeometryNode'
+ if merge_position == 'CENTER':
+ loc_y = ((nodes_list[len(nodes_list) - 1][2]) + (nodes_list[len(nodes_list) - 2][2])) / 2 # average yloc of last two nodes (lowest two)
+ if nodes_list[len(nodes_list) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
+ if do_hide:
+ loc_y += 40
+ else:
+ loc_y += 80
+ else:
+ loc_y = nodes_list[len(nodes_list) - 1][2]
+ offset_y = 100
+ if not do_hide:
+ offset_y = 200
+ if nodes_list == selected_shader and not do_hide_shader:
+ offset_y = 150.0
+ the_range = len(nodes_list) - 1
+ if len(nodes_list) == 1:
+ the_range = 1
+ was_multi = False
+ for i in range(the_range):
+ if nodes_list == selected_mix:
+ add_type = node_type + 'MixRGB'
+ add = nodes.new(add_type)
+ add.blend_type = mode
+ if mode != 'MIX':
+ add.inputs[0].default_value = 1.0
+ add.show_preview = False
+ add.hide = do_hide
+ if do_hide:
+ loc_y = loc_y - 50
+ first = 1
+ second = 2
+ add.width_hidden = 100.0
+ elif nodes_list == selected_math:
+ add_type = node_type + 'Math'
+ add = nodes.new(add_type)
+ add.operation = mode
+ add.hide = do_hide
+ if do_hide:
+ loc_y = loc_y - 50
+ first = 0
+ second = 1
+ add.width_hidden = 100.0
+ elif nodes_list == selected_shader:
+ if mode == 'MIX':
+ add_type = node_type + 'MixShader'
add = nodes.new(add_type)
- add.blend_type = mode
- if mode != 'MIX':
- add.inputs[0].default_value = 1.0
- add.show_preview = False
- add.hide = do_hide
- if do_hide:
+ add.hide = do_hide_shader
+ if do_hide_shader:
loc_y = loc_y - 50
first = 1
second = 2
add.width_hidden = 100.0
- elif nodes_list == selected_math:
- add_type = node_type + 'Math'
+ elif mode == 'ADD':
+ add_type = node_type + 'AddShader'
add = nodes.new(add_type)
- add.operation = mode
- add.hide = do_hide
- if do_hide:
+ add.hide = do_hide_shader
+ if do_hide_shader:
loc_y = loc_y - 50
first = 0
second = 1
add.width_hidden = 100.0
- elif nodes_list == selected_shader:
- if mode == 'MIX':
- add_type = node_type + 'MixShader'
- add = nodes.new(add_type)
- add.hide = do_hide_shader
- if do_hide_shader:
- loc_y = loc_y - 50
- first = 1
- second = 2
- add.width_hidden = 100.0
- elif mode == 'ADD':
- add_type = node_type + 'AddShader'
- add = nodes.new(add_type)
- add.hide = do_hide_shader
- if do_hide_shader:
- loc_y = loc_y - 50
- first = 0
- second = 1
- add.width_hidden = 100.0
- elif nodes_list == selected_z:
- add = nodes.new('CompositorNodeZcombine')
- add.show_preview = False
- add.hide = do_hide
- if do_hide:
- loc_y = loc_y - 50
- first = 0
- second = 2
- add.width_hidden = 100.0
- elif nodes_list == selected_alphaover:
- add = nodes.new('CompositorNodeAlphaOver')
- add.show_preview = False
- add.hide = do_hide
- if do_hide:
- loc_y = loc_y - 50
- first = 1
- second = 2
- add.width_hidden = 100.0
- add.location = loc_x, loc_y
- loc_y += offset_y
- add.select = True
- count_adds = i + 1
- count_after = len(nodes)
- index = count_after - 1
- first_selected = nodes[nodes_list[0][0]]
- # "last" node has been added as first, so its index is count_before.
- last_add = nodes[count_before]
- # Special case:
- # Two nodes were selected and first selected has no output links, second selected has output links.
- # Then add links from last add to all links 'to_socket' of out links of second selected.
- if len(nodes_list) == 2:
- if not first_selected.outputs[0].links:
- second_selected = nodes[nodes_list[1][0]]
- for ss_link in second_selected.outputs[0].links:
- # Prevent cyclic dependencies when nodes to be marged are linked to one another.
- # Create list of invalid indexes.
- invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader + selected_z)]
- # Link only if "to_node" index not in invalid indexes list.
- if ss_link.to_node not in [nodes[i] for i in invalid_i]:
- links.new(last_add.outputs[0], ss_link.to_socket)
- # add links from last_add to all links 'to_socket' of out links of first selected.
- for fs_link in first_selected.outputs[0].links:
- # Prevent cyclic dependencies when nodes to be marged are linked to one another.
- # Create list of invalid indexes.
- invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader + selected_z)]
- # Link only if "to_node" index not in invalid indexes list.
- if fs_link.to_node not in [nodes[i] for i in invalid_i]:
- links.new(last_add.outputs[0], fs_link.to_socket)
- # add link from "first" selected and "first" add node
- node_to = nodes[count_after - 1]
- links.new(first_selected.outputs[0], node_to.inputs[first])
- if node_to.type == 'ZCOMBINE':
- for fs_out in first_selected.outputs:
- if fs_out != first_selected.outputs[0] and fs_out.name in ('Z', 'Depth'):
- links.new(fs_out, node_to.inputs[1])
- break
- # add links between added ADD nodes and between selected and ADD nodes
- for i in range(count_adds):
- if i < count_adds - 1:
- node_from = nodes[index]
- node_to = nodes[index - 1]
- node_to_input_i = first
- node_to_z_i = 1 # if z combine - link z to first z input
- links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
- if node_to.type == 'ZCOMBINE':
- for from_out in node_from.outputs:
- if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
- links.new(from_out, node_to.inputs[node_to_z_i])
- if len(nodes_list) > 1:
- node_from = nodes[nodes_list[i + 1][0]]
- node_to = nodes[index]
- node_to_input_i = second
- node_to_z_i = 3 # if z combine - link z to second z input
- links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
- if node_to.type == 'ZCOMBINE':
- for from_out in node_from.outputs:
- if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
- links.new(from_out, node_to.inputs[node_to_z_i])
- index -= 1
- # set "last" of added nodes as active
- nodes.active = last_add
- for i, x, y, dx, h in nodes_list:
- nodes[i].select = False
+ elif nodes_list == selected_geometry:
+ if mode in ('JOIN', 'MIX'):
+ add_type = node_type + 'JoinGeometry'
+ add = self.merge_with_multi_input(nodes_list, merge_position, do_hide, loc_x, links, nodes, add_type,[0])
+ else:
+ add_type = node_type + 'Boolean'
+ indices = [0,1] if mode == 'DIFFERENCE' else [1]
+ add = self.merge_with_multi_input(nodes_list, merge_position, do_hide, loc_x, links, nodes, add_type,indices)
+ add.operation = mode
+ was_multi = True
+ break
+ elif nodes_list == selected_vector:
+ add_type = node_type + 'VectorMath'
+ add = nodes.new(add_type)
+ add.operation = mode
+ add.hide = do_hide
+ if do_hide:
+ loc_y = loc_y - 50
+ first = 0
+ second = 1
+ add.width_hidden = 100.0
+ elif nodes_list == selected_z:
+ add = nodes.new('CompositorNodeZcombine')
+ add.show_preview = False
+ add.hide = do_hide
+ if do_hide:
+ loc_y = loc_y - 50
+ first = 0
+ second = 2
+ add.width_hidden = 100.0
+ elif nodes_list == selected_alphaover:
+ add = nodes.new('CompositorNodeAlphaOver')
+ add.show_preview = False
+ add.hide = do_hide
+ if do_hide:
+ loc_y = loc_y - 50
+ first = 1
+ second = 2
+ add.width_hidden = 100.0
+ add.location = loc_x, loc_y
+ loc_y += offset_y
+ add.select = True
+
+ # This has already been handled separately
+ if was_multi:
+ continue
+ count_adds = i + 1
+ count_after = len(nodes)
+ index = count_after - 1
+ first_selected = nodes[nodes_list[0][0]]
+ # "last" node has been added as first, so its index is count_before.
+ last_add = nodes[count_before]
+ # Create list of invalid indexes.
+ invalid_nodes = [nodes[n[0]] for n in (selected_mix + selected_math + selected_shader + selected_z + selected_geometry)]
+
+ # Special case:
+ # Two nodes were selected and first selected has no output links, second selected has output links.
+ # Then add links from last add to all links 'to_socket' of out links of second selected.
+ if len(nodes_list) == 2:
+ if not first_selected.outputs[0].links:
+ second_selected = nodes[nodes_list[1][0]]
+ for ss_link in second_selected.outputs[0].links:
+ # Prevent cyclic dependencies when nodes to be merged are linked to one another.
+ # Link only if "to_node" index not in invalid indexes list.
+ if not self.link_creates_cycle(ss_link, invalid_nodes):
+ links.new(last_add.outputs[0], ss_link.to_socket)
+ # add links from last_add to all links 'to_socket' of out links of first selected.
+ for fs_link in first_selected.outputs[0].links:
+ # Link only if "to_node" index not in invalid indexes list.
+ if not self.link_creates_cycle(fs_link, invalid_nodes):
+ links.new(last_add.outputs[0], fs_link.to_socket)
+ # add link from "first" selected and "first" add node
+ node_to = nodes[count_after - 1]
+ links.new(first_selected.outputs[0], node_to.inputs[first])
+ if node_to.type == 'ZCOMBINE':
+ for fs_out in first_selected.outputs:
+ if fs_out != first_selected.outputs[0] and fs_out.name in ('Z', 'Depth'):
+ links.new(fs_out, node_to.inputs[1])
+ break
+ # add links between added ADD nodes and between selected and ADD nodes
+ for i in range(count_adds):
+ if i < count_adds - 1:
+ node_from = nodes[index]
+ node_to = nodes[index - 1]
+ node_to_input_i = first
+ node_to_z_i = 1 # if z combine - link z to first z input
+ links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
+ if node_to.type == 'ZCOMBINE':
+ for from_out in node_from.outputs:
+ if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
+ links.new(from_out, node_to.inputs[node_to_z_i])
+ if len(nodes_list) > 1:
+ node_from = nodes[nodes_list[i + 1][0]]
+ node_to = nodes[index]
+ node_to_input_i = second
+ node_to_z_i = 3 # if z combine - link z to second z input
+ links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
+ if node_to.type == 'ZCOMBINE':
+ for from_out in node_from.outputs:
+ if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
+ links.new(from_out, node_to.inputs[node_to_z_i])
+ index -= 1
+ # set "last" of added nodes as active
+ nodes.active = last_add
+ for i, x, y, dx, h in nodes_list:
+ nodes[i].select = False
return {'FINISHED'}
@@ -2534,12 +2788,10 @@ class NWBatchChangeNodes(Operator, NWBase):
)
def execute(self, context):
-
- nodes, links = get_nodes_links(context)
blend_type = self.blend_type
operation = self.operation
for node in context.selected_nodes:
- if node.type == 'MIX_RGB':
+ if node.type == 'MIX_RGB' or node.bl_idname == 'GeometryNodeAttributeMix':
if not blend_type in [nav[0] for nav in navs]:
node.blend_type = blend_type
else:
@@ -2558,7 +2810,7 @@ class NWBatchChangeNodes(Operator, NWBase):
else:
node.blend_type = blend_types[index - 1][0]
- if node.type == 'MATH':
+ if node.type == 'MATH' or node.bl_idname == 'GeometryNodeAttributeMath':
if not operation in [nav[0] for nav in navs]:
node.operation = operation
else:
@@ -3478,7 +3730,7 @@ class NWDetachOutputs(Operator, NWBase):
return {'FINISHED'}
-class NWLinkToOutputNode(Operator, NWBase):
+class NWLinkToOutputNode(Operator):
"""Link to Composite node or Material Output node"""
bl_idname = "node.nw_link_out"
bl_label = "Connect to Output"
@@ -3487,7 +3739,7 @@ class NWLinkToOutputNode(Operator, NWBase):
@classmethod
def poll(cls, context):
valid = False
- if nw_check(context):
+ if nw_check(context) and context.space_data.tree_type != 'GeometryNodeTree':
if context.active_node is not None:
for out in context.active_node.outputs:
if is_visible_socket(out):
@@ -3993,7 +4245,8 @@ def drawlayout(context, layout, mode='non-panel'):
col = layout.column(align=True)
col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
- col.operator(NWLinkToOutputNode.bl_idname, icon='DRIVER')
+ if tree_type != 'GeometryNodeTree':
+ col.operator(NWLinkToOutputNode.bl_idname, icon='DRIVER')
col.separator()
col = layout.column(align=True)
@@ -4012,7 +4265,8 @@ def drawlayout(context, layout, mode='non-panel'):
col = layout.column(align=True)
if tree_type == 'CompositorNodeTree':
col.operator(NWResetBG.bl_idname, icon='ZOOM_PREVIOUS')
- col.operator(NWReloadImages.bl_idname, icon='FILE_REFRESH')
+ if tree_type != 'GeometryNodeTree':
+ col.operator(NWReloadImages.bl_idname, icon='FILE_REFRESH')
col.separator()
col = layout.column(align=True)
@@ -4067,15 +4321,29 @@ class NWMergeNodesMenu(Menu, NWBase):
layout = self.layout
if type == 'ShaderNodeTree':
layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
- layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
- layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
- props = layout.operator(NWMergeNodes.bl_idname, text="Use Z-Combine Nodes")
- props.mode = 'MIX'
- props.merge_type = 'ZCOMBINE'
- props = layout.operator(NWMergeNodes.bl_idname, text="Use Alpha Over Nodes")
- props.mode = 'MIX'
- props.merge_type = 'ALPHAOVER'
-
+ if type == 'GeometryNodeTree':
+ layout.menu(NWMergeGeometryMenu.bl_idname, text="Use Geometry Nodes")
+ layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
+ else:
+ layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
+ layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
+ props = layout.operator(NWMergeNodes.bl_idname, text="Use Z-Combine Nodes")
+ props.mode = 'MIX'
+ props.merge_type = 'ZCOMBINE'
+ props = layout.operator(NWMergeNodes.bl_idname, text="Use Alpha Over Nodes")
+ props.mode = 'MIX'
+ props.merge_type = 'ALPHAOVER'
+
+class NWMergeGeometryMenu(Menu, NWBase):
+ bl_idname = "NODE_MT_nw_merge_geometry_menu"
+ bl_label = "Merge Selected Nodes using Geometry Nodes"
+ def draw(self, context):
+ layout = self.layout
+ # The boolean node + Join Geometry node
+ for type, name, description in geo_combine_operations:
+ props = layout.operator(NWMergeNodes.bl_idname, text=name)
+ props.mode = type
+ props.merge_type = 'GEOMETRY'
class NWMergeShadersMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_shaders_menu"
@@ -4110,18 +4378,12 @@ class NWConnectionListOutputs(Menu, NWBase):
nodes, links = get_nodes_links(context)
n1 = nodes[context.scene.NWLazySource]
-
- if n1.type == "R_LAYERS":
- index=0
- for o in n1.outputs:
- if o.enabled: # Check which passes the render layer has enabled
- layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
- index+=1
- else:
- index=0
- for o in n1.outputs:
+ index=0
+ for o in n1.outputs:
+ # Only show sockets that are exposed.
+ if o.enabled:
layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
- index+=1
+ index+=1
class NWConnectionListInputs(Menu, NWBase):
@@ -4136,10 +4398,15 @@ class NWConnectionListInputs(Menu, NWBase):
index = 0
for i in n2.inputs:
- op = layout.operator(NWMakeLink.bl_idname, text=i.name, icon="FORWARD")
- op.from_socket = context.scene.NWSourceSocket
- op.to_socket = index
- index+=1
+ # Only show sockets that are exposed.
+ # This prevents, for example, the scale value socket
+ # of the vector math node being added to the list when
+ # the mode is not 'SCALE'.
+ if i.enabled:
+ op = layout.operator(NWMakeLink.bl_idname, text=i.name, icon="FORWARD")
+ op.from_socket = context.scene.NWSourceSocket
+ op.to_socket = index
+ index+=1
class NWMergeMathMenu(Menu, NWBase):
@@ -4351,6 +4618,17 @@ class NWSwitchNodeTypeMenu(Menu, NWBase):
layout.menu(NWSwitchTexConverterSubmenu.bl_idname)
layout.menu(NWSwitchTexDistortSubmenu.bl_idname)
layout.menu(NWSwitchTexLayoutSubmenu.bl_idname)
+ if tree.type == 'GEOMETRY':
+ categories = [c for c in node_categories_iter(context)
+ if c.name not in ['Group', 'Script']]
+ for cat in categories:
+ idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
+ if hasattr(bpy.types, idname):
+ layout.menu(idname)
+ else:
+ layout.label(text="Unable to load altered node lists.")
+ layout.label(text="Please re-enable Node Wrangler.")
+ break
class NWSwitchShadersInputSubmenu(Menu, NWBase):
@@ -4697,6 +4975,17 @@ class NWSwitchTexLayoutSubmenu(Menu, NWBase):
props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
props.to_type = ident
+def draw_switch_category_submenu(self, context):
+ layout = self.layout
+ if self.category.name == 'Layout':
+ for node in self.category.items(context):
+ if node.nodetype != 'NodeFrame':
+ props = layout.operator(NWSwitchNodeType.bl_idname, text=node.label)
+ props.to_type = node.nodetype
+ else:
+ for node in self.category.items(context):
+ props = layout.operator(NWSwitchNodeType.bl_idname, text=node.label)
+ props.geo_to_type = node.nodetype
#
# APPENDAGES TO EXISTING UI
@@ -4754,7 +5043,7 @@ def reset_nodes_button(self, context):
#
# REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
#
-
+switch_category_menus = []
addon_keymaps = []
# kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
# props entry: (property name, property value)
@@ -4916,8 +5205,8 @@ kmi_defs = (
(NWFrameSelected.bl_idname, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
# Swap Outputs
(NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
- # Emission Viewer
- (NWEmissionViewer.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
+ # Preview Node
+ (NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Preview node output"),
# Reload Images
(NWReloadImages.bl_idname, 'R', 'PRESS', False, False, True, None, "Reload images"),
# Lazy Mix
@@ -4951,7 +5240,7 @@ classes = (
NWSwapLinks,
NWResetBG,
NWAddAttrNode,
- NWEmissionViewer,
+ NWPreviewNode,
NWFrameSelected,
NWReloadImages,
NWSwitchNodeType,
@@ -4981,6 +5270,7 @@ classes = (
NodeWranglerMenu,
NWMergeNodesMenu,
NWMergeShadersMenu,
+ NWMergeGeometryMenu,
NWMergeMixMenu,
NWConnectionListOutputs,
NWConnectionListInputs,
@@ -5081,6 +5371,23 @@ def register():
bpy.types.NODE_PT_active_node_generic.prepend(reset_nodes_button)
bpy.types.NODE_MT_node.prepend(reset_nodes_button)
+ # switch submenus
+ switch_category_menus.clear()
+ for cat in node_categories_iter(None):
+ if cat.name not in ['Group', 'Script'] and cat.identifier.startswith('GEO'):
+ idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
+ switch_category_type = type(idname, (bpy.types.Menu,), {
+ "bl_space_type": 'NODE_EDITOR',
+ "bl_label": cat.name,
+ "category": cat,
+ "poll": cat.poll,
+ "draw": draw_switch_category_submenu,
+ })
+
+ switch_category_menus.append(switch_category_type)
+
+ bpy.utils.register_class(switch_category_type)
+
def unregister():
from bpy.utils import unregister_class
@@ -5092,6 +5399,10 @@ def unregister():
del bpy.types.Scene.NWSourceSocket
del bpy.types.NodeSocketInterface.NWViewerSocket
+ for cat_types in switch_category_menus:
+ bpy.utils.unregister_class(cat_types)
+ switch_category_menus.clear()
+
# keymaps
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)