Node Wrangler: apply autopep8 formatting #104444

Closed
Wannes Malfait wants to merge 1 commits from wannes.malfait/blender-addons:nw_apply_formatter into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Showing only changes of commit 8c17c196e5 - Show all commits

View File

@ -71,7 +71,7 @@ rl_outputs = (
RL_entry('use_pass_uv', 'UV', 'UV', True, True),
RL_entry('use_pass_vector', 'Speed', 'Vector', False, True),
RL_entry('use_pass_z', 'Z', 'Depth', True, True),
)
)
# list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
# used list, not tuple for easy merging with other lists.
@ -192,11 +192,13 @@ 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 get_first_enabled_output(node):
for output in node.outputs:
if output.enabled:
@ -204,9 +206,11 @@ def get_first_enabled_output(node):
else:
return node.outputs[0]
def is_visible_socket(socket):
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
def nice_hotkey_name(punc):
# convert the ugly string name into the actual character
nice_name = {
@ -329,12 +333,14 @@ def autolink(node1, node2, links):
print("Could not make a link from " + node1.name + " to " + node2.name)
return link_made
def abs_node_location(node):
abs_location = node.location
if node.parent is None:
return abs_location
return abs_location + abs_node_location(node.parent)
def node_at_pos(nodes, context, event):
nodes_under_mouse = []
target_node = None
@ -395,16 +401,17 @@ def store_mouse_cursor(context, event):
else:
space.cursor_location = tree.view_center
def draw_line(x1, y1, x2, y2, size, colour=(1.0, 1.0, 1.0, 0.7)):
shader = gpu.shader.from_builtin('POLYLINE_SMOOTH_COLOR')
shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
shader.uniform_float("lineWidth", size * prefs_line_width())
vertices = ((x1, y1), (x2, y2))
vertex_colors = ((colour[0]+(1.0-colour[0])/4,
colour[1]+(1.0-colour[1])/4,
colour[2]+(1.0-colour[2])/4,
colour[3]+(1.0-colour[3])/4),
vertex_colors = ((colour[0] + (1.0 - colour[0]) / 4,
colour[1] + (1.0 - colour[1]) / 4,
colour[2] + (1.0 - colour[2]) / 4,
colour[3] + (1.0 - colour[3]) / 4),
colour)
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices, "color": vertex_colors})
@ -431,8 +438,8 @@ def draw_rounded_node_border(node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
nlocx, nlocy = abs_node_location(node)
nlocx = (nlocx+1) * dpi_fac()
nlocy = (nlocy+1) * dpi_fac()
nlocx = (nlocx + 1) * dpi_fac()
nlocy = (nlocy + 1) * dpi_fac()
ndimx = node.dimensions.x
ndimy = node.dimensions.y
@ -451,52 +458,52 @@ def draw_rounded_node_border(node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
# Top left corner
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
vertices = [(mx,my)]
for i in range(sides+1):
if (4<=i<=8):
vertices = [(mx, my)]
for i in range(sides + 1):
if (4 <= i <= 8):
if mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
vertices.append((cosine, sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
batch.draw(shader)
# Top right corner
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
vertices = [(mx,my)]
for i in range(sides+1):
if (0<=i<=4):
vertices = [(mx, my)]
for i in range(sides + 1):
if (0 <= i <= 4):
if mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
vertices.append((cosine, sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
batch.draw(shader)
# Bottom left corner
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
vertices = [(mx,my)]
for i in range(sides+1):
if (8<=i<=12):
vertices = [(mx, my)]
for i in range(sides + 1):
if (8 <= i <= 12):
if mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
vertices.append((cosine, sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
batch.draw(shader)
# Bottom right corner
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
vertices = [(mx,my)]
for i in range(sides+1):
if (12<=i<=16):
vertices = [(mx, my)]
for i in range(sides + 1):
if (12 <= i <= 16):
if mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
vertices.append((cosine, sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
batch.draw(shader)
@ -510,10 +517,10 @@ def draw_rounded_node_border(node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
if m1x < area_width and m2x < area_width:
vertices.extend([(m2x-radius,m2y), (m2x,m2y),
(m1x,m1y), (m1x-radius,m1y)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
vertices.extend([(m2x - radius, m2y), (m2x, m2y),
(m1x, m1y), (m1x - radius, m1y)])
indices.extend([(id_last, id_last + 1, id_last + 3),
(id_last + 3, id_last + 1, id_last + 2)])
id_last += 4
# Top edge
@ -521,37 +528,38 @@ def draw_rounded_node_border(node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
m1x = min(m1x, area_width)
m2x = min(m2x, area_width)
vertices.extend([(m1x,m1y), (m2x,m1y),
(m2x,m1y+radius), (m1x,m1y+radius)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
vertices.extend([(m1x, m1y), (m2x, m1y),
(m2x, m1y + radius), (m1x, m1y + radius)])
indices.extend([(id_last, id_last + 1, id_last + 3),
(id_last + 3, id_last + 1, id_last + 2)])
id_last += 4
# Right edge
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
if m1x < area_width and m2x < area_width:
vertices.extend([(m1x,m2y), (m1x+radius,m2y),
(m1x+radius,m1y), (m1x,m1y)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
vertices.extend([(m1x, m2y), (m1x + radius, m2y),
(m1x + radius, m1y), (m1x, m1y)])
indices.extend([(id_last, id_last + 1, id_last + 3),
(id_last + 3, id_last + 1, id_last + 2)])
id_last += 4
# Bottom edge
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy-ndimy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy-ndimy, clip=False)
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
m1x = min(m1x, area_width)
m2x = min(m2x, area_width)
vertices.extend([(m1x,m2y), (m2x,m2y),
(m2x,m1y-radius), (m1x,m1y-radius)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
vertices.extend([(m1x, m2y), (m2x, m2y),
(m2x, m1y - radius), (m1x, m1y - radius)])
indices.extend([(id_last, id_last + 1, id_last + 3),
(id_last + 3, id_last + 1, id_last + 2)])
# now draw all edges in one batch
if len(vertices) != 0:
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
batch.draw(shader)
def draw_callback_nodeoutline(self, context, mode):
if self.mouse_path:
gpu.state.blend_set('ALPHA')
@ -602,6 +610,7 @@ def draw_callback_nodeoutline(self, context, mode):
gpu.state.blend_set('NONE')
def get_active_tree(context):
tree = context.space_data.node_tree
path = []
@ -616,16 +625,19 @@ def get_active_tree(context):
path.append(tree)
return tree, path
def get_nodes_links(context):
tree, path = get_active_tree(context)
return tree.nodes, tree.links
def is_viewer_socket(socket):
# checks if a internal socket is a valid viewer socket
return socket.name == viewer_socket_name and socket.NWViewerSocket
def get_internal_socket(socket):
#get the internal socket from a socket inside or outside the group
# get the internal socket from a socket inside or outside the group
node = socket.node
if node.type == 'GROUP_OUTPUT':
source_iterator = node.inputs
@ -648,6 +660,7 @@ def get_internal_socket(socket):
break
return iterator[i]
def is_viewer_link(link, output_node):
if link.to_node == output_node and link.to_socket == output_node.inputs[0]:
return True
@ -657,11 +670,13 @@ def is_viewer_link(link, output_node):
return True
return False
def get_group_output_node(tree):
for node in tree.nodes:
if node.type == 'GROUP_OUTPUT' and node.is_active_output == True:
if node.type == 'GROUP_OUTPUT' and node.is_active_output:
return node
def get_output_location(tree):
# get right-most location
sorted_by_xloc = (sorted(tree.nodes, key=lambda x: x.location.x))
@ -677,6 +692,8 @@ def get_output_location(tree):
return loc_x, loc_y
# Principled prefs
class NWPrincipledPreferences(bpy.types.PropertyGroup):
base_color: StringProperty(
name='Base Color',
@ -732,6 +749,8 @@ class NWPrincipledPreferences(bpy.types.PropertyGroup):
description='Naming Components for AO maps')
# Addon prefs
class NWNodeWrangler(bpy.types.AddonPreferences):
bl_idname = __name__
@ -779,7 +798,11 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
box = layout.box()
col = box.column(align=True)
col.prop(self, "show_principled_lists", text='Edit tags for auto texture detection in Principled BSDF setup', toggle=True)
col.prop(
self,
"show_principled_lists",
text='Edit tags for auto texture detection in Principled BSDF setup',
toggle=True)
if self.show_principled_lists:
tags = self.principled_tags
@ -823,7 +846,6 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
row.label(text=keystr)
def nw_check(context):
space = context.space_data
valid_trees = ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree", "GeometryNodeTree"]
@ -836,6 +858,7 @@ def nw_check(context):
return False
class NWBase:
@classmethod
def poll(cls, context):
@ -908,7 +931,8 @@ class NWLazyMix(Operator, NWBase):
args = (self, context, 'MIX')
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(
draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
self.mouse_path = []
@ -966,7 +990,7 @@ class NWLazyConnect(Operator, NWBase):
original_sel = []
original_unsel = []
for node in nodes:
if node.select == True:
if node.select:
node.select = False
original_sel.append(node)
else:
@ -1013,7 +1037,8 @@ class NWLazyConnect(Operator, NWBase):
args = (self, context, mode)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(
draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
self.mouse_path = []
@ -1030,12 +1055,18 @@ class NWDeleteUnused(Operator, NWBase):
bl_label = 'Delete Unused Nodes'
bl_options = {'REGISTER', 'UNDO'}
delete_muted: BoolProperty(name="Delete Muted", description="Delete (but reconnect, like Ctrl-X) all muted nodes", default=True)
delete_frames: BoolProperty(name="Delete Empty Frames", description="Delete all frames that have no nodes inside them", default=True)
delete_muted: BoolProperty(
name="Delete Muted",
description="Delete (but reconnect, like Ctrl-X) all muted nodes",
default=True)
delete_frames: BoolProperty(
name="Delete Empty Frames",
description="Delete all frames that have no nodes inside them",
default=True)
def is_unused_node(self, node):
end_types = ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
end_types = ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE',
'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT',
'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
if node.type in end_types:
return False
@ -1059,7 +1090,7 @@ class NWDeleteUnused(Operator, NWBase):
# Store selection
selection = []
for node in nodes:
if node.select == True:
if node.select:
selection.append(node.name)
for node in nodes:
@ -1176,12 +1207,14 @@ class NWSwapLinks(Operator, NWBase):
try:
links.new(n2.outputs[connection[0]], connection[1])
except:
self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
self.report({'WARNING'},
"Some connections have been lost due to differing numbers of output sockets")
for connection in n2_outputs:
try:
links.new(n1.outputs[connection[0]], connection[1])
except:
self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
self.report({'WARNING'},
"Some connections have been lost due to differing numbers of output sockets")
else:
if n1.outputs or n2.outputs:
self.report({'WARNING'}, "One of the nodes has no outputs!")
@ -1195,14 +1228,14 @@ class NWSwapLinks(Operator, NWBase):
return {'FINISHED'}
if n1.inputs:
types = []
i=0
i = 0
for i1 in n1.inputs:
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 and not i2.is_multi_input:
similar_types += 1
types.append ([i1, similar_types, i])
types.append([i1, similar_types, i])
i += 1
types.sort(key=lambda k: k[1], reverse=True)
@ -1223,7 +1256,7 @@ class NWSwapLinks(Operator, NWBase):
fs = t[0].links[0].from_socket
i = t[2]
links.remove(t[0].links[0])
if i+1 == len(n1.inputs):
if i + 1 == len(n1.inputs):
i = -1
i += 1
while n1.inputs[i].is_linked:
@ -1281,6 +1314,7 @@ class NWAddAttrNode(Operator, NWBase):
nodes.active.attribute_name = self.attr_name
return {'FINISHED'}
class NWPreviewNode(Operator, NWBase):
bl_idname = "node.nw_preview_node"
bl_label = "Preview Node"
@ -1308,24 +1342,24 @@ class NWPreviewNode(Operator, NWBase):
return False
def ensure_viewer_socket(self, node, socket_type, connect_socket=None):
#check if a viewer output already exists in a node group otherwise create
# check if a viewer output already exists in a node group otherwise create
if hasattr(node, "node_tree"):
index = None
if len(node.node_tree.outputs):
free_socket = None
for i, socket in enumerate(node.node_tree.outputs):
if is_viewer_socket(socket) and is_visible_socket(node.outputs[i]) and socket.type == socket_type:
#if viewer output is already used but leads to the same socket we can still use it
# if viewer output is already used but leads to the same socket we can still use it
is_used = self.is_socket_used_other_mats(socket)
if is_used:
if connect_socket == None:
if connect_socket is None:
continue
groupout = get_group_output_node(node.node_tree)
groupout_input = groupout.inputs[i]
links = groupout_input.links
if connect_socket not in [link.from_socket for link in links]:
continue
index=i
index = i
break
if not free_socket:
free_socket = i
@ -1333,7 +1367,7 @@ class NWPreviewNode(Operator, NWBase):
index = free_socket
if not index:
#create viewer socket
# create viewer socket
node.node_tree.outputs.new(socket_type, viewer_socket_name)
index = len(node.node_tree.outputs) - 1
node.node_tree.outputs[index].NWViewerSocket = True
@ -1354,12 +1388,12 @@ class NWPreviewNode(Operator, NWBase):
def get_shader_output_node(self, tree):
for node in tree.nodes:
if node.type == self.shader_output_type and node.is_active_output == True:
if node.type == self.shader_output_type and node.is_active_output:
return node
@classmethod
def ensure_group_output(cls, tree):
#check if a group output node exists otherwise create
# check if a group output node exists otherwise create
groupout = get_group_output_node(tree)
if not groupout:
groupout = tree.nodes.new('NodeGroupOutput')
@ -1388,7 +1422,7 @@ class NWPreviewNode(Operator, NWBase):
socket = next_node.node_tree.outputs[socket_index]
if is_viewer_socket(socket) and socket not in sockets:
sockets.append(socket)
#continue search inside of node group but restrict socket to where we came from
# continue search inside of node group but restrict socket to where we came from
groupout = get_group_output_node(next_node.node_tree)
cls.search_sockets(groupout, sockets, index=socket_index)
@ -1405,12 +1439,12 @@ class NWPreviewNode(Operator, NWBase):
cls.scan_nodes(node.node_tree, sockets)
def link_leads_to_used_socket(self, link):
#return True if link leads to a socket that is already used in this material
# return True if link leads to a socket that is already used in this material
socket = get_internal_socket(link.to_socket)
return (socket and self.is_socket_used_active_mat(socket))
def is_socket_used_active_mat(self, socket):
#ensure used sockets in active material is calculated and check given socket
# ensure used sockets in active material is calculated and check given socket
if not hasattr(self, "used_viewer_sockets_active_mat"):
self.used_viewer_sockets_active_mat = []
materialout = self.get_shader_output_node(bpy.context.space_data.node_tree)
@ -1419,7 +1453,7 @@ class NWPreviewNode(Operator, NWBase):
return socket in self.used_viewer_sockets_active_mat
def is_socket_used_other_mats(self, socket):
#ensure used sockets in other materials are calculated and check given socket
# ensure used sockets in other materials are calculated and check given socket
if not hasattr(self, "used_viewer_sockets_other_mats"):
self.used_viewer_sockets_other_mats = []
for mat in bpy.data.materials:
@ -1493,7 +1527,7 @@ class NWPreviewNode(Operator, NWBase):
socket_type = 'GEOMETRY'
# Find an input socket of the output of type geometry
geometryoutindex = None
for i,inp in enumerate(geometryoutput.inputs):
for i, inp in enumerate(geometryoutput.inputs):
if inp.type == socket_type:
geometryoutindex = i
break
@ -1510,7 +1544,8 @@ class NWPreviewNode(Operator, NWBase):
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)
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:
@ -1531,7 +1566,6 @@ class NWPreviewNode(Operator, NWBase):
force_update(context)
return {'FINISHED'}
# What follows is code for the shader editor
output_types = [x.nodetype for x in
get_nodes_from_category('Output', context)]
@ -1547,7 +1581,7 @@ class NWPreviewNode(Operator, NWBase):
materialout = None # placeholder node
delete_sockets = []
#scan through all nodes in tree including nodes inside of groups to find viewer sockets
# scan through all nodes in tree including nodes inside of groups to find viewer sockets
self.scan_nodes(base_node_tree, delete_sockets)
materialout = self.get_shader_output_node(base_node_tree)
@ -1586,7 +1620,8 @@ class NWPreviewNode(Operator, NWBase):
link_end = output_socket
while tree.nodes.active != active:
node = tree.nodes.active
index = self.ensure_viewer_socket(node, socket_type, connect_socket=active.outputs[out_i] if node.node_tree.nodes.active == active else None)
index = self.ensure_viewer_socket(
node, socket_type, 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:
@ -1649,7 +1684,7 @@ class NWFrameSelected(Operator, NWBase):
nodes, links = get_nodes_links(context)
selected = []
for node in nodes:
if node.select == True:
if node.select:
selected.append(node)
bpy.ops.node.add_node(type='NodeFrame')
@ -1715,7 +1750,7 @@ class NWSwitchNodeType(Operator, NWBase):
to_type: StringProperty(
name="Switch to type",
default = '',
default='',
)
def execute(self, context):
@ -1783,8 +1818,10 @@ class NWSwitchNodeType(Operator, NWBase):
for the_type in types_order_one:
if socket.type == the_type:
# create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
# entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
sockets[in_out_name][the_type].append((len(sockets[in_out_name][the_type]), i, the_name, dval, socket_links))
# entry structure: (index_in_type, socket_index, socket_name,
# socket_default_value, socket_links)
sockets[in_out_name][the_type].append(
(len(sockets[in_out_name][the_type]), i, the_name, dval, socket_links))
# Check which of the types in inputs/outputs is considered to be "main".
# Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
for type_check in types_order_one:
@ -1903,7 +1940,7 @@ class NWMergeNodes(Operator, NWBase):
mode: EnumProperty(
name="mode",
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],
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",
@ -1925,7 +1962,7 @@ class NWMergeNodes(Operator, NWBase):
# 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:
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.
@ -1939,7 +1976,7 @@ class NWMergeNodes(Operator, NWBase):
for output in node.outputs:
if output.is_linked:
for olink in output.links:
if NWMergeNodes.link_creates_cycle(olink, selected_nodes, depth+1):
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
@ -1949,14 +1986,14 @@ class NWMergeNodes(Operator, NWBase):
# 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):
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):
for i in range(len(nodes_list) - 1):
loc_y += nodes_list[i][2]
loc_y = loc_y/len(nodes_list)
loc_y = loc_y / len(nodes_list)
new_node = nodes.new(node_name)
new_node.hide = do_hide
new_node.location.x = loc_x
@ -1964,12 +2001,14 @@ class NWMergeNodes(Operator, NWBase):
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):
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)]
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.
@ -2081,7 +2120,14 @@ 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_geometry, selected_math, selected_vector, selected_z, selected_alphaover]:
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)
@ -2100,8 +2146,9 @@ class NWMergeNodes(Operator, NWBase):
else:
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
# average yloc of last two nodes (lowest two)
loc_y = ((nodes_list[len(nodes_list) - 1][2]) + (nodes_list[len(nodes_list) - 2][2])) / 2
if nodes_list[len(nodes_list) - 1][-1]: # if last node is hidden, mix should be shifted up a bit
if do_hide:
loc_y += 40
else:
@ -2171,11 +2218,13 @@ class NWMergeNodes(Operator, NWBase):
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])
add = self.merge_with_multi_input(
nodes_list, merge_position, do_hide, loc_x, links, nodes, add_type, [0])
else:
add_type = node_type + 'MeshBoolean'
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)
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
@ -2221,7 +2270,8 @@ class NWMergeNodes(Operator, NWBase):
# "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)]
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.
@ -2299,7 +2349,7 @@ class NWBatchChangeNodes(Operator, NWBase):
operation = self.operation
for node in context.selected_nodes:
if node.type == 'MIX_RGB' or (node.bl_idname == 'ShaderNodeMix' and node.data_type == 'RGBA'):
if not blend_type in [nav[0] for nav in navs]:
if blend_type not in [nav[0] for nav in navs]:
node.blend_type = blend_type
else:
if blend_type == 'NEXT':
@ -2318,7 +2368,7 @@ class NWBatchChangeNodes(Operator, NWBase):
node.blend_type = blend_types[index - 1][0]
if node.type == 'MATH' or node.bl_idname == 'ShaderNodeMath':
if not operation in [nav[0] for nav in navs]:
if operation not in [nav[0] for nav in navs]:
node.operation = operation
else:
if operation == 'NEXT':
@ -2414,7 +2464,11 @@ class NWCopySettings(Operator, NWBase):
# Report nodes that are not valid
valid_node_names = [n.name for n in valid_nodes]
not_valid_names = list(set(selected_node_names) - set(valid_node_names))
self.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names), node_active.name))
self.report(
{'INFO'},
"Ignored {} (not of the same type as {})".format(
", ".join(not_valid_names),
node_active.name))
# Reference original
orig = node_active
@ -2479,7 +2533,11 @@ class NWCopySettings(Operator, NWBase):
orig.select = True
node_tree.nodes.active = orig
self.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig.name, ", ".join(success_names)))
self.report(
{'INFO'},
"Successfully copied attributes from {} to: {}".format(
orig.name,
", ".join(success_names)))
return {'FINISHED'}
@ -2587,7 +2645,10 @@ class NWAddTextureSetup(Operator, NWBase):
bl_description = "Add Texture Node Setup to Selected Shaders"
bl_options = {'REGISTER', 'UNDO'}
add_mapping: BoolProperty(name="Add Mapping Nodes", description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default=True)
add_mapping: BoolProperty(
name="Add Mapping Nodes",
description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)",
default=True)
@classmethod
def poll(cls, context):
@ -2744,7 +2805,7 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
match_files_to_socket_names(self.files, socketnames)
# Remove socketnames without found files
socketnames = [s for s in socketnames if s[2]
and path.exists(self.directory+s[2])]
and path.exists(self.directory + s[2])]
if not socketnames:
self.report({'INFO'}, 'No matching images found')
print('No matching images found')
@ -2891,7 +2952,8 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
# If more than one texture add reroute node in between
reroute = nodes.new(type='NodeReroute')
texture_nodes.append(reroute)
tex_coords = Vector((texture_nodes[0].location.x, sum(n.location.y for n in texture_nodes)/len(texture_nodes)))
tex_coords = Vector((texture_nodes[0].location.x,
sum(n.location.y for n in texture_nodes) / len(texture_nodes)))
reroute.location = tex_coords + Vector((-50, -120))
for texture_node in texture_nodes:
link = links.new(texture_node.inputs[0], reroute.outputs[0])
@ -3131,7 +3193,8 @@ class NWAlignNodes(Operator, NWBase):
active_loc = copy(nodes.active.location) # make a copy, not a reference
# Check if nodes should be laid out horizontally or vertically
x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection] # use dimension to get center of node, not corner
# use dimension to get center of node, not corner
x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection]
y_locs = [n.location.y - (n.dimensions.y / 2) for n in selection]
x_range = max(x_locs) - min(x_locs)
y_range = max(y_locs) - min(y_locs)
@ -3166,7 +3229,8 @@ class NWAlignNodes(Operator, NWBase):
for node in selection:
node.location += active_loc_diff
else: # Position nodes centered around where they used to be
locs = ([n.location.x + (n.dimensions.x / 2) for n in selection]) if horizontal else ([n.location.y - (n.dimensions.y / 2) for n in selection])
locs = ([n.location.x + (n.dimensions.x / 2) for n in selection]
) if horizontal else ([n.location.y - (n.dimensions.y / 2) for n in selection])
new_mid = (max(locs) + min(locs)) / 2
for node in selection:
if horizontal:
@ -3394,22 +3458,21 @@ class NWAddSequence(Operator, NWBase, ImportHelper):
if not files[0].name and not filename:
self.report({'ERROR'}, "No file chosen")
return {'CANCELLED'}
elif files[0].name and (not filename or not path.exists(directory+filename)):
elif files[0].name and (not filename or not path.exists(directory + filename)):
# User has selected multiple files without an active one, or the active one is non-existent
filename = files[0].name
if not path.exists(directory+filename):
self.report({'ERROR'}, filename+" does not exist!")
if not path.exists(directory + filename):
self.report({'ERROR'}, filename + " does not exist!")
return {'CANCELLED'}
without_ext = '.'.join(filename.split('.')[:-1])
# if last digit isn't a number, it's not a sequence
if not without_ext[-1].isdigit():
self.report({'ERROR'}, filename+" does not seem to be part of a sequence")
self.report({'ERROR'}, filename + " does not seem to be part of a sequence")
return {'CANCELLED'}
extension = filename.split('.')[-1]
reverse = without_ext[::-1] # reverse string
@ -3420,9 +3483,9 @@ class NWAddSequence(Operator, NWBase, ImportHelper):
else:
break
without_num = without_ext[:count_numbers*-1]
without_num = without_ext[:count_numbers * -1]
files = sorted(glob(directory + without_num + "[0-9]"*count_numbers + "." + extension))
files = sorted(glob(directory + without_num + "[0-9]" * count_numbers + "." + extension))
num_frames = len(files)
@ -3434,18 +3497,18 @@ class NWAddSequence(Operator, NWBase, ImportHelper):
for node in nodes:
node.select = False
yloc += node_mid_pt(node, 'y')
yloc = yloc/len(nodes)
yloc = yloc / len(nodes)
else:
xloc = 0
yloc = 0
name_with_hashes = without_num + "#"*count_numbers + '.' + extension
name_with_hashes = without_num + "#" * count_numbers + '.' + extension
bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type=node_type)
node = nodes.active
node.label = name_with_hashes
filepath = directory+(without_ext+'.'+extension)
filepath = directory + (without_ext + '.' + extension)
if self.relative_path:
if bpy.data.filepath:
try:
@ -3458,7 +3521,8 @@ class NWAddSequence(Operator, NWBase, ImportHelper):
img.name = name_with_hashes
node.image = img
image_user = node.image_user if tree.type == 'SHADER' else node
image_user.frame_offset = int(files[0][len(without_num)+len(directory):-1*(len(extension)+1)]) - 1 # separate the number from the file name of the first file
# separate the number from the file name of the first file
image_user.frame_offset = int(files[0][len(without_num) + len(directory):-1 * (len(extension) + 1)]) - 1
image_user.frame_duration = num_frames
return {'FINISHED'}
@ -3480,7 +3544,7 @@ class NWAddMultipleImages(Operator, NWBase, ImportHelper):
def execute(self, context):
nodes, links = get_nodes_links(context)
xloc, yloc = context.region.view2d.region_to_view(context.area.width/2, context.area.height/2)
xloc, yloc = context.region.view2d.region_to_view(context.area.width / 2, context.area.height / 2)
if context.space_data.node_tree.type == 'SHADER':
node_type = "ShaderNodeTexImage"
@ -3503,7 +3567,7 @@ class NWAddMultipleImages(Operator, NWBase, ImportHelper):
node.location.y = yloc
yloc -= 40
img = bpy.data.images.load(self.directory+fname)
img = bpy.data.images.load(self.directory + fname)
node.image = img
# shift new nodes up to center of tree
@ -3511,7 +3575,7 @@ class NWAddMultipleImages(Operator, NWBase, ImportHelper):
for node in nodes:
if node in new_nodes:
node.select = True
node.location.y += (list_size/2)
node.location.y += (list_size / 2)
else:
node.select = False
return {'FINISHED'}
@ -3535,7 +3599,7 @@ class NWViewerFocus(bpy.types.Operator):
def invoke(self, context, event):
render = context.scene.render
space = context.space_data
percent = render.resolution_percentage*0.01
percent = render.resolution_percentage * 0.01
nodes, links = get_nodes_links(context)
viewers = [n for n in nodes if n.type == 'VIEWER']
@ -3545,7 +3609,7 @@ class NWViewerFocus(bpy.types.Operator):
mlocy = event.mouse_region_y
select_node = bpy.ops.node.select(location=(mlocx, mlocy), extend=False)
if not 'FINISHED' in select_node: # only run if we're not clicking on a node
if 'FINISHED' not in select_node: # only run if we're not clicking on a node
region_x = context.region.width
region_y = context.region.height
@ -3647,7 +3711,7 @@ class NWResetNodes(bpy.types.Operator):
def execute(self, context):
node_active = context.active_node
node_selected = context.selected_nodes
node_ignore = ["FRAME","REROUTE", "GROUP"]
node_ignore = ["FRAME", "REROUTE", "GROUP"]
# Check if one node is selected at least
if not (len(node_selected) > 0):
@ -3859,9 +3923,11 @@ class NWMergeNodesMenu(Menu, NWBase):
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
@ -3870,6 +3936,7 @@ class NWMergeGeometryMenu(Menu, NWBase):
props.mode = type
props.merge_type = 'GEOMETRY'
class NWMergeShadersMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_shaders_menu"
bl_label = "Merge Selected Nodes using Shaders"
@ -3906,7 +3973,7 @@ class NWConnectionListOutputs(Menu, NWBase):
for index, output in enumerate(n1.outputs):
# Only show sockets that are exposed.
if output.enabled:
layout.operator(NWCallInputsMenu.bl_idname, text=output.name, icon="RADIOBUT_OFF").from_socket=index
layout.operator(NWCallInputsMenu.bl_idname, text=output.name, icon="RADIOBUT_OFF").from_socket = index
class NWConnectionListInputs(Menu, NWBase):
@ -4176,7 +4243,7 @@ def save_viewer_menu_func(self, context):
def reset_nodes_button(self, context):
node_active = context.active_node
node_selected = context.selected_nodes
node_ignore = ["FRAME","REROUTE", "GROUP"]
node_ignore = ["FRAME", "REROUTE", "GROUP"]
# Check if active node is in the selection and respective type
if (len(node_selected) == 1) and node_active and node_active.select and node_active.type not in node_ignore:
@ -4318,10 +4385,14 @@ kmi_defs = (
(NWLinkActiveToSelected.bl_idname, 'SEMI_COLON', 'PRESS', False, True, False,
(('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
# CHANGE MIX FACTOR
(NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
(NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
(NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
(NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
(NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False,
False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
(NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False,
False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
(NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False,
True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
(NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False,
True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
(NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
(NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
(NWChangeMixFactor.bl_idname, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
@ -4333,16 +4404,19 @@ kmi_defs = (
# MODIFY LABEL (Alt Shift L)
(NWModifyLabels.bl_idname, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
# Copy Label from active to selected
(NWCopyLabel.bl_idname, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
(NWCopyLabel.bl_idname, 'V', 'PRESS', False, True, False,
(('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
# DETACH OUTPUTS (Alt Shift D)
(NWDetachOutputs.bl_idname, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
# LINK TO OUTPUT NODE (O)
(NWLinkToOutputNode.bl_idname, 'O', 'PRESS', False, False, False, None, "Link to output node"),
# SELECT PARENT/CHILDREN
# Select Children
(NWSelectParentChildren.bl_idname, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
(NWSelectParentChildren.bl_idname, 'RIGHT_BRACKET', 'PRESS',
False, False, False, (('option', 'CHILD'),), "Select children"),
# Select Parent
(NWSelectParentChildren.bl_idname, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
(NWSelectParentChildren.bl_idname, 'LEFT_BRACKET', 'PRESS',
False, False, False, (('option', 'PARENT'),), "Select Parent"),
# Add Texture Setup
(NWAddTextureSetup.bl_idname, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
# Add Principled BSDF Texture Setup
@ -4356,8 +4430,10 @@ kmi_defs = (
# Swap Links
(NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Links"),
# Preview Node
(NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False, (('run_in_geometry_nodes', False),), "Preview node output"),
(NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', False, True, True, (('run_in_geometry_nodes', True),), "Preview node output"),
(NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False,
(('run_in_geometry_nodes', False),), "Preview node output"),
(NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', False, True,
True, (('run_in_geometry_nodes', True),), "Preview node output"),
# Reload Images
(NWReloadImages.bl_idname, 'R', 'PRESS', False, False, True, None, "Reload images"),
# Lazy Mix
@ -4365,18 +4441,22 @@ kmi_defs = (
# Lazy Connect
(NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', False, False, True, (('with_menu', False),), "Lazy Connect"),
# Lazy Connect with Menu
(NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', False, True, True, (('with_menu', True),), "Lazy Connect with Socket Menu"),
(NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', False, True,
True, (('with_menu', True),), "Lazy Connect with Socket Menu"),
# Viewer Tile Center
(NWViewerFocus.bl_idname, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
# Align Nodes
(NWAlignNodes.bl_idname, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
# Reset Nodes (Back Space)
(NWResetNodes.bl_idname, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
(NWResetNodes.bl_idname, 'BACK_SPACE', 'PRESS', False, False, False,
None, "Revert node back to default state, but keep connections"),
# MENUS
('wm.call_menu', 'W', 'PRESS', False, True, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wrangler menu"),
('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False,
(('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False,
(('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"),
('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu.bl_idname),), "Switch node type menu"),
)
@ -4440,6 +4520,7 @@ classes = (
NWSwitchNodeTypeMenu,
)
def register():
from bpy.utils import register_class
@ -4541,5 +4622,6 @@ def unregister():
for cls in classes:
unregister_class(cls)
if __name__ == "__main__":
register()