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:
authorJacques Lucke <mail@jlucke.com>2021-06-01 15:45:37 +0300
committerJacques Lucke <mail@jlucke.com>2021-06-01 15:45:37 +0300
commitcdabac54c4fe7c6f8df125814442762aa539172b (patch)
tree34192f8fb0730fd999e693c21d9e0f7216c440a5
parentf21f6d34bae2c84f8e3edf8278f652b27db22a16 (diff)
parent26c4357e0bfaf2b9f148efdeab18b6447fd565d9 (diff)
Merge branch 'blender-v2.93-release'
-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)