add support for reading lamp shadow-color, distance, cast-shadow settings.
[blender-addons.git] / node_efficiency_tools.py
blob6f352a23db8a861d614107659f00b4650c412d3a
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_info = {
20 'name': "Nodes Efficiency Tools",
21 'author': "Bartek Skorupa",
22 'version': (2, 32),
23 'blender': (2, 6, 8),
24 'location': "Node Editor Properties Panel (Ctrl-SPACE)",
25 'description': "Nodes Efficiency Tools",
26 'warning': "",
27 'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Nodes/Nodes_Efficiency_Tools",
28 'tracker_url': "http://projects.blender.org/tracker/index.php?func=detail&aid=33543&group_id=153&atid=469",
29 'category': "Node",
32 import bpy
33 from bpy.types import Operator, Panel, Menu
34 from bpy.props import FloatProperty, EnumProperty, BoolProperty
35 from mathutils import Vector
37 #################
38 # rl_outputs:
39 # list of outputs of Input Render Layer
40 # with attributes determinig if pass is used,
41 # and MultiLayer EXR outputs names and corresponding render engines
43 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_internal, in_cycles)
44 rl_outputs = (
45 ('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
46 ('use_pass_color', 'Color', 'Color', True, False),
47 ('use_pass_combined', 'Image', 'Combined', True, True),
48 ('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
49 ('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
50 ('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
51 ('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
52 ('use_pass_emit', 'Emit', 'Emit', True, False),
53 ('use_pass_environment', 'Environment', 'Env', True, False),
54 ('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
55 ('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
56 ('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
57 ('use_pass_indirect', 'Indirect', 'Indirect', True, False),
58 ('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
59 ('use_pass_mist', 'Mist', 'Mist', True, False),
60 ('use_pass_normal', 'Normal', 'Normal', True, True),
61 ('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
62 ('use_pass_reflection', 'Reflect', 'Reflect', True, False),
63 ('use_pass_refraction', 'Refract', 'Refract', True, False),
64 ('use_pass_shadow', 'Shadow', 'Shadow', True, True),
65 ('use_pass_specular', 'Specular', 'Spec', True, False),
66 ('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
67 ('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
68 ('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
69 ('use_pass_uv', 'UV', 'UV', True, True),
70 ('use_pass_vector', 'Speed', 'Vector', True, True),
71 ('use_pass_z', 'Z', 'Depth', True, True),
73 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
74 blend_types = [
75 ('MIX', 'Mix', 'Mix Mode'),
76 ('ADD', 'Add', 'Add Mode'),
77 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
78 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
79 ('SCREEN', 'Screen', 'Screen Mode'),
80 ('DIVIDE', 'Divide', 'Divide Mode'),
81 ('DIFFERENCE', 'Difference', 'Difference Mode'),
82 ('DARKEN', 'Darken', 'Darken Mode'),
83 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
84 ('OVERLAY', 'Overlay', 'Overlay Mode'),
85 ('DODGE', 'Dodge', 'Dodge Mode'),
86 ('BURN', 'Burn', 'Burn Mode'),
87 ('HUE', 'Hue', 'Hue Mode'),
88 ('SATURATION', 'Saturation', 'Saturation Mode'),
89 ('VALUE', 'Value', 'Value Mode'),
90 ('COLOR', 'Color', 'Color Mode'),
91 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
92 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
94 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
95 operations = [
96 ('ADD', 'Add', 'Add Mode'),
97 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
98 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
99 ('DIVIDE', 'Divide', 'Divide Mode'),
100 ('SINE', 'Sine', 'Sine Mode'),
101 ('COSINE', 'Cosine', 'Cosine Mode'),
102 ('TANGENT', 'Tangent', 'Tangent Mode'),
103 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
104 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
105 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
106 ('POWER', 'Power', 'Power Mode'),
107 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
108 ('MINIMUM', 'Minimum', 'Minimum Mode'),
109 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
110 ('ROUND', 'Round', 'Round Mode'),
111 ('LESS_THAN', 'Less Than', 'Less Thann Mode'),
112 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
114 # in BatchChangeNodes additional types/operations in a form that can be used as 'items' for EnumProperty.
115 navs = [
116 ('CURRENT', 'Current', 'Leave at current state'),
117 ('NEXT', 'Next', 'Next blend type/operation'),
118 ('PREV', 'Prev', 'Previous blend type/operation'),
120 # list of mixing shaders
121 merge_shaders_types = ('MIX', 'ADD')
122 # list of regular shaders. Entry: (identified, type, name for humans). Will be used in SwapShaders and menus.
123 # Keeping mixed case to avoid having to translate entries when adding new nodes in SwapNodes.
124 regular_shaders = (
125 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
126 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
127 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
128 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
129 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
130 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
131 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
132 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
133 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
134 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
135 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
136 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
137 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
138 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
140 merge_shaders = (
141 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
142 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
145 def get_nodes_links(context):
146 space = context.space_data
147 tree = space.node_tree
148 nodes = tree.nodes
149 links = tree.links
150 active = nodes.active
151 context_active = context.active_node
152 # check if we are working on regular node tree or node group is currently edited.
153 # if group is edited - active node of space_tree is the group
154 # if context.active_node != space active node - it means that the group is being edited.
155 # in such case we set "nodes" to be nodes of this group, "links" to be links of this group
156 # if context.active_node == space.active_node it means that we are not currently editing group
157 is_main_tree = True
158 if active:
159 is_main_tree = context_active == active
160 if not is_main_tree: # if group is currently edited
161 tree = active.node_tree
162 nodes = tree.nodes
163 links = tree.links
165 return nodes, links
168 class NodeToolBase:
169 @classmethod
170 def poll(cls, context):
171 space = context.space_data
172 return space.type == 'NODE_EDITOR' and space.node_tree is not None
175 class MergeNodes(Operator, NodeToolBase):
176 bl_idname = "node.merge_nodes"
177 bl_label = "Merge Nodes"
178 bl_description = "Merge Selected Nodes"
179 bl_options = {'REGISTER', 'UNDO'}
181 mode = EnumProperty(
182 name="mode",
183 description="All possible blend types and math operations",
184 items=blend_types + [op for op in operations if op not in blend_types],
186 merge_type = EnumProperty(
187 name="merge type",
188 description="Type of Merge to be used",
189 items=(
190 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
191 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
192 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
193 ('MATH', 'Math Node', 'Merge using Math Nodes'),
197 def execute(self, context):
198 tree_type = context.space_data.node_tree.type
199 if tree_type == 'COMPOSITING':
200 node_type = 'CompositorNode'
201 elif tree_type == 'SHADER':
202 node_type = 'ShaderNode'
203 nodes, links = get_nodes_links(context)
204 mode = self.mode
205 merge_type = self.merge_type
206 selected_mix = [] # entry = [index, loc]
207 selected_shader = [] # entry = [index, loc]
208 selected_math = [] # entry = [index, loc]
210 for i, node in enumerate(nodes):
211 if node.select and node.outputs:
212 if merge_type == 'AUTO':
213 for (type, types_list, dst) in (
214 ('SHADER', merge_shaders_types, selected_shader),
215 ('RGBA', [t[0] for t in blend_types], selected_mix),
216 ('VALUE', [t[0] for t in operations], selected_math),
218 output_type = node.outputs[0].type
219 valid_mode = mode in types_list
220 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
221 # Cheat that output type is 'RGBA',
222 # and that 'MIX' exists in math operations list.
223 # This way when selected_mix list is analyzed:
224 # Node data will be appended even though it doesn't meet requirements.
225 if output_type != 'SHADER' and mode == 'MIX':
226 output_type = 'RGBA'
227 valid_mode = True
228 if output_type == type and valid_mode:
229 dst.append([i, node.location.x, node.location.y])
230 else:
231 for (type, types_list, dst) in (
232 ('SHADER', merge_shaders_types, selected_shader),
233 ('MIX', [t[0] for t in blend_types], selected_mix),
234 ('MATH', [t[0] for t in operations], selected_math),
236 if merge_type == type and mode in types_list:
237 dst.append([i, node.location.x, node.location.y])
238 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
239 # use only 'Mix' nodes for merging.
240 # For that we add selected_math list to selected_mix list and clear selected_math.
241 if selected_mix and selected_math and merge_type == 'AUTO':
242 selected_mix += selected_math
243 selected_math = []
245 for nodes_list in [selected_mix, selected_shader, selected_math]:
246 if nodes_list:
247 count_before = len(nodes)
248 # sort list by loc_x - reversed
249 nodes_list.sort(key=lambda k: k[1], reverse=True)
250 # get maximum loc_x
251 loc_x = nodes_list[0][1] + 350.0
252 nodes_list.sort(key=lambda k: k[2], reverse=True)
253 loc_y = nodes_list[len(nodes_list) - 1][2]
254 offset_y = 40.0
255 if nodes_list == selected_shader:
256 offset_y = 150.0
257 the_range = len(nodes_list) - 1
258 do_hide = True
259 if len(nodes_list) == 1:
260 the_range = 1
261 do_hide = False
262 for i in range(the_range):
263 if nodes_list == selected_mix:
264 add_type = node_type + 'MixRGB'
265 add = nodes.new(add_type)
266 add.blend_type = mode
267 add.show_preview = False
268 add.hide = do_hide
269 first = 1
270 second = 2
271 add.width_hidden = 100.0
272 elif nodes_list == selected_math:
273 add_type = node_type + 'Math'
274 add = nodes.new(add_type)
275 add.operation = mode
276 add.hide = do_hide
277 first = 0
278 second = 1
279 add.width_hidden = 100.0
280 elif nodes_list == selected_shader:
281 if mode == 'MIX':
282 add_type = node_type + 'MixShader'
283 add = nodes.new(add_type)
284 first = 1
285 second = 2
286 add.width_hidden = 100.0
287 elif mode == 'ADD':
288 add_type = node_type + 'AddShader'
289 add = nodes.new(add_type)
290 first = 0
291 second = 1
292 add.width_hidden = 100.0
293 add.location = loc_x, loc_y
294 loc_y += offset_y
295 add.select = True
296 count_adds = i + 1
297 count_after = len(nodes)
298 index = count_after - 1
299 first_selected = nodes[nodes_list[0][0]]
300 # "last" node has been added as first, so its index is count_before.
301 last_add = nodes[count_before]
302 # add links from last_add to all links 'to_socket' of out links of first selected.
303 for fs_link in first_selected.outputs[0].links:
304 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
305 # Create list of invalid indexes.
306 invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader)]
307 # Link only if "to_node" index not in invalid indexes list.
308 if fs_link.to_node not in [nodes[i] for i in invalid_i]:
309 links.new(last_add.outputs[0], fs_link.to_socket)
310 # add link from "first" selected and "first" add node
311 links.new(first_selected.outputs[0], nodes[count_after - 1].inputs[first])
312 # add links between added ADD nodes and between selected and ADD nodes
313 for i in range(count_adds):
314 if i < count_adds - 1:
315 links.new(nodes[index - 1].inputs[first], nodes[index].outputs[0])
316 if len(nodes_list) > 1:
317 links.new(nodes[index].inputs[second], nodes[nodes_list[i + 1][0]].outputs[0])
318 index -= 1
319 # set "last" of added nodes as active
320 nodes.active = last_add
321 for i, x, y in nodes_list:
322 nodes[i].select = False
324 return {'FINISHED'}
327 class BatchChangeNodes(Operator, NodeToolBase):
328 bl_idname = "node.batch_change"
329 bl_label = "Batch Change"
330 bl_description = "Batch Change Blend Type and Math Operation"
331 bl_options = {'REGISTER', 'UNDO'}
333 blend_type = EnumProperty(
334 name="Blend Type",
335 items=blend_types + navs,
337 operation = EnumProperty(
338 name="Operation",
339 items=operations + navs,
342 def execute(self, context):
343 nodes, links = get_nodes_links(context)
344 blend_type = self.blend_type
345 operation = self.operation
346 for node in context.selected_nodes:
347 if node.type == 'MIX_RGB':
348 if not blend_type in [nav[0] for nav in navs]:
349 node.blend_type = blend_type
350 else:
351 if blend_type == 'NEXT':
352 index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
353 #index = blend_types.index(node.blend_type)
354 if index == len(blend_types) - 1:
355 node.blend_type = blend_types[0][0]
356 else:
357 node.blend_type = blend_types[index + 1][0]
359 if blend_type == 'PREV':
360 index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
361 if index == 0:
362 node.blend_type = blend_types[len(blend_types) - 1][0]
363 else:
364 node.blend_type = blend_types[index - 1][0]
366 if node.type == 'MATH':
367 if not operation in [nav[0] for nav in navs]:
368 node.operation = operation
369 else:
370 if operation == 'NEXT':
371 index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
372 #index = operations.index(node.operation)
373 if index == len(operations) - 1:
374 node.operation = operations[0][0]
375 else:
376 node.operation = operations[index + 1][0]
378 if operation == 'PREV':
379 index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
380 #index = operations.index(node.operation)
381 if index == 0:
382 node.operation = operations[len(operations) - 1][0]
383 else:
384 node.operation = operations[index - 1][0]
386 return {'FINISHED'}
389 class ChangeMixFactor(Operator, NodeToolBase):
390 bl_idname = "node.factor"
391 bl_label = "Change Factor"
392 bl_description = "Change Factors of Mix Nodes and Mix Shader Nodes"
393 bl_options = {'REGISTER', 'UNDO'}
395 # option: Change factor.
396 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
397 # Else - change factor by option value.
398 option = FloatProperty()
400 def execute(self, context):
401 nodes, links = get_nodes_links(context)
402 option = self.option
403 selected = [] # entry = index
404 for si, node in enumerate(nodes):
405 if node.select:
406 if node.type in {'MIX_RGB', 'MIX_SHADER'}:
407 selected.append(si)
409 for si in selected:
410 fac = nodes[si].inputs[0]
411 nodes[si].hide = False
412 if option in {0.0, 1.0}:
413 fac.default_value = option
414 else:
415 fac.default_value += option
417 return {'FINISHED'}
420 class NodesCopySettings(Operator):
421 bl_idname = "node.copy_settings"
422 bl_label = "Copy Settings"
423 bl_description = "Copy Settings of Active Node to Selected Nodes"
424 bl_options = {'REGISTER', 'UNDO'}
426 @classmethod
427 def poll(cls, context):
428 space = context.space_data
429 valid = False
430 if (space.type == 'NODE_EDITOR' and
431 space.node_tree is not None and
432 context.active_node is not None and
433 context.active_node.type is not 'FRAME'
435 valid = True
436 return valid
438 def execute(self, context):
439 nodes, links = get_nodes_links(context)
440 selected = [n for n in nodes if n.select]
441 reselect = [] # duplicated nodes will be selected after execution
442 active = nodes.active
443 if active.select:
444 reselect.append(active)
446 for node in selected:
447 if node.type == active.type and node != active:
448 # duplicate active, relink links as in 'node', append copy to 'reselect', delete node
449 bpy.ops.node.select_all(action='DESELECT')
450 nodes.active = active
451 active.select = True
452 bpy.ops.node.duplicate()
453 copied = nodes.active
454 # Copied active should however inherit some properties from 'node'
455 attributes = (
456 'hide', 'show_preview', 'mute', 'label',
457 'use_custom_color', 'color', 'width', 'width_hidden',
459 for attr in attributes:
460 setattr(copied, attr, getattr(node, attr))
461 # Handle scenario when 'node' is in frame. 'copied' is in same frame then.
462 if copied.parent:
463 bpy.ops.node.parent_clear()
464 locx = node.location.x
465 locy = node.location.y
466 # get absolute node location
467 parent = node.parent
468 while parent:
469 locx += parent.location.x
470 locy += parent.location.y
471 parent = parent.parent
472 copied.location = [locx, locy]
473 # reconnect links from node to copied
474 for i, input in enumerate(node.inputs):
475 if input.links:
476 link = input.links[0]
477 links.new(link.from_socket, copied.inputs[i])
478 for out, output in enumerate(node.outputs):
479 if output.links:
480 out_links = output.links
481 for link in out_links:
482 links.new(copied.outputs[out], link.to_socket)
483 bpy.ops.node.select_all(action='DESELECT')
484 node.select = True
485 bpy.ops.node.delete()
486 reselect.append(copied)
487 else: # If selected wasn't copied, need to reselect it afterwards.
488 reselect.append(node)
489 # clean up
490 bpy.ops.node.select_all(action='DESELECT')
491 for node in reselect:
492 node.select = True
493 nodes.active = active
495 return {'FINISHED'}
498 class NodesCopyLabel(Operator, NodeToolBase):
499 bl_idname = "node.copy_label"
500 bl_label = "Copy Label"
501 bl_options = {'REGISTER', 'UNDO'}
503 option = EnumProperty(
504 name="option",
505 description="Source of name of label",
506 items=(
507 ('FROM_ACTIVE', 'from active', 'from active node',),
508 ('FROM_NODE', 'from node', 'from node linked to selected node'),
509 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
513 def execute(self, context):
514 nodes, links = get_nodes_links(context)
515 option = self.option
516 active = nodes.active
517 if option == 'FROM_ACTIVE':
518 if active:
519 src_label = active.label
520 for node in [n for n in nodes if n.select and nodes.active != n]:
521 node.label = src_label
522 elif option == 'FROM_NODE':
523 selected = [n for n in nodes if n.select]
524 for node in selected:
525 for input in node.inputs:
526 if input.links:
527 src = input.links[0].from_node
528 node.label = src.label
529 break
530 elif option == 'FROM_SOCKET':
531 selected = [n for n in nodes if n.select]
532 for node in selected:
533 for input in node.inputs:
534 if input.links:
535 src = input.links[0].from_socket
536 node.label = src.name
537 break
539 return {'FINISHED'}
542 class NodesClearLabel(Operator, NodeToolBase):
543 bl_idname = "node.clear_label"
544 bl_label = "Clear Label"
545 bl_options = {'REGISTER', 'UNDO'}
547 option = BoolProperty()
549 def execute(self, context):
550 nodes, links = get_nodes_links(context)
551 for node in [n for n in nodes if n.select]:
552 node.label = ''
554 return {'FINISHED'}
556 def invoke(self, context, event):
557 if self.option:
558 return self.execute(context)
559 else:
560 return context.window_manager.invoke_confirm(self, event)
563 class NodesAddTextureSetup(Operator):
564 bl_idname = "node.add_texture"
565 bl_label = "Texture Setup"
566 bl_description = "Add Texture Node Setup to Selected Shaders"
567 bl_options = {'REGISTER', 'UNDO'}
569 @classmethod
570 def poll(cls, context):
571 space = context.space_data
572 valid = False
573 if space.type == 'NODE_EDITOR':
574 if space.tree_type == 'ShaderNodeTree' and space.node_tree is not None:
575 valid = True
576 return valid
578 def execute(self, context):
579 nodes, links = get_nodes_links(context)
580 active = nodes.active
581 valid = False
582 if active:
583 if active.select:
584 if active.type in {
585 'BSDF_ANISOTROPIC',
586 'BSDF_DIFFUSE',
587 'BSDF_GLOSSY',
588 'BSDF_GLASS',
589 'BSDF_REFRACTION',
590 'BSDF_TRANSLUCENT',
591 'BSDF_TRANSPARENT',
592 'BSDF_VELVET',
593 'EMISSION',
594 'AMBIENT_OCCLUSION',
596 if not active.inputs[0].is_linked:
597 valid = True
598 if valid:
599 locx = active.location.x
600 locy = active.location.y
601 tex = nodes.new('ShaderNodeTexImage')
602 tex.location = [locx - 200.0, locy + 28.0]
603 map = nodes.new('ShaderNodeMapping')
604 map.location = [locx - 490.0, locy + 80.0]
605 coord = nodes.new('ShaderNodeTexCoord')
606 coord.location = [locx - 700, locy + 40.0]
607 active.select = False
608 nodes.active = tex
610 links.new(tex.outputs[0], active.inputs[0])
611 links.new(map.outputs[0], tex.inputs[0])
612 links.new(coord.outputs[2], map.inputs[0])
614 return {'FINISHED'}
617 class NodesAddReroutes(Operator, NodeToolBase):
618 bl_idname = "node.add_reroutes"
619 bl_label = "Add Reroutes"
620 bl_description = "Add Reroutes to Outputs"
621 bl_options = {'REGISTER', 'UNDO'}
623 option = EnumProperty(
624 name="option",
625 items=[
626 ('ALL', 'to all', 'Add to all outputs'),
627 ('LOOSE', 'to loose', 'Add only to loose outputs'),
628 ('LINKED', 'to linked', 'Add only to linked outputs'),
632 def execute(self, context):
633 tree_type = context.space_data.node_tree.type
634 option = self.option
635 nodes, links = get_nodes_links(context)
636 # output valid when option is 'all' or when 'loose' output has no links
637 valid = False
638 post_select = [] # nodes to be selected after execution
639 # create reroutes and recreate links
640 for node in [n for n in nodes if n.select]:
641 if node.outputs:
642 x = node.location.x
643 y = node.location.y
644 width = node.width
645 # unhide 'REROUTE' nodes to avoid issues with location.y
646 if node.type == 'REROUTE':
647 node.hide = False
648 # When node is hidden - width_hidden not usable.
649 # Hack needed to calculate real width
650 if node.hide:
651 bpy.ops.node.select_all(action='DESELECT')
652 helper = nodes.new('NodeReroute')
653 helper.select = True
654 node.select = True
655 # resize node and helper to zero. Then check locations to calculate width
656 bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
657 width = 2.0 * (helper.location.x - node.location.x)
658 # restore node location
659 node.location = x, y
660 # delete helper
661 node.select = False
662 # only helper is selected now
663 bpy.ops.node.delete()
664 x = node.location.x + width + 20.0
665 if node.type != 'REROUTE':
666 y -= 35.0
667 y_offset = -22.0
668 loc = x, y
669 reroutes_count = 0 # will be used when aligning reroutes added to hidden nodes
670 for out_i, output in enumerate(node.outputs):
671 pass_used = False # initial value to be analyzed if 'R_LAYERS'
672 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
673 if node.type != 'R_LAYERS':
674 pass_used = True
675 else: # if 'R_LAYERS' check if output represent used render pass
676 node_scene = node.scene
677 node_layer = node.layer
678 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
679 if output.name == 'Alpha':
680 pass_used = True
681 else:
682 # check entries in global 'rl_outputs' variable
683 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
684 if output.name == out_name:
685 pass_used = getattr(node_scene.render.layers[node_layer], render_pass)
686 break
687 if pass_used:
688 valid = ((option == 'ALL') or
689 (option == 'LOOSE' and not output.links) or
690 (option == 'LINKED' and output.links))
691 # Add reroutes only if valid, but offset location in all cases.
692 if valid:
693 n = nodes.new('NodeReroute')
694 nodes.active = n
695 for link in output.links:
696 links.new(n.outputs[0], link.to_socket)
697 links.new(output, n.inputs[0])
698 n.location = loc
699 post_select.append(n)
700 reroutes_count += 1
701 y += y_offset
702 loc = x, y
703 # disselect the node so that after execution of script only newly created nodes are selected
704 node.select = False
705 # nicer reroutes distribution along y when node.hide
706 if node.hide:
707 y_translate = reroutes_count * y_offset / 2.0 - y_offset - 35.0
708 for reroute in [r for r in nodes if r.select]:
709 reroute.location.y -= y_translate
710 for node in post_select:
711 node.select = True
713 return {'FINISHED'}
716 class NodesSwap(Operator, NodeToolBase):
717 bl_idname = "node.swap_nodes"
718 bl_label = "Swap Nodes"
719 bl_options = {'REGISTER', 'UNDO'}
721 option = EnumProperty(
722 items=[
723 ('CompositorNodeSwitch', 'Switch', 'Switch'),
724 ('NodeReroute', 'Reroute', 'Reroute'),
725 ('NodeMixRGB', 'Mix Node', 'Mix Node'),
726 ('NodeMath', 'Math Node', 'Math Node'),
727 ('CompositorNodeAlphaOver', 'Alpha Over', 'Alpha Over'),
728 ('ShaderNodeMixShader', 'Mix Shader', 'Mix Shader'),
729 ('ShaderNodeAddShader', 'Add Shader', 'Add Shader'),
730 ('ShaderNodeBsdfDiffuse', 'Diffuse BSDF', 'Diffuse BSDF'),
731 ('ShaderNodeBsdfGlossy', 'Glossy BSDF', 'Glossy BSDF'),
732 ('ShaderNodeBsdfTransparent', 'Transparent BSDF', 'Transparent BSDF'),
733 ('ShaderNodeBsdfRefraction', 'Refraction BSDF', 'Refraction BSDF'),
734 ('ShaderNodeBsdfGlass', 'Glass BSDF', 'Glass BSDF'),
735 ('ShaderNodeBsdfTranslucent', 'Translucent BSDF', 'Translucent BSDF'),
736 ('ShaderNodeBsdfAnisotropic', 'Anisotropic BSDF', 'Anisotropic BSDF'),
737 ('ShaderNodeBsdfVelvet', 'Velvet BSDF', 'Velvet BSDF'),
738 ('ShaderNodeBsdfToon', 'Toon BSDF', 'Toon BSDF'),
739 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
740 ('ShaderNodeEmission', 'Emission', 'Emission'),
741 ('ShaderNodeBackground', 'Background', 'Background'),
742 ('ShaderNodeAmbientOcclusion', 'Ambient Occlusion', 'Ambient Occlusion'),
743 ('ShaderNodeHoldout', 'Holdout', 'Holdout'),
747 def execute(self, context):
748 nodes, links = get_nodes_links(context)
749 tree_type = context.space_data.tree_type
750 if tree_type == 'CompositorNodeTree':
751 prefix = 'Compositor'
752 elif tree_type == 'ShaderNodeTree':
753 prefix = 'Shader'
754 option = self.option
755 selected = [n for n in nodes if n.select]
756 reselect = []
757 mode = None # will be used to set proper operation or blend type in new Math or Mix nodes.
758 # regular_shaders - global list. Entry: (identifier, type, name for humans)
759 # example: ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF')
760 swap_shaders = option in (s[0] for s in regular_shaders)
761 swap_merge_shaders = option in (s[0] for s in merge_shaders)
762 if swap_shaders or swap_merge_shaders:
763 # replace_types - list of node types that can be replaced using selected option
764 shaders = regular_shaders + merge_shaders
765 replace_types = [type[1] for type in shaders]
766 new_type = option
767 elif option == 'CompositorNodeSwitch':
768 replace_types = ('REROUTE', 'MIX_RGB', 'MATH', 'ALPHAOVER')
769 new_type = option
770 elif option == 'NodeReroute':
771 replace_types = ('SWITCH')
772 new_type = option
773 elif option == 'NodeMixRGB':
774 replace_types = ('REROUTE', 'SWITCH', 'MATH', 'ALPHAOVER')
775 new_type = prefix + option
776 elif option == 'NodeMath':
777 replace_types = ('REROUTE', 'SWITCH', 'MIX_RGB', 'ALPHAOVER')
778 new_type = prefix + option
779 elif option == 'CompositorNodeAlphaOver':
780 replace_types = ('REROUTE', 'SWITCH', 'MATH', 'MIX_RGB')
781 new_type = option
782 for node in selected:
783 if node.type in replace_types:
784 hide = node.hide
785 if node.type == 'REROUTE':
786 hide = True
787 new_node = nodes.new(new_type)
788 # if swap Mix to Math of vice-verca - try to set blend type or operation accordingly
789 if new_node.type in {'MIX_RGB', 'ALPHAOVER'}:
790 new_node.inputs[0].default_value = 1.0
791 if node.type == 'MATH':
792 if node.operation in [entry[0] for entry in blend_types]:
793 if hasattr(new_node, 'blend_type'):
794 new_node.blend_type = node.operation
795 for i in range(2):
796 new_node.inputs[i+1].default_value = [node.inputs[i].default_value] * 3 + [1.0]
797 elif node.type in {'MIX_RGB', 'ALPHAOVER'}:
798 for i in range(3):
799 new_node.inputs[i].default_value = node.inputs[i].default_value
800 elif new_node.type == 'MATH':
801 if node.type in {'MIX_RGB', 'ALPHAOVER'}:
802 if hasattr(node, 'blend_type'):
803 if node.blend_type in [entry[0] for entry in operations]:
804 new_node.operation = node.blend_type
805 for i in range(2):
806 channels = []
807 for c in range(3):
808 channels.append(node.inputs[i+1].default_value[c])
809 new_node.inputs[i].default_value = max(channels)
810 old_inputs_count = len(node.inputs)
811 new_inputs_count = len(new_node.inputs)
812 replace = [] # entries - pairs: old input index, new input index.
813 if swap_shaders:
814 for old_i, old_input in enumerate(node.inputs):
815 for new_i, new_input in enumerate(new_node.inputs):
816 if old_input.name == new_input.name:
817 replace.append((old_i, new_i))
818 new_input.default_value = old_input.default_value
819 break
820 elif option == 'ShaderNodeAddShader':
821 if node.type == 'ADD_SHADER':
822 replace = ((0, 0), (1, 1))
823 elif node.type == 'MIX_SHADER':
824 replace = ((1, 0), (2, 1))
825 elif option == 'ShaderNodeMixShader':
826 if node.type == 'ADD_SHADER':
827 replace = ((0, 1), (1, 2))
828 elif node.type == 'MIX_SHADER':
829 replace = ((1, 1), (2, 2))
830 elif new_inputs_count == 1:
831 replace = ((0, 0), )
832 elif new_inputs_count == 2:
833 if old_inputs_count == 1:
834 replace = ((0, 0), )
835 elif old_inputs_count == 2:
836 replace = ((0, 0), (1, 1))
837 elif old_inputs_count == 3:
838 replace = ((1, 0), (2, 1))
839 elif new_inputs_count == 3:
840 if old_inputs_count == 1:
841 replace = ((0, 1), )
842 elif old_inputs_count == 2:
843 replace = ((0, 1), (1, 2))
844 elif old_inputs_count == 3:
845 replace = ((0, 0), (1, 1), (2, 2))
846 if replace:
847 for old_i, new_i in replace:
848 if node.inputs[old_i].links:
849 in_link = node.inputs[old_i].links[0]
850 links.new(in_link.from_socket, new_node.inputs[new_i])
851 for out_link in node.outputs[0].links:
852 links.new(new_node.outputs[0], out_link.to_socket)
853 for attr in {'location', 'label', 'mute', 'show_preview', 'width_hidden', 'use_clamp'}:
854 if hasattr(node, attr) and hasattr(new_node, attr):
855 setattr(new_node, attr, getattr(node, attr))
856 new_node.hide = hide
857 nodes.active = new_node
858 reselect.append(new_node)
859 bpy.ops.node.select_all(action="DESELECT")
860 node.select = True
861 bpy.ops.node.delete()
862 else:
863 reselect.append(node)
864 for node in reselect:
865 node.select = True
867 return {'FINISHED'}
870 class NodesLinkActiveToSelected(Operator):
871 bl_idname = "node.link_active_to_selected"
872 bl_label = "Link Active Node to Selected"
873 bl_options = {'REGISTER', 'UNDO'}
875 replace = BoolProperty()
876 use_node_name = BoolProperty()
877 use_outputs_names = BoolProperty()
879 @classmethod
880 def poll(cls, context):
881 space = context.space_data
882 valid = False
883 if space.type == 'NODE_EDITOR':
884 if space.node_tree is not None and context.active_node is not None:
885 if context.active_node.select:
886 valid = True
887 return valid
889 def execute(self, context):
890 nodes, links = get_nodes_links(context)
891 replace = self.replace
892 use_node_name = self.use_node_name
893 use_outputs_names = self.use_outputs_names
894 active = nodes.active
895 selected = [node for node in nodes if node.select and node != active]
896 outputs = [] # Only usable outputs of active nodes will be stored here.
897 for out in active.outputs:
898 if active.type != 'R_LAYERS':
899 outputs.append(out)
900 else:
901 # 'R_LAYERS' node type needs special handling.
902 # outputs of 'R_LAYERS' are callable even if not seen in UI.
903 # Only outputs that represent used passes should be taken into account
904 # Check if pass represented by output is used.
905 # global 'rl_outputs' list will be used for that
906 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
907 pass_used = False # initial value. Will be set to True if pass is used
908 if out.name == 'Alpha':
909 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
910 pass_used = True
911 elif out.name == out_name:
912 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
913 pass_used = getattr(active.scene.render.layers[active.layer], render_pass)
914 break
915 if pass_used:
916 outputs.append(out)
917 doit = True # Will be changed to False when links successfully added to previous output.
918 for out in outputs:
919 if doit:
920 for node in selected:
921 dst_name = node.name # Will be compared with src_name if needed.
922 # When node has label - use it as dst_name
923 if node.label:
924 dst_name = node.label
925 valid = True # Initial value. Will be changed to False if names don't match.
926 src_name = dst_name # If names not used - this asignment will keep valid = True.
927 if use_node_name:
928 # Set src_name to source node name or label
929 src_name = active.name
930 if active.label:
931 src_name = active.label
932 elif use_outputs_names:
933 src_name = (out.name, )
934 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
935 if out.name in {out_name, exr_name}:
936 src_name = (out_name, exr_name)
937 if dst_name not in src_name:
938 valid = False
939 if valid:
940 for input in node.inputs:
941 if input.type == out.type or node.type == 'REROUTE':
942 if replace or not input.is_linked:
943 links.new(out, input)
944 if not use_node_name and not use_outputs_names:
945 doit = False
946 break
948 return {'FINISHED'}
951 class AlignNodes(Operator, NodeToolBase):
952 bl_idname = "node.align_nodes"
953 bl_label = "Align nodes"
954 bl_options = {'REGISTER', 'UNDO'}
956 # option: 'Vertically', 'Horizontally'
957 option = EnumProperty(
958 name="option",
959 description="Direction",
960 items=(
961 ('AXIS_X', "Align Vertically", 'Align Vertically'),
962 ('AXIS_Y', "Aligh Horizontally", 'Aligh Horizontally'),
966 def execute(self, context):
967 nodes, links = get_nodes_links(context)
968 selected = [] # entry = [index, loc.x, loc.y, width, height]
969 frames_reselect = [] # entry = frame node. will be used to reselect all selected frames
970 active = nodes.active
971 for i, node in enumerate(nodes):
972 if node.select:
973 if node.type == 'FRAME':
974 node.select = False
975 frames_reselect.append(i)
976 else:
977 locx = node.location.x
978 locy = node.location.y
979 parent = node.parent
980 while parent is not None:
981 locx += parent.location.x
982 locy += parent.location.y
983 parent = parent.parent
984 selected.append([i, locx, locy])
985 count = len(selected)
986 # add reroute node then scale all to 0.0 and calculate widths and heights of nodes
987 if count > 1: # aligning makes sense only if at least 2 nodes are selected
988 helper = nodes.new('NodeReroute')
989 helper.select = True
990 bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
991 # store helper's location for further calculations
992 zero_x = helper.location.x
993 zero_y = helper.location.y
994 nodes.remove(helper)
995 # helper is deleted but its location is stored
996 # helper's width and height are 0.0.
997 # Check loc of other nodes in relation to helper to calculate their dimensions
998 # and append them to entries of "selected"
999 total_w = 0.0 # total width of all nodes. Will be calculated later.
1000 total_h = 0.0 # total height of all nodes. Will be calculated later
1001 for j, [i, x, y] in enumerate(selected):
1002 locx = nodes[i].location.x
1003 locy = nodes[i].location.y
1004 # take node's parent (frame) into account. Get absolute location
1005 parent = nodes[i].parent
1006 while parent is not None:
1007 locx += parent.location.x
1008 locy += parent.location.y
1009 parent = parent.parent
1010 width = abs((zero_x - locx) * 2.0)
1011 height = abs((zero_y - locy) * 2.0)
1012 selected[j].append(width) # complete selected's entry for nodes[i]
1013 selected[j].append(height) # complete selected's entry for nodes[i]
1014 total_w += width # add nodes[i] width to total width of all nodes
1015 total_h += height # add nodes[i] height to total height of all nodes
1016 selected_sorted_x = sorted(selected, key=lambda k: (k[1], -k[2]))
1017 selected_sorted_y = sorted(selected, key=lambda k: (-k[2], k[1]))
1018 min_x = selected_sorted_x[0][1] # min loc.x
1019 min_x_loc_y = selected_sorted_x[0][2] # loc y of node with min loc x
1020 min_x_w = selected_sorted_x[0][3] # width of node with max loc x
1021 max_x = selected_sorted_x[count - 1][1] # max loc.x
1022 max_x_loc_y = selected_sorted_x[count - 1][2] # loc y of node with max loc.x
1023 max_x_w = selected_sorted_x[count - 1][3] # width of node with max loc.x
1024 min_y = selected_sorted_y[0][2] # min loc.y
1025 min_y_loc_x = selected_sorted_y[0][1] # loc.x of node with min loc.y
1026 min_y_h = selected_sorted_y[0][4] # height of node with min loc.y
1027 min_y_w = selected_sorted_y[0][3] # width of node with min loc.y
1028 max_y = selected_sorted_y[count - 1][2] # max loc.y
1029 max_y_loc_x = selected_sorted_y[count - 1][1] # loc x of node with max loc.y
1030 max_y_w = selected_sorted_y[count - 1][3] # width of node with max loc.y
1031 max_y_h = selected_sorted_y[count - 1][4] # height of node with max loc.y
1033 if self.option == 'AXIS_Y': # Horizontally. Equivelent of s -> x -> 0 with even spacing.
1034 loc_x = min_x
1035 #loc_y = (max_x_loc_y + min_x_loc_y) / 2.0
1036 loc_y = (max_y - max_y_h / 2.0 + min_y - min_y_h / 2.0) / 2.0
1037 offset_x = (max_x - min_x - total_w + max_x_w) / (count - 1)
1038 for i, x, y, w, h in selected_sorted_x:
1039 nodes[i].location.x = loc_x
1040 nodes[i].location.y = loc_y + h / 2.0
1041 parent = nodes[i].parent
1042 while parent is not None:
1043 nodes[i].location.x -= parent.location.x
1044 nodes[i].location.y -= parent.location.y
1045 parent = parent.parent
1046 loc_x += offset_x + w
1047 else: # if self.option == 'AXIS_Y'
1048 #loc_x = (max_y_loc_x + max_y_w / 2.0 + min_y_loc_x + min_y_w / 2.0) / 2.0
1049 loc_x = (max_x + max_x_w / 2.0 + min_x + min_x_w / 2.0) / 2.0
1050 loc_y = min_y
1051 offset_y = (max_y - min_y + total_h - min_y_h) / (count - 1)
1052 for i, x, y, w, h in selected_sorted_y:
1053 nodes[i].location.x = loc_x - w / 2.0
1054 nodes[i].location.y = loc_y
1055 parent = nodes[i].parent
1056 while parent is not None:
1057 nodes[i].location.x -= parent.location.x
1058 nodes[i].location.y -= parent.location.y
1059 parent = parent.parent
1060 loc_y += offset_y - h
1062 # reselect selected frames
1063 for i in frames_reselect:
1064 nodes[i].select = True
1065 # restore active node
1066 nodes.active = active
1068 return {'FINISHED'}
1071 class SelectParentChildren(Operator, NodeToolBase):
1072 bl_idname = "node.select_parent_child"
1073 bl_label = "Select Parent or Children"
1074 bl_options = {'REGISTER', 'UNDO'}
1076 option = EnumProperty(
1077 name="option",
1078 items=(
1079 ('PARENT', 'Select Parent', 'Select Parent Frame'),
1080 ('CHILD', 'Select Children', 'Select members of selected frame'),
1084 def execute(self, context):
1085 nodes, links = get_nodes_links(context)
1086 option = self.option
1087 selected = [node for node in nodes if node.select]
1088 if option == 'PARENT':
1089 for sel in selected:
1090 parent = sel.parent
1091 if parent:
1092 parent.select = True
1093 else: # option == 'CHILD'
1094 for sel in selected:
1095 children = [node for node in nodes if node.parent == sel]
1096 for kid in children:
1097 kid.select = True
1099 return {'FINISHED'}
1102 class DetachOutputs(Operator, NodeToolBase):
1103 bl_idname = "node.detach_outputs"
1104 bl_label = "Detach Outputs"
1105 bl_options = {'REGISTER', 'UNDO'}
1107 def execute(self, context):
1108 nodes, links = get_nodes_links(context)
1109 selected = context.selected_nodes
1110 bpy.ops.node.duplicate_move_keep_inputs()
1111 new_nodes = context.selected_nodes
1112 bpy.ops.node.select_all(action="DESELECT")
1113 for node in selected:
1114 node.select = True
1115 bpy.ops.node.delete_reconnect()
1116 for new_node in new_nodes:
1117 new_node.select = True
1118 bpy.ops.transform.translate('INVOKE_DEFAULT')
1120 return {'FINISHED'}
1123 class LinkToOutputNode(Operator, NodeToolBase):
1124 bl_idname = "node.link_to_output_node"
1125 bl_label = "Link to Output Node"
1126 bl_options = {'REGISTER', 'UNDO'}
1128 @classmethod
1129 def poll(cls, context):
1130 space = context.space_data
1131 valid = False
1132 if (space.type == 'NODE_EDITOR' and
1133 space.node_tree is not None and
1134 context.active_node is not None
1136 valid = True
1137 return valid
1139 def execute(self, context):
1140 nodes, links = get_nodes_links(context)
1141 active = nodes.active
1142 output_node = None
1143 for node in nodes:
1144 if (node.type == 'OUTPUT_MATERIAL' or\
1145 node.type == 'OUTPUT_WORLD' or\
1146 node.type == 'OUTPUT_LAMP' or\
1147 node.type == 'COMPOSITE'):
1148 output_node = node
1149 break
1150 if not output_node:
1151 bpy.ops.node.select_all(action="DESELECT")
1152 type = context.space_data.tree_type
1153 if type == 'ShaderNodeTree':
1154 output_node = nodes.new('ShaderNodeOutputMaterial')
1155 elif type == 'CompositorNodeTree':
1156 output_node = nodes.new('CompositorNodeComposite')
1157 output_node.location = active.location + Vector((300.0, 0.0))
1158 nodes.active = output_node
1159 if (output_node and active.outputs):
1160 output_index = 0
1161 for i, output in enumerate(active.outputs):
1162 if output.type == output_node.inputs[0].type:
1163 output_index = i
1164 break
1165 links.new(active.outputs[output_index], output_node.inputs[0])
1167 return {'FINISHED'}
1170 #############################################################
1171 # P A N E L S
1172 #############################################################
1174 class EfficiencyToolsPanel(Panel, NodeToolBase):
1175 bl_idname = "NODE_PT_efficiency_tools"
1176 bl_space_type = 'NODE_EDITOR'
1177 bl_region_type = 'UI'
1178 bl_label = "Efficiency Tools (Ctrl-SPACE)"
1180 def draw(self, context):
1181 type = context.space_data.tree_type
1182 layout = self.layout
1184 box = layout.box()
1185 box.menu(MergeNodesMenu.bl_idname)
1186 if type == 'ShaderNodeTree':
1187 box.operator(NodesAddTextureSetup.bl_idname, text="Add Image Texture (Ctrl T)")
1188 box.menu(BatchChangeNodesMenu.bl_idname, text="Batch Change...")
1189 box.menu(NodeAlignMenu.bl_idname, text="Align Nodes (Shift =)")
1190 box.menu(CopyToSelectedMenu.bl_idname, text="Copy to Selected (Shift-C)")
1191 box.operator(NodesClearLabel.bl_idname).option = True
1192 box.operator(DetachOutputs.bl_idname)
1193 box.menu(AddReroutesMenu.bl_idname, text="Add Reroutes ( / )")
1194 box.menu(NodesSwapMenu.bl_idname, text="Swap Nodes (Shift-S)")
1195 box.menu(LinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected ( \\ )")
1196 box.operator(LinkToOutputNode.bl_idname)
1199 #############################################################
1200 # M E N U S
1201 #############################################################
1203 class EfficiencyToolsMenu(Menu, NodeToolBase):
1204 bl_idname = "NODE_MT_node_tools_menu"
1205 bl_label = "Efficiency Tools"
1207 def draw(self, context):
1208 type = context.space_data.tree_type
1209 layout = self.layout
1210 layout.menu(MergeNodesMenu.bl_idname, text="Merge Selected Nodes")
1211 if type == 'ShaderNodeTree':
1212 layout.operator(NodesAddTextureSetup.bl_idname, text="Add Image Texture with coordinates")
1213 layout.menu(BatchChangeNodesMenu.bl_idname, text="Batch Change")
1214 layout.menu(NodeAlignMenu.bl_idname, text="Align Nodes")
1215 layout.menu(CopyToSelectedMenu.bl_idname, text="Copy to Selected")
1216 layout.operator(NodesClearLabel.bl_idname).option = True
1217 layout.operator(DetachOutputs.bl_idname)
1218 layout.menu(AddReroutesMenu.bl_idname, text="Add Reroutes")
1219 layout.menu(NodesSwapMenu.bl_idname, text="Swap Nodes")
1220 layout.menu(LinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected")
1221 layout.operator(LinkToOutputNode.bl_idname)
1224 class MergeNodesMenu(Menu, NodeToolBase):
1225 bl_idname = "NODE_MT_merge_nodes_menu"
1226 bl_label = "Merge Selected Nodes"
1228 def draw(self, context):
1229 type = context.space_data.tree_type
1230 layout = self.layout
1231 if type == 'ShaderNodeTree':
1232 layout.menu(MergeShadersMenu.bl_idname, text="Use Shaders")
1233 layout.menu(MergeMixMenu.bl_idname, text="Use Mix Nodes")
1234 layout.menu(MergeMathMenu.bl_idname, text="Use Math Nodes")
1237 class MergeShadersMenu(Menu, NodeToolBase):
1238 bl_idname = "NODE_MT_merge_shaders_menu"
1239 bl_label = "Merge Selected Nodes using Shaders"
1241 def draw(self, context):
1242 layout = self.layout
1243 for type in merge_shaders_types:
1244 props = layout.operator(MergeNodes.bl_idname, text=type)
1245 props.mode = type
1246 props.merge_type = 'SHADER'
1249 class MergeMixMenu(Menu, NodeToolBase):
1250 bl_idname = "NODE_MT_merge_mix_menu"
1251 bl_label = "Merge Selected Nodes using Mix"
1253 def draw(self, context):
1254 layout = self.layout
1255 for type, name, description in blend_types:
1256 props = layout.operator(MergeNodes.bl_idname, text=name)
1257 props.mode = type
1258 props.merge_type = 'MIX'
1261 class MergeMathMenu(Menu, NodeToolBase):
1262 bl_idname = "NODE_MT_merge_math_menu"
1263 bl_label = "Merge Selected Nodes using Math"
1265 def draw(self, context):
1266 layout = self.layout
1267 for type, name, description in operations:
1268 props = layout.operator(MergeNodes.bl_idname, text=name)
1269 props.mode = type
1270 props.merge_type = 'MATH'
1273 class BatchChangeNodesMenu(Menu, NodeToolBase):
1274 bl_idname = "NODE_MT_batch_change_nodes_menu"
1275 bl_label = "Batch Change Selected Nodes"
1277 def draw(self, context):
1278 layout = self.layout
1279 layout.menu(BatchChangeBlendTypeMenu.bl_idname)
1280 layout.menu(BatchChangeOperationMenu.bl_idname)
1283 class BatchChangeBlendTypeMenu(Menu, NodeToolBase):
1284 bl_idname = "NODE_MT_batch_change_blend_type_menu"
1285 bl_label = "Batch Change Blend Type"
1287 def draw(self, context):
1288 layout = self.layout
1289 for type, name, description in blend_types:
1290 props = layout.operator(BatchChangeNodes.bl_idname, text=name)
1291 props.blend_type = type
1292 props.operation = 'CURRENT'
1295 class BatchChangeOperationMenu(Menu, NodeToolBase):
1296 bl_idname = "NODE_MT_batch_change_operation_menu"
1297 bl_label = "Batch Change Math Operation"
1299 def draw(self, context):
1300 layout = self.layout
1301 for type, name, description in operations:
1302 props = layout.operator(BatchChangeNodes.bl_idname, text=name)
1303 props.blend_type = 'CURRENT'
1304 props.operation = type
1307 class CopyToSelectedMenu(Menu, NodeToolBase):
1308 bl_idname = "NODE_MT_copy_node_properties_menu"
1309 bl_label = "Copy to Selected"
1311 def draw(self, context):
1312 layout = self.layout
1313 layout.operator(NodesCopySettings.bl_idname, text="Settings from Active")
1314 layout.menu(CopyLabelMenu.bl_idname)
1317 class CopyLabelMenu(Menu, NodeToolBase):
1318 bl_idname = "NODE_MT_copy_label_menu"
1319 bl_label = "Copy Label"
1321 def draw(self, context):
1322 layout = self.layout
1323 layout.operator(NodesCopyLabel.bl_idname, text="from Active Node's Label").option = 'FROM_ACTIVE'
1324 layout.operator(NodesCopyLabel.bl_idname, text="from Linked Node's Label").option = 'FROM_NODE'
1325 layout.operator(NodesCopyLabel.bl_idname, text="from Linked Output's Name").option = 'FROM_SOCKET'
1328 class AddReroutesMenu(Menu, NodeToolBase):
1329 bl_idname = "NODE_MT_add_reroutes_menu"
1330 bl_label = "Add Reroutes"
1331 bl_description = "Add Reroute Nodes to Selected Nodes' Outputs"
1333 def draw(self, context):
1334 layout = self.layout
1335 layout.operator(NodesAddReroutes.bl_idname, text="to All Outputs").option = 'ALL'
1336 layout.operator(NodesAddReroutes.bl_idname, text="to Loose Outputs").option = 'LOOSE'
1337 layout.operator(NodesAddReroutes.bl_idname, text="to Linked Outputs").option = 'LINKED'
1340 class NodesSwapMenu(Menu, NodeToolBase):
1341 bl_idname = "NODE_MT_swap_menu"
1342 bl_label = "Swap Nodes"
1344 def draw(self, context):
1345 type = context.space_data.tree_type
1346 layout = self.layout
1347 if type == 'ShaderNodeTree':
1348 layout.menu(ShadersSwapMenu.bl_idname, text="Swap Shaders")
1349 layout.operator(NodesSwap.bl_idname, text="Change to Mix Nodes").option = 'NodeMixRGB'
1350 layout.operator(NodesSwap.bl_idname, text="Change to Math Nodes").option = 'NodeMath'
1351 if type == 'CompositorNodeTree':
1352 layout.operator(NodesSwap.bl_idname, text="Change to Alpha Over").option = 'CompositorNodeAlphaOver'
1353 if type == 'CompositorNodeTree':
1354 layout.operator(NodesSwap.bl_idname, text="Change to Switches").option = 'CompositorNodeSwitch'
1355 layout.operator(NodesSwap.bl_idname, text="Change to Reroutes").option = 'NodeReroute'
1358 class ShadersSwapMenu(Menu):
1359 bl_idname = "NODE_MT_shaders_swap_menu"
1360 bl_label = "Swap Shaders"
1362 @classmethod
1363 def poll(cls, context):
1364 space = context.space_data
1365 valid = False
1366 if space.type == 'NODE_EDITOR':
1367 if space.tree_type == 'ShaderNodeTree' and space.node_tree is not None:
1368 valid = True
1369 return valid
1371 def draw(self, context):
1372 layout = self.layout
1373 shaders = merge_shaders + regular_shaders
1374 for opt, type, txt in shaders:
1375 layout.operator(NodesSwap.bl_idname, text=txt).option = opt
1378 class LinkActiveToSelectedMenu(Menu, NodeToolBase):
1379 bl_idname = "NODE_MT_link_active_to_selected_menu"
1380 bl_label = "Link Active to Selected"
1382 def draw(self, context):
1383 layout = self.layout
1384 layout.menu(LinkStandardMenu.bl_idname)
1385 layout.menu(LinkUseNodeNameMenu.bl_idname)
1386 layout.menu(LinkUseOutputsNamesMenu.bl_idname)
1389 class LinkStandardMenu(Menu, NodeToolBase):
1390 bl_idname = "NODE_MT_link_standard_menu"
1391 bl_label = "To All Selected"
1393 def draw(self, context):
1394 layout = self.layout
1395 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Don't Replace Links")
1396 props.replace = False
1397 props.use_node_name = False
1398 props.use_outputs_names = False
1399 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Replace Links")
1400 props.replace = True
1401 props.use_node_name = False
1402 props.use_outputs_names = False
1405 class LinkUseNodeNameMenu(Menu, NodeToolBase):
1406 bl_idname = "NODE_MT_link_use_node_name_menu"
1407 bl_label = "Use Node Name/Label"
1409 def draw(self, context):
1410 layout = self.layout
1411 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Don't Replace Links")
1412 props.replace = False
1413 props.use_node_name = True
1414 props.use_outputs_names = False
1415 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Replace Links")
1416 props.replace = True
1417 props.use_node_name = True
1418 props.use_outputs_names = False
1421 class LinkUseOutputsNamesMenu(Menu, NodeToolBase):
1422 bl_idname = "NODE_MT_link_use_outputs_names_menu"
1423 bl_label = "Use Outputs Names"
1425 def draw(self, context):
1426 layout = self.layout
1427 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Don't Replace Links")
1428 props.replace = False
1429 props.use_node_name = False
1430 props.use_outputs_names = True
1431 props = layout.operator(NodesLinkActiveToSelected.bl_idname, text="Replace Links")
1432 props.replace = True
1433 props.use_node_name = False
1434 props.use_outputs_names = True
1437 class NodeAlignMenu(Menu, NodeToolBase):
1438 bl_idname = "NODE_MT_node_align_menu"
1439 bl_label = "Align Nodes"
1441 def draw(self, context):
1442 layout = self.layout
1443 layout.operator(AlignNodes.bl_idname, text="Horizontally").option = 'AXIS_X'
1444 layout.operator(AlignNodes.bl_idname, text="Vertically").option = 'AXIS_Y'
1447 #############################################################
1448 # MENU ITEMS
1449 #############################################################
1451 def select_parent_children_buttons(self, context):
1452 layout = self.layout
1453 layout.operator(SelectParentChildren.bl_idname, text="Select frame's members (children)").option = 'CHILD'
1454 layout.operator(SelectParentChildren.bl_idname, text="Select parent frame").option = 'PARENT'
1456 #############################################################
1457 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
1458 #############################################################
1460 addon_keymaps = []
1461 # kmi_defs entry: (identifier, key, CTRL, SHIFT, ALT, props)
1462 # props entry: (property name, property value)
1463 kmi_defs = (
1464 # MERGE NODES
1465 # MergeNodes with Ctrl (AUTO).
1466 (MergeNodes.bl_idname, 'NUMPAD_0', True, False, False,
1467 (('mode', 'MIX'), ('merge_type', 'AUTO'),)),
1468 (MergeNodes.bl_idname, 'ZERO', True, False, False,
1469 (('mode', 'MIX'), ('merge_type', 'AUTO'),)),
1470 (MergeNodes.bl_idname, 'NUMPAD_PLUS', True, False, False,
1471 (('mode', 'ADD'), ('merge_type', 'AUTO'),)),
1472 (MergeNodes.bl_idname, 'EQUAL', True, False, False,
1473 (('mode', 'ADD'), ('merge_type', 'AUTO'),)),
1474 (MergeNodes.bl_idname, 'NUMPAD_ASTERIX', True, False, False,
1475 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),)),
1476 (MergeNodes.bl_idname, 'EIGHT', True, False, False,
1477 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),)),
1478 (MergeNodes.bl_idname, 'NUMPAD_MINUS', True, False, False,
1479 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),)),
1480 (MergeNodes.bl_idname, 'MINUS', True, False, False,
1481 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),)),
1482 (MergeNodes.bl_idname, 'NUMPAD_SLASH', True, False, False,
1483 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),)),
1484 (MergeNodes.bl_idname, 'SLASH', True, False, False,
1485 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),)),
1486 (MergeNodes.bl_idname, 'COMMA', True, False, False,
1487 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),)),
1488 (MergeNodes.bl_idname, 'PERIOD', True, False, False,
1489 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),)),
1490 # MergeNodes with Ctrl Alt (MIX)
1491 (MergeNodes.bl_idname, 'NUMPAD_0', True, False, True,
1492 (('mode', 'MIX'), ('merge_type', 'MIX'),)),
1493 (MergeNodes.bl_idname, 'ZERO', True, False, True,
1494 (('mode', 'MIX'), ('merge_type', 'MIX'),)),
1495 (MergeNodes.bl_idname, 'NUMPAD_PLUS', True, False, True,
1496 (('mode', 'ADD'), ('merge_type', 'MIX'),)),
1497 (MergeNodes.bl_idname, 'EQUAL', True, False, True,
1498 (('mode', 'ADD'), ('merge_type', 'MIX'),)),
1499 (MergeNodes.bl_idname, 'NUMPAD_ASTERIX', True, False, True,
1500 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),)),
1501 (MergeNodes.bl_idname, 'EIGHT', True, False, True,
1502 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),)),
1503 (MergeNodes.bl_idname, 'NUMPAD_MINUS', True, False, True,
1504 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),)),
1505 (MergeNodes.bl_idname, 'MINUS', True, False, True,
1506 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),)),
1507 (MergeNodes.bl_idname, 'NUMPAD_SLASH', True, False, True,
1508 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),)),
1509 (MergeNodes.bl_idname, 'SLASH', True, False, True,
1510 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),)),
1511 # MergeNodes with Ctrl Shift (MATH)
1512 (MergeNodes.bl_idname, 'NUMPAD_PLUS', True, True, False,
1513 (('mode', 'ADD'), ('merge_type', 'MATH'),)),
1514 (MergeNodes.bl_idname, 'EQUAL', True, True, False,
1515 (('mode', 'ADD'), ('merge_type', 'MATH'),)),
1516 (MergeNodes.bl_idname, 'NUMPAD_ASTERIX', True, True, False,
1517 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),)),
1518 (MergeNodes.bl_idname, 'EIGHT', True, True, False,
1519 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),)),
1520 (MergeNodes.bl_idname, 'NUMPAD_MINUS', True, True, False,
1521 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),)),
1522 (MergeNodes.bl_idname, 'MINUS', True, True, False,
1523 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),)),
1524 (MergeNodes.bl_idname, 'NUMPAD_SLASH', True, True, False,
1525 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),)),
1526 (MergeNodes.bl_idname, 'SLASH', True, True, False,
1527 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),)),
1528 (MergeNodes.bl_idname, 'COMMA', True, True, False,
1529 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),)),
1530 (MergeNodes.bl_idname, 'PERIOD', True, True, False,
1531 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),)),
1532 # BATCH CHANGE NODES
1533 # BatchChangeNodes with Alt
1534 (BatchChangeNodes.bl_idname, 'NUMPAD_0', False, False, True,
1535 (('blend_type', 'MIX'), ('operation', 'CURRENT'),)),
1536 (BatchChangeNodes.bl_idname, 'ZERO', False, False, True,
1537 (('blend_type', 'MIX'), ('operation', 'CURRENT'),)),
1538 (BatchChangeNodes.bl_idname, 'NUMPAD_PLUS', False, False, True,
1539 (('blend_type', 'ADD'), ('operation', 'ADD'),)),
1540 (BatchChangeNodes.bl_idname, 'EQUAL', False, False, True,
1541 (('blend_type', 'ADD'), ('operation', 'ADD'),)),
1542 (BatchChangeNodes.bl_idname, 'NUMPAD_ASTERIX', False, False, True,
1543 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),)),
1544 (BatchChangeNodes.bl_idname, 'EIGHT', False, False, True,
1545 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),)),
1546 (BatchChangeNodes.bl_idname, 'NUMPAD_MINUS', False, False, True,
1547 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),)),
1548 (BatchChangeNodes.bl_idname, 'MINUS', False, False, True,
1549 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),)),
1550 (BatchChangeNodes.bl_idname, 'NUMPAD_SLASH', False, False, True,
1551 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),)),
1552 (BatchChangeNodes.bl_idname, 'SLASH', False, False, True,
1553 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),)),
1554 (BatchChangeNodes.bl_idname, 'COMMA', False, False, True,
1555 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),)),
1556 (BatchChangeNodes.bl_idname, 'PERIOD', False, False, True,
1557 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),)),
1558 (BatchChangeNodes.bl_idname, 'DOWN_ARROW', False, False, True,
1559 (('blend_type', 'NEXT'), ('operation', 'NEXT'),)),
1560 (BatchChangeNodes.bl_idname, 'UP_ARROW', False, False, True,
1561 (('blend_type', 'PREV'), ('operation', 'PREV'),)),
1562 # LINK ACTIVE TO SELECTED
1563 # Don't use names, don't replace links (K)
1564 (NodesLinkActiveToSelected.bl_idname, 'K', False, False, False,
1565 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),)),
1566 # Don't use names, replace links (Shift K)
1567 (NodesLinkActiveToSelected.bl_idname, 'K', False, True, False,
1568 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),)),
1569 # Use node name, don't replace links (')
1570 (NodesLinkActiveToSelected.bl_idname, 'QUOTE', False, False, False,
1571 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),)),
1572 # Don't use names, replace links (')
1573 (NodesLinkActiveToSelected.bl_idname, 'QUOTE', False, True, False,
1574 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),)),
1575 (NodesLinkActiveToSelected.bl_idname, 'SEMI_COLON', False, False, False,
1576 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),)),
1577 # Don't use names, replace links (')
1578 (NodesLinkActiveToSelected.bl_idname, 'SEMI_COLON', False, True, False,
1579 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),)),
1580 # CHANGE MIX FACTOR
1581 (ChangeMixFactor.bl_idname, 'LEFT_ARROW', False, False, True, (('option', -0.1),)),
1582 (ChangeMixFactor.bl_idname, 'RIGHT_ARROW', False, False, True, (('option', 0.1),)),
1583 (ChangeMixFactor.bl_idname, 'LEFT_ARROW', False, True, True, (('option', -0.01),)),
1584 (ChangeMixFactor.bl_idname, 'RIGHT_ARROW', False, True, True, (('option', 0.01),)),
1585 (ChangeMixFactor.bl_idname, 'LEFT_ARROW', True, True, True, (('option', 0.0),)),
1586 (ChangeMixFactor.bl_idname, 'RIGHT_ARROW', True, True, True, (('option', 1.0),)),
1587 (ChangeMixFactor.bl_idname, 'NUMPAD_0', True, True, True, (('option', 0.0),)),
1588 (ChangeMixFactor.bl_idname, 'ZERO', True, True, True, (('option', 0.0),)),
1589 (ChangeMixFactor.bl_idname, 'NUMPAD_1', True, True, True, (('option', 1.0),)),
1590 (ChangeMixFactor.bl_idname, 'ONE', True, True, True, (('option', 1.0),)),
1591 # CLEAR LABEL (Alt L)
1592 (NodesClearLabel.bl_idname, 'L', False, False, True, (('option', False),)),
1593 # DETACH OUTPUTS (Alt Shift D)
1594 (DetachOutputs.bl_idname, 'D', False, True, True, None),
1595 # LINK TO OUTPUT NODE (O)
1596 (LinkToOutputNode.bl_idname, 'O', False, False, False, None),
1597 # SELECT PARENT/CHILDREN
1598 # Select Children
1599 (SelectParentChildren.bl_idname, 'RIGHT_BRACKET', False, False, False, (('option', 'CHILD'),)),
1600 # Select Parent
1601 (SelectParentChildren.bl_idname, 'LEFT_BRACKET', False, False, False, (('option', 'PARENT'),)),
1602 # Add Texture Setup
1603 (NodesAddTextureSetup.bl_idname, 'T', True, False, False, None),
1604 # Copy Label from active to selected
1605 (NodesCopyLabel.bl_idname, 'V', False, True, False, (('option', 'FROM_ACTIVE'),)),
1606 # MENUS
1607 ('wm.call_menu', 'SPACE', True, False, False, (('name', EfficiencyToolsMenu.bl_idname),)),
1608 ('wm.call_menu', 'SLASH', False, False, False, (('name', AddReroutesMenu.bl_idname),)),
1609 ('wm.call_menu', 'NUMPAD_SLASH', False, False, False, (('name', AddReroutesMenu.bl_idname),)),
1610 ('wm.call_menu', 'EQUAL', False, True, False, (('name', NodeAlignMenu.bl_idname),)),
1611 ('wm.call_menu', 'BACK_SLASH', False, False, False, (('name', LinkActiveToSelectedMenu.bl_idname),)),
1612 ('wm.call_menu', 'C', False, True, False, (('name', CopyToSelectedMenu.bl_idname),)),
1613 ('wm.call_menu', 'S', False, True, False, (('name', NodesSwapMenu.bl_idname),)),
1617 def register():
1618 bpy.utils.register_module(__name__)
1619 km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
1620 for (identifier, key, CTRL, SHIFT, ALT, props) in kmi_defs:
1621 kmi = km.keymap_items.new(identifier, key, 'PRESS', ctrl=CTRL, shift=SHIFT, alt=ALT)
1622 if props:
1623 for prop, value in props:
1624 setattr(kmi.properties, prop, value)
1625 addon_keymaps.append((km, kmi))
1626 # menu items
1627 bpy.types.NODE_MT_select.append(select_parent_children_buttons)
1630 def unregister():
1631 bpy.utils.unregister_module(__name__)
1632 bpy.types.NODE_MT_select.remove(select_parent_children_buttons)
1633 for km, kmi in addon_keymaps:
1634 km.keymap_items.remove(kmi)
1635 addon_keymaps.clear()
1637 if __name__ == "__main__":
1638 register()