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 #####
20 'name': "Nodes Efficiency Tools",
21 'author': "Bartek Skorupa",
24 'location': "Node Editor Properties Panel (Ctrl-SPACE)",
25 'description': "Nodes Efficiency Tools",
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",
33 from bpy
.types
import Operator
, Panel
, Menu
34 from bpy
.props
import FloatProperty
, EnumProperty
, BoolProperty
35 from mathutils
import Vector
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)
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.
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.
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.
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.
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'),
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
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
159 is_main_tree
= context_active
== active
160 if not is_main_tree
: # if group is currently edited
161 tree
= active
.node_tree
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'}
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(
188 description
="Type of Merge to be used",
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
)
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':
228 if output_type
== type and valid_mode
:
229 dst
.append([i
, node
.location
.x
, node
.location
.y
])
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
245 for nodes_list
in [selected_mix
, selected_shader
, selected_math
]:
247 count_before
= len(nodes
)
248 # sort list by loc_x - reversed
249 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
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]
255 if nodes_list
== selected_shader
:
257 the_range
= len(nodes_list
) - 1
259 if len(nodes_list
) == 1:
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
271 add
.width_hidden
= 100.0
272 elif nodes_list
== selected_math
:
273 add_type
= node_type
+ 'Math'
274 add
= nodes
.new(add_type
)
279 add
.width_hidden
= 100.0
280 elif nodes_list
== selected_shader
:
282 add_type
= node_type
+ 'MixShader'
283 add
= nodes
.new(add_type
)
286 add
.width_hidden
= 100.0
288 add_type
= node_type
+ 'AddShader'
289 add
= nodes
.new(add_type
)
292 add
.width_hidden
= 100.0
293 add
.location
= loc_x
, loc_y
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])
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
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(
335 items
=blend_types
+ navs
,
337 operation
= EnumProperty(
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
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]
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]
362 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
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
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]
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)
382 node
.operation
= operations
[len(operations
) - 1][0]
384 node
.operation
= operations
[index
- 1][0]
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
)
403 selected
= [] # entry = index
404 for si
, node
in enumerate(nodes
):
406 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
410 fac
= nodes
[si
].inputs
[0]
411 nodes
[si
].hide
= False
412 if option
in {0.0, 1.0}:
413 fac
.default_value
= option
415 fac
.default_value
+= option
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'}
427 def poll(cls
, context
):
428 space
= context
.space_data
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'
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
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
452 bpy
.ops
.node
.duplicate()
453 copied
= nodes
.active
454 # Copied active should however inherit some properties from 'node'
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.
463 bpy
.ops
.node
.parent_clear()
464 locx
= node
.location
.x
465 locy
= node
.location
.y
466 # get absolute node location
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
):
476 link
= input.links
[0]
477 links
.new(link
.from_socket
, copied
.inputs
[i
])
478 for out
, output
in enumerate(node
.outputs
):
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')
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
)
490 bpy
.ops
.node
.select_all(action
='DESELECT')
491 for node
in reselect
:
493 nodes
.active
= active
498 class NodesCopyLabel(Operator
, NodeToolBase
):
499 bl_idname
= "node.copy_label"
500 bl_label
= "Copy Label"
501 bl_options
= {'REGISTER', 'UNDO'}
503 option
= EnumProperty(
505 description
="Source of name of label",
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
)
516 active
= nodes
.active
517 if option
== 'FROM_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
:
527 src
= input.links
[0].from_node
528 node
.label
= src
.label
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
:
535 src
= input.links
[0].from_socket
536 node
.label
= src
.name
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
]:
556 def invoke(self
, context
, event
):
558 return self
.execute(context
)
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'}
570 def poll(cls
, context
):
571 space
= context
.space_data
573 if space
.type == 'NODE_EDITOR':
574 if space
.tree_type
== 'ShaderNodeTree' and space
.node_tree
is not None:
578 def execute(self
, context
):
579 nodes
, links
= get_nodes_links(context
)
580 active
= nodes
.active
596 if not active
.inputs
[0].is_linked
:
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
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])
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(
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
635 nodes
, links
= get_nodes_links(context
)
636 # output valid when option is 'all' or when 'loose' output has no links
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
]:
645 # unhide 'REROUTE' nodes to avoid issues with location.y
646 if node
.type == 'REROUTE':
648 # When node is hidden - width_hidden not usable.
649 # Hack needed to calculate real width
651 bpy
.ops
.node
.select_all(action
='DESELECT')
652 helper
= nodes
.new('NodeReroute')
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
662 # only helper is selected now
663 bpy
.ops
.node
.delete()
664 x
= node
.location
.x
+ width
+ 20.0
665 if node
.type != 'REROUTE':
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':
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':
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
)
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.
693 n
= nodes
.new('NodeReroute')
695 for link
in output
.links
:
696 links
.new(n
.outputs
[0], link
.to_socket
)
697 links
.new(output
, n
.inputs
[0])
699 post_select
.append(n
)
703 # disselect the node so that after execution of script only newly created nodes are selected
705 # nicer reroutes distribution along y when 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
:
716 class NodesSwap(Operator
, NodeToolBase
):
717 bl_idname
= "node.swap_nodes"
718 bl_label
= "Swap Nodes"
719 bl_options
= {'REGISTER', 'UNDO'}
721 option
= EnumProperty(
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':
755 selected
= [n
for n
in nodes
if n
.select
]
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
]
767 elif option
== 'CompositorNodeSwitch':
768 replace_types
= ('REROUTE', 'MIX_RGB', 'MATH', 'ALPHAOVER')
770 elif option
== 'NodeReroute':
771 replace_types
= ('SWITCH')
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')
782 for node
in selected
:
783 if node
.type in replace_types
:
785 if node
.type == 'REROUTE':
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
796 new_node
.inputs
[i
+1].default_value
= [node
.inputs
[i
].default_value
] * 3 + [1.0]
797 elif node
.type in {'MIX_RGB', 'ALPHAOVER'}:
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
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.
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
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:
832 elif new_inputs_count
== 2:
833 if old_inputs_count
== 1:
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:
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))
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
))
857 nodes
.active
= new_node
858 reselect
.append(new_node
)
859 bpy
.ops
.node
.select_all(action
="DESELECT")
861 bpy
.ops
.node
.delete()
863 reselect
.append(node
)
864 for node
in reselect
:
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()
880 def poll(cls
, context
):
881 space
= context
.space_data
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
:
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':
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.
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
)
917 doit
= True # Will be changed to False when links successfully added to previous output.
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
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.
928 # Set src_name to source node name or label
929 src_name
= active
.name
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
:
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
:
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(
959 description
="Direction",
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
):
973 if node
.type == 'FRAME':
975 frames_reselect
.append(i
)
977 locx
= node
.location
.x
978 locy
= node
.location
.y
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')
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
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.
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
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
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(
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
:
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
:
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
:
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')
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'}
1129 def poll(cls
, context
):
1130 space
= context
.space_data
1132 if (space
.type == 'NODE_EDITOR' and
1133 space
.node_tree
is not None and
1134 context
.active_node
is not None
1139 def execute(self
, context
):
1140 nodes
, links
= get_nodes_links(context
)
1141 active
= nodes
.active
1144 if (node
.type == 'OUTPUT_MATERIAL' or\
1145 node
.type == 'OUTPUT_WORLD' or\
1146 node
.type == 'OUTPUT_LAMP' or\
1147 node
.type == 'COMPOSITE'):
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
):
1161 for i
, output
in enumerate(active
.outputs
):
1162 if output
.type == output_node
.inputs
[0].type:
1165 links
.new(active
.outputs
[output_index
], output_node
.inputs
[0])
1170 #############################################################
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
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 #############################################################
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)
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
)
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
)
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"
1363 def poll(cls
, context
):
1364 space
= context
.space_data
1366 if space
.type == 'NODE_EDITOR':
1367 if space
.tree_type
== 'ShaderNodeTree' and space
.node_tree
is not None:
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 #############################################################
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 #############################################################
1461 # kmi_defs entry: (identifier, key, CTRL, SHIFT, ALT, props)
1462 # props entry: (property name, property value)
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),)),
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
1599 (SelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', False, False, False, (('option', 'CHILD'),)),
1601 (SelectParentChildren
.bl_idname
, 'LEFT_BRACKET', False, False, False, (('option', 'PARENT'),)),
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'),)),
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
),)),
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
)
1623 for prop
, value
in props
:
1624 setattr(kmi
.properties
, prop
, value
)
1625 addon_keymaps
.append((km
, kmi
))
1627 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
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__":