Newer
Older
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
y = node.location.y
width = node.width
# unhide 'REROUTE' nodes to avoid issues with location.y
if node.type == 'REROUTE':
node.hide = False
# When node is hidden - width_hidden not usable.
# Hack needed to calculate real width
if node.hide:
bpy.ops.node.select_all(action='DESELECT')
helper = nodes.new('NodeReroute')
helper.select = True
node.select = True
# resize node and helper to zero. Then check locations to calculate width
bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
width = 2.0 * (helper.location.x - node.location.x)
# restore node location
node.location = x, y
# delete helper
node.select = False
# only helper is selected now
bpy.ops.node.delete()
x = node.location.x + width + 20.0
if node.type != 'REROUTE':
y -= 35.0
y_offset = -22.0
loc = x, y
reroutes_count = 0 # will be used when aligning reroutes added to hidden nodes
for out_i, output in enumerate(node.outputs):
pass_used = False # initial value to be analyzed if 'R_LAYERS'
# if node != 'R_LAYERS' - "pass_used" not needed, so set it to True
if node.type != 'R_LAYERS':
pass_used = True
else: # if 'R_LAYERS' check if output represent used render pass
node_scene = node.scene
node_layer = node.layer
# If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
if output.name == 'Alpha':
pass_used = True
else:
# check entries in global 'rl_outputs' variable
for rlo in rl_outputs:
if output.name in {rlo.output_name, rlo.exr_output_name}:
pass_used = getattr(node_scene.view_layers[node_layer], rlo.render_pass)
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
break
if pass_used:
valid = ((option == 'ALL') or
(option == 'LOOSE' and not output.links) or
(option == 'LINKED' and output.links))
# Add reroutes only if valid, but offset location in all cases.
if valid:
n = nodes.new('NodeReroute')
nodes.active = n
for link in output.links:
links.new(n.outputs[0], link.to_socket)
links.new(output, n.inputs[0])
n.location = loc
post_select.append(n)
reroutes_count += 1
y += y_offset
loc = x, y
# disselect the node so that after execution of script only newly created nodes are selected
node.select = False
# nicer reroutes distribution along y when node.hide
if node.hide:
y_translate = reroutes_count * y_offset / 2.0 - y_offset - 35.0
for reroute in [r for r in nodes if r.select]:
reroute.location.y -= y_translate
for node in post_select:
node.select = True
return {'FINISHED'}
class NWLinkActiveToSelected(Operator, NWBase):
"""Link active node to selected nodes basing on various criteria"""
bl_idname = "node.nw_link_active_to_selected"
bl_label = "Link Active Node to Selected"
bl_options = {'REGISTER', 'UNDO'}
replace: BoolProperty()
use_node_name: BoolProperty()
use_outputs_names: BoolProperty()
@classmethod
def poll(cls, context):
valid = False
if nw_check(context):
if context.active_node is not None:
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
if context.active_node.select:
valid = True
return valid
def execute(self, context):
nodes, links = get_nodes_links(context)
replace = self.replace
use_node_name = self.use_node_name
use_outputs_names = self.use_outputs_names
active = nodes.active
selected = [node for node in nodes if node.select and node != active]
outputs = [] # Only usable outputs of active nodes will be stored here.
for out in active.outputs:
if active.type != 'R_LAYERS':
outputs.append(out)
else:
# 'R_LAYERS' node type needs special handling.
# outputs of 'R_LAYERS' are callable even if not seen in UI.
# Only outputs that represent used passes should be taken into account
# Check if pass represented by output is used.
# global 'rl_outputs' list will be used for that
pass_used = False # initial value. Will be set to True if pass is used
if out.name == 'Alpha':
# Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
pass_used = True
elif out.name in {rlo.output_name, rlo.exr_output_name}:
# example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
pass_used = getattr(active.scene.view_layers[active.layer], rlo.render_pass)
break
if pass_used:
outputs.append(out)
doit = True # Will be changed to False when links successfully added to previous output.
for out in outputs:
if doit:
for node in selected:
dst_name = node.name # Will be compared with src_name if needed.
# When node has label - use it as dst_name
if node.label:
dst_name = node.label
valid = True # Initial value. Will be changed to False if names don't match.
src_name = dst_name # If names not used - this asignment will keep valid = True.
if use_node_name:
# Set src_name to source node name or label
src_name = active.name
if active.label:
src_name = active.label
elif use_outputs_names:
src_name = (out.name, )
for rlo in rl_outputs:
if out.name in {rlo.output_name, rlo.exr_output_name}:
src_name = (rlo.output_name, rlo.exr_output_name)
if dst_name not in src_name:
valid = False
if valid:
for input in node.inputs:
if input.type == out.type or node.type == 'REROUTE':
if replace or not input.is_linked:
links.new(out, input)
if not use_node_name and not use_outputs_names:
doit = False
break
return {'FINISHED'}
class NWAlignNodes(Operator, NWBase):
'''Align the selected nodes neatly in a row/column'''
bl_idname = "node.nw_align_nodes"
bl_options = {'REGISTER', 'UNDO'}
margin: IntProperty(name='Margin', default=50, description='The amount of space between nodes')
def execute(self, context):
nodes, links = get_nodes_links(context)
selection = []
for node in nodes:
if node.select and node.type != 'FRAME':
selection.append(node)
# If no nodes are selected, align all nodes
active_loc = None
if not selection:
selection = nodes
elif nodes.active in selection:
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
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)
mid_x = (max(x_locs) + min(x_locs)) / 2
mid_y = (max(y_locs) + min(y_locs)) / 2
horizontal = x_range > y_range
# Sort selection by location of node mid-point
if horizontal:
selection = sorted(selection, key=lambda n: n.location.x + (n.dimensions.x / 2))
else:
selection = sorted(selection, key=lambda n: n.location.y - (n.dimensions.y / 2), reverse=True)
# Alignment
current_pos = 0
for node in selection:
current_margin = margin
current_margin = current_margin * 0.5 if node.hide else current_margin # use a smaller margin for hidden nodes
if horizontal:
node.location.x = current_pos
current_pos += current_margin + node.dimensions.x
node.location.y = mid_y + (node.dimensions.y / 2)
else:
node.location.y = current_pos
current_pos -= (current_margin * 0.3) + node.dimensions.y # use half-margin for vertical alignment
node.location.x = mid_x - (node.dimensions.x / 2)
# If active node is selected, center nodes around it
if active_loc is not None:
active_loc_diff = active_loc - nodes.active.location
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])
new_mid = (max(locs) + min(locs)) / 2
for node in selection:
if horizontal:
node.location.x += (mid_x - new_mid)
else:
node.location.y += (mid_y - new_mid)
return {'FINISHED'}
class NWSelectParentChildren(Operator, NWBase):
bl_idname = "node.nw_select_parent_child"
bl_label = "Select Parent or Children"
bl_options = {'REGISTER', 'UNDO'}
name="option",
items=(
('PARENT', 'Select Parent', 'Select Parent Frame'),
('CHILD', 'Select Children', 'Select members of selected frame'),
)
)
def execute(self, context):
nodes, links = get_nodes_links(context)
option = self.option
selected = [node for node in nodes if node.select]
if option == 'PARENT':
for sel in selected:
parent = sel.parent
if parent:
parent.select = True
else: # option == 'CHILD'
for sel in selected:
children = [node for node in nodes if node.parent == sel]
for kid in children:
kid.select = True
return {'FINISHED'}
class NWDetachOutputs(Operator, NWBase):
"""Detach outputs of selected node leaving inputs linked"""
bl_idname = "node.nw_detach_outputs"
def execute(self, context):
nodes, links = get_nodes_links(context)
selected = context.selected_nodes
bpy.ops.node.duplicate_move_keep_inputs()
new_nodes = context.selected_nodes
bpy.ops.node.select_all(action="DESELECT")
for node in selected:
node.select = True
bpy.ops.node.delete_reconnect()
for new_node in new_nodes:
new_node.select = True
Bartek Skorupa
committed
bpy.ops.transform.translate('INVOKE_DEFAULT')
Bartek Skorupa
committed
class NWLinkToOutputNode(Operator, NWBase):
"""Link to Composite node or Material Output node"""
bl_idname = "node.nw_link_out"
bl_label = "Connect to Output"
valid = False
if nw_check(context):
if context.active_node is not None:
for out in context.active_node.outputs:
if is_visible_socket(out):
valid = True
break
def execute(self, context):
nodes, links = get_nodes_links(context)
active = nodes.active
output_node = None
output_index = None
tree_type = context.space_data.tree_type
output_types_shaders = [x[1] for x in shaders_output_nodes_props]
output_types_compo = ['COMPOSITE']
output_types_blender_mat = ['OUTPUT']
output_types_textures = ['OUTPUT']
output_types = output_types_shaders + output_types_compo + output_types_blender_mat
if node.type in output_types:
output_node = node
break
if not output_node:
bpy.ops.node.select_all(action="DESELECT")
if tree_type == 'ShaderNodeTree':
Brecht Van Lommel
committed
output_node = nodes.new('ShaderNodeOutputMaterial')
elif tree_type == 'CompositorNodeTree':
output_node = nodes.new('CompositorNodeComposite')
elif tree_type == 'TextureNodeTree':
output_node = nodes.new('TextureNodeOutput')
output_node.location.x = active.location.x + active.dimensions.x + 80
output_node.location.y = active.location.y
if (output_node and active.outputs):
for i, output in enumerate(active.outputs):
if is_visible_socket(output):
output_index = i
break
for i, output in enumerate(active.outputs):
if output.type == output_node.inputs[0].type and is_visible_socket(output):
out_input_index = 0
Brecht Van Lommel
committed
if tree_type == 'ShaderNodeTree':
if active.outputs[output_index].name == 'Volume':
out_input_index = 1
elif active.outputs[output_index].type != 'SHADER': # connect to displacement if not a shader
out_input_index = 2
links.new(active.outputs[output_index], output_node.inputs[out_input_index])
force_update(context) # viewport render does not update
class NWMakeLink(Operator, NWBase):
"""Make a link from one socket to another"""
bl_idname = 'node.nw_make_link'
bl_label = 'Make Link'
bl_options = {'REGISTER', 'UNDO'}
from_socket: IntProperty()
to_socket: IntProperty()
def execute(self, context):
nodes, links = get_nodes_links(context)
n1 = nodes[context.scene.NWLazySource]
n2 = nodes[context.scene.NWLazyTarget]
links.new(n1.outputs[self.from_socket], n2.inputs[self.to_socket])
return {'FINISHED'}
class NWCallInputsMenu(Operator, NWBase):
"""Link from this output"""
bl_idname = 'node.nw_call_inputs_menu'
bl_label = 'Make Link'
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
nodes, links = get_nodes_links(context)
context.scene.NWSourceSocket = self.from_socket
n1 = nodes[context.scene.NWLazySource]
n2 = nodes[context.scene.NWLazyTarget]
if len(n2.inputs) > 1:
bpy.ops.wm.call_menu("INVOKE_DEFAULT", name=NWConnectionListInputs.bl_idname)
elif len(n2.inputs) == 1:
links.new(n1.outputs[self.from_socket], n2.inputs[0])
return {'FINISHED'}
class NWAddSequence(Operator, NWBase, ImportHelper):
"""Add an Image Sequence"""
bl_idname = 'node.nw_add_sequence'
bl_label = 'Import Image Sequence'
bl_options = {'REGISTER', 'UNDO'}
directory: StringProperty(
subtype="DIR_PATH"
)
filename: StringProperty(
subtype="FILE_NAME"
)
files: CollectionProperty(
type=bpy.types.OperatorFileListElement,
options={'HIDDEN', 'SKIP_SAVE'}
)
def execute(self, context):
nodes, links = get_nodes_links(context)
directory = self.directory
filename = self.filename
# DEBUG
# print ("\nDIR:", directory)
# print ("FN:", filename)
# print ("Fs:", list(f.name for f in files), '\n')
node_type = "CompositorNodeImage"
else:
self.report({'ERROR'}, "Unsupported Node Tree type!")
return {'CANCELLED'}
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)):
# User has selected multiple files without an active one, or the active one is non-existant
filename = files[0].name
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
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
count_numbers = 0
for char in reverse:
without_num = without_ext[:count_numbers*-1]
files = sorted(glob(directory + without_num + "[0-9]"*count_numbers + "." + extension))
nodes_list = [node for node in nodes]
if nodes_list:
nodes_list.sort(key=lambda k: k.location.x)
xloc = nodes_list[0].location.x - 220 # place new nodes at far left
yloc = 0
for node in nodes:
node.select = False
yloc += node_mid_pt(node, 'y')
yloc = yloc/len(nodes)
else:
xloc = 0
yloc = 0
name_with_hashes = without_num + "#"*count_numbers + '.' + extension
bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type=node_type)
node.label = name_with_hashes
img = bpy.data.images.load(directory+(without_ext+'.'+extension))
img.source = 'SEQUENCE'
img.name = name_with_hashes
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
image_user.frame_duration = num_frames
return {'FINISHED'}
class NWAddMultipleImages(Operator, NWBase, ImportHelper):
"""Add multiple images at once"""
bl_idname = 'node.nw_add_multiple_images'
bl_label = 'Open Selected Images'
bl_options = {'REGISTER', 'UNDO'}
directory: StringProperty(
subtype="DIR_PATH"
)
files: CollectionProperty(
type=bpy.types.OperatorFileListElement,
options={'HIDDEN', 'SKIP_SAVE'}
)
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)
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
if context.space_data.node_tree.type == 'SHADER':
node_type = "ShaderNodeTexImage"
elif context.space_data.node_tree.type == 'COMPOSITING':
node_type = "CompositorNodeImage"
else:
self.report({'ERROR'}, "Unsupported Node Tree type!")
return {'CANCELLED'}
new_nodes = []
for f in self.files:
fname = f.name
node = nodes.new(node_type)
new_nodes.append(node)
node.label = fname
node.hide = True
node.width_hidden = 100
node.location.x = xloc
node.location.y = yloc
yloc -= 40
img = bpy.data.images.load(self.directory+fname)
node.image = img
# shift new nodes up to center of tree
list_size = new_nodes[0].location.y - new_nodes[-1].location.y
for node in nodes:
if node in new_nodes:
node.select = True
node.location.y += (list_size/2)
else:
node.select = False
class NWViewerFocus(bpy.types.Operator):
"""Set the viewer tile center to the mouse position"""
bl_idname = "node.nw_viewer_focus"
bl_label = "Viewer Focus"
x: bpy.props.IntProperty()
y: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
return nw_check(context) and context.space_data.tree_type == 'CompositorNodeTree'
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
render = context.scene.render
space = context.space_data
percent = render.resolution_percentage*0.01
nodes, links = get_nodes_links(context)
viewers = [n for n in nodes if n.type == 'VIEWER']
if viewers:
mlocx = event.mouse_region_x
mlocy = event.mouse_region_y
select_node = bpy.ops.node.select(mouse_x=mlocx, mouse_y=mlocy, extend=False)
if not 'FINISHED' in select_node: # only run if we're not clicking on a node
region_x = context.region.width
region_y = context.region.height
region_center_x = context.region.width / 2
region_center_y = context.region.height / 2
bd_x = render.resolution_x * percent * space.backdrop_zoom
bd_y = render.resolution_y * percent * space.backdrop_zoom
backdrop_center_x = (bd_x / 2) - space.backdrop_offset[0]
backdrop_center_y = (bd_y / 2) - space.backdrop_offset[1]
margin_x = region_center_x - backdrop_center_x
margin_y = region_center_y - backdrop_center_y
abs_mouse_x = (mlocx - margin_x) / bd_x
abs_mouse_y = (mlocy - margin_y) / bd_y
for node in viewers:
node.center_x = abs_mouse_x
node.center_y = abs_mouse_y
else:
return {'PASS_THROUGH'}
return self.execute(context)
class NWSaveViewer(bpy.types.Operator, ExportHelper):
"""Save the current viewer node to an image file"""
bl_idname = "node.nw_save_viewer"
bl_label = "Save This Image"
filepath: StringProperty(subtype="FILE_PATH")
filename_ext: EnumProperty(
name="Format",
description="Choose the file format to save to",
items=(('.bmp', "BMP", ""),
('.rgb', 'IRIS', ""),
('.png', 'PNG', ""),
('.jpg', 'JPEG', ""),
('.jp2', 'JPEG2000', ""),
('.tga', 'TARGA', ""),
('.cin', 'CINEON', ""),
('.dpx', 'DPX', ""),
('.exr', 'OPEN_EXR', ""),
('.hdr', 'HDR', ""),
('.tif', 'TIFF', "")),
default='.png',
)
@classmethod
def poll(cls, context):
valid = False
if nw_check(context):
if context.space_data.tree_type == 'CompositorNodeTree':
if "Viewer Node" in [i.name for i in bpy.data.images]:
if sum(bpy.data.images["Viewer Node"].size) > 0: # False if not connected or connected but no image
valid = True
return valid
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
def execute(self, context):
fp = self.filepath
if fp:
formats = {
'.bmp': 'BMP',
'.rgb': 'IRIS',
'.png': 'PNG',
'.jpg': 'JPEG',
'.jpeg': 'JPEG',
'.jp2': 'JPEG2000',
'.tga': 'TARGA',
'.cin': 'CINEON',
'.dpx': 'DPX',
'.exr': 'OPEN_EXR',
'.hdr': 'HDR',
'.tiff': 'TIFF',
'.tif': 'TIFF'}
basename, ext = path.splitext(fp)
old_render_format = context.scene.render.image_settings.file_format
context.scene.render.image_settings.file_format = formats[self.filename_ext]
context.area.type = "IMAGE_EDITOR"
context.area.spaces[0].image = bpy.data.images['Viewer Node']
context.area.spaces[0].image.save_render(fp)
context.area.type = "NODE_EDITOR"
context.scene.render.image_settings.file_format = old_render_format
return {'FINISHED'}
class NWResetNodes(bpy.types.Operator):
"""Reset Nodes in Selection"""
bl_idname = "node.nw_reset_nodes"
bl_label = "Reset Nodes"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR'
def execute(self, context):
node_active = context.active_node
node_selected = context.selected_nodes
node_ignore = ["FRAME","REROUTE", "GROUP"]
# Check if one node is selected at least
if not (len(node_selected) > 0):
self.report({'ERROR'}, "1 node must be selected at least")
return {'CANCELLED'}
active_node_name = node_active.name if node_active.select else None
valid_nodes = [n for n in node_selected if n.type not in node_ignore]
# Create output lists
selected_node_names = [n.name for n in node_selected]
success_names = []
# Reset all valid children in a frame
node_active_is_frame = False
if len(node_selected) == 1 and node_active.type == "FRAME":
node_tree = node_active.id_data
children = [n for n in node_tree.nodes if n.parent == node_active]
if children:
valid_nodes = [n for n in children if n.type not in node_ignore]
selected_node_names = [n.name for n in children if n.type not in node_ignore]
node_active_is_frame = True
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
# Check if valid nodes in selection
if not (len(valid_nodes) > 0):
# Check for frames only
frames_selected = [n for n in node_selected if n.type == "FRAME"]
if (len(frames_selected) > 1 and len(frames_selected) == len(node_selected)):
self.report({'ERROR'}, "Please select only 1 frame to reset")
else:
self.report({'ERROR'}, "No valid node(s) in selection")
return {'CANCELLED'}
# Report nodes that are not valid
if len(valid_nodes) != len(node_selected) and node_active_is_frame is False:
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 {}".format(", ".join(not_valid_names)))
# Deselect all nodes
for i in node_selected:
i.select = False
# Run through all valid nodes
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
parent = node.parent if node.parent else None
node_loc = [node.location.x, node.location.y]
node_tree = node.id_data
props_to_copy = 'bl_idname name location height width'.split(' ')
reconnections = []
mappings = chain.from_iterable([node.inputs, node.outputs])
for i in (i for i in mappings if i.is_linked):
for L in i.links:
reconnections.append([L.from_socket.path_from_id(), L.to_socket.path_from_id()])
props = {j: getattr(node, j) for j in props_to_copy}
new_node = node_tree.nodes.new(props['bl_idname'])
props_to_copy.pop(0)
for prop in props_to_copy:
setattr(new_node, prop, props[prop])
nodes = node_tree.nodes
nodes.remove(node)
new_node.name = props['name']
if parent:
new_node.parent = parent
new_node.location = node_loc
for str_from, str_to in reconnections:
node_tree.links.new(eval(str_from), eval(str_to))
new_node.select = False
success_names.append(new_node.name)
# Reselect all nodes
if selected_node_names and node_active_is_frame is False:
for i in selected_node_names:
node_tree.nodes[i].select = True
if active_node_name is not None:
node_tree.nodes[active_node_name].select = True
node_tree.nodes.active = node_tree.nodes[active_node_name]
self.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names)))
return {'FINISHED'}
#
# P A N E L
#
def drawlayout(context, layout, mode='non-panel'):
tree_type = context.space_data.tree_type
col = layout.column(align=True)
col.menu(NWMergeNodesMenu.bl_idname)
col.separator()
col = layout.column(align=True)
col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type")
col.separator()
Brecht Van Lommel
committed
if tree_type == 'ShaderNodeTree':
col = layout.column(align=True)
col.operator(NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
col.operator(NWAddPrincipledSetup.bl_idname, text="Add Principled Setup", icon='NODE_SEL')
col.separator()
col = layout.column(align=True)
col.operator(NWDetachOutputs.bl_idname, icon='UNLINKED')
col.operator(NWSwapLinks.bl_idname)
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
col.menu(NWAddReroutesMenu.bl_idname, text="Add Reroutes", icon='LAYER_USED')
col.separator()
col = layout.column(align=True)
col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
col.operator(NWLinkToOutputNode.bl_idname, icon='DRIVER')
col.separator()
col = layout.column(align=True)
if mode == 'panel':
row = col.row(align=True)
row.operator(NWClearLabel.bl_idname).option = True
row.operator(NWModifyLabels.bl_idname)
else:
col.operator(NWClearLabel.bl_idname).option = True
col.operator(NWModifyLabels.bl_idname)
col.menu(NWBatchChangeNodesMenu.bl_idname, text="Batch Change")
col.separator()
col.menu(NWCopyToSelectedMenu.bl_idname, text="Copy to Selected")
col.separator()
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')
col.separator()
col = layout.column(align=True)
col.operator(NWFrameSelected.bl_idname, icon='STICKY_UVS_LOC')
col.separator()
col = layout.column(align=True)
col.operator(NWAlignNodes.bl_idname, icon='CENTER_ONLY')
col = layout.column(align=True)
col.operator(NWDeleteUnused.bl_idname, icon='CANCEL')
col.separator()
class NodeWranglerPanel(Panel, NWBase):
bl_idname = "NODE_PT_nw_node_wrangler"
bl_space_type = 'NODE_EDITOR'
bl_label = "Node Wrangler"
bl_region_type = "UI"
bl_category = "Node Wrangler"
name='prepend',
)
append: StringProperty()
remove: StringProperty()
def draw(self, context):
self.layout.label(text="(Quick access: Shift+W)")
drawlayout(context, self.layout, mode='panel')
#
# M E N U S
#
class NodeWranglerMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_node_wrangler_menu"
bl_label = "Node Wrangler"
def draw(self, context):
self.layout.operator_context = 'INVOKE_DEFAULT'
drawlayout(context, self.layout)
class NWMergeNodesMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_nodes_menu"
bl_label = "Merge Selected Nodes"
def draw(self, context):
type = context.space_data.tree_type
layout = self.layout
Brecht Van Lommel
committed
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'
class NWMergeShadersMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_shaders_menu"
bl_label = "Merge Selected Nodes using Shaders"
def draw(self, context):
layout = self.layout
for type in ('MIX', 'ADD'):
props = layout.operator(NWMergeNodes.bl_idname, text=type)
props.mode = type
props.merge_type = 'SHADER'
class NWMergeMixMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_mix_menu"
bl_label = "Merge Selected Nodes using Mix"
def draw(self, context):
layout = self.layout
for type, name, description in blend_types:
props = layout.operator(NWMergeNodes.bl_idname, text=name)
props.mode = type
props.merge_type = 'MIX'
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
class NWConnectionListOutputs(Menu, NWBase):
bl_idname = "NODE_MT_nw_connection_list_out"
bl_label = "From:"
def draw(self, context):
layout = self.layout
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:
layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
index+=1
class NWConnectionListInputs(Menu, NWBase):
bl_idname = "NODE_MT_nw_connection_list_in"
bl_label = "To:"
def draw(self, context):
layout = self.layout
nodes, links = get_nodes_links(context)
n2 = nodes[context.scene.NWLazyTarget]
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
class NWMergeMathMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_merge_math_menu"
bl_label = "Merge Selected Nodes using Math"
def draw(self, context):
layout = self.layout
for type, name, description in operations:
props = layout.operator(NWMergeNodes.bl_idname, text=name)
props.mode = type
props.merge_type = 'MATH'
class NWBatchChangeNodesMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_batch_change_nodes_menu"
bl_label = "Batch Change Selected Nodes"
def draw(self, context):
layout = self.layout
layout.menu(NWBatchChangeBlendTypeMenu.bl_idname)
layout.menu(NWBatchChangeOperationMenu.bl_idname)
class NWBatchChangeBlendTypeMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_batch_change_blend_type_menu"
bl_label = "Batch Change Blend Type"
def draw(self, context):
layout = self.layout
for type, name, description in blend_types:
props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
props.blend_type = type
props.operation = 'CURRENT'
class NWBatchChangeOperationMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_batch_change_operation_menu"
bl_label = "Batch Change Math Operation"
def draw(self, context):
layout = self.layout
for type, name, description in operations:
props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
props.blend_type = 'CURRENT'
props.operation = type
class NWCopyToSelectedMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_copy_node_properties_menu"
bl_label = "Copy to Selected"
def draw(self, context):
layout = self.layout
layout.operator(NWCopySettings.bl_idname, text="Settings from Active")
layout.menu(NWCopyLabelMenu.bl_idname)
class NWCopyLabelMenu(Menu, NWBase):
bl_idname = "NODE_MT_nw_copy_label_menu"