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': "Node Wrangler (aka Nodes Efficiency Tools)",
21 'author': "Bartek Skorupa, Greg Zaal",
23 'blender': (2, 70, 0),
24 'location': "Node Editor Properties Panel or Ctrl-SPACE",
25 'description': "Various tools to enhance and speed up node-based workflow",
27 'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Nodes/Nodes_Efficiency_Tools",
29 'tracker_url': "https://developer.blender.org/T33543",
34 from bpy
.types
import Operator
, Panel
, Menu
35 from bpy
.props
import FloatProperty
, EnumProperty
, BoolProperty
, IntProperty
, StringProperty
, FloatVectorProperty
, CollectionProperty
36 from bpy_extras
.io_utils
import ImportHelper
37 from mathutils
import Vector
38 from math
import cos
, sin
, pi
, sqrt
39 from os
import listdir
43 # list of outputs of Input Render Layer
44 # with attributes determinig if pass is used,
45 # and MultiLayer EXR outputs names and corresponding render engines
47 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_internal, in_cycles)
49 ('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
50 ('use_pass_color', 'Color', 'Color', True, False),
51 ('use_pass_combined', 'Image', 'Combined', True, True),
52 ('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
53 ('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
54 ('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
55 ('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
56 ('use_pass_emit', 'Emit', 'Emit', True, False),
57 ('use_pass_environment', 'Environment', 'Env', True, False),
58 ('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
59 ('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
60 ('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
61 ('use_pass_indirect', 'Indirect', 'Indirect', True, False),
62 ('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
63 ('use_pass_mist', 'Mist', 'Mist', True, False),
64 ('use_pass_normal', 'Normal', 'Normal', True, True),
65 ('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
66 ('use_pass_reflection', 'Reflect', 'Reflect', True, False),
67 ('use_pass_refraction', 'Refract', 'Refract', True, False),
68 ('use_pass_shadow', 'Shadow', 'Shadow', True, True),
69 ('use_pass_specular', 'Specular', 'Spec', True, False),
70 ('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', False, True),
71 ('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', False, True),
72 ('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
73 ('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
74 ('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
75 ('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
76 ('use_pass_uv', 'UV', 'UV', True, True),
77 ('use_pass_vector', 'Speed', 'Vector', True, True),
78 ('use_pass_z', 'Z', 'Depth', True, True),
82 # (rna_type.identifier, type, rna_type.name)
83 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
84 shaders_input_nodes_props
= (
85 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
86 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
87 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
88 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
89 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
90 ('ShaderNodeRGB', 'RGB', 'RGB'),
91 ('ShaderNodeValue', 'VALUE', 'Value'),
92 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
93 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
94 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
95 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
96 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
97 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
98 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
100 # (rna_type.identifier, type, rna_type.name)
101 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
102 shaders_output_nodes_props
= (
103 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
104 ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp Output'),
105 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
107 # (rna_type.identifier, type, rna_type.name)
108 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
109 shaders_shader_nodes_props
= (
110 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
111 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
112 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
113 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
114 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
115 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
116 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
117 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
118 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
119 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
120 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
121 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
122 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
123 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
124 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
125 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
126 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
127 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
128 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
130 # (rna_type.identifier, type, rna_type.name)
131 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
132 shaders_texture_nodes_props
= (
133 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image'),
134 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment'),
135 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky'),
136 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise'),
137 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave'),
138 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
139 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
140 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient'),
141 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic'),
142 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker'),
143 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick')
145 # (rna_type.identifier, type, rna_type.name)
146 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
147 shaders_color_nodes_props
= (
148 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
149 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
150 ('ShaderNodeInvert', 'INVERT', 'Invert'),
151 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
152 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
153 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
154 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
156 # (rna_type.identifier, type, rna_type.name)
157 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
158 shaders_vector_nodes_props
= (
159 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
160 ('ShaderNodeBump', 'BUMP', 'Bump'),
161 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
162 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
163 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
164 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
166 # (rna_type.identifier, type, rna_type.name)
167 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
168 shaders_converter_nodes_props
= (
169 ('ShaderNodeMath', 'MATH', 'Math'),
170 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
171 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
172 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
173 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
174 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
175 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
176 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
177 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
178 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
180 # (rna_type.identifier, type, rna_type.name)
181 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
182 shaders_layout_nodes_props
= (
183 ('NodeFrame', 'FRAME', 'Frame'),
184 ('NodeReroute', 'REROUTE', 'Reroute'),
188 # (rna_type.identifier, type, rna_type.name)
189 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
190 compo_input_nodes_props
= (
191 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
192 ('CompositorNodeImage', 'IMAGE', 'Image'),
193 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
194 ('CompositorNodeMask', 'MASK', 'Mask'),
195 ('CompositorNodeRGB', 'RGB', 'RGB'),
196 ('CompositorNodeValue', 'VALUE', 'Value'),
197 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
198 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
199 ('CompositorNodeTime', 'TIME', 'Time'),
200 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
202 # (rna_type.identifier, type, rna_type.name)
203 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
204 compo_output_nodes_props
= (
205 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
206 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
207 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
208 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
209 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
211 # (rna_type.identifier, type, rna_type.name)
212 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
213 compo_color_nodes_props
= (
214 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
215 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
216 ('CompositorNodeInvert', 'INVERT', 'Invert'),
217 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
218 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
219 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
220 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
221 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
222 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
223 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
224 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
225 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
227 # (rna_type.identifier, type, rna_type.name)
228 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
229 compo_converter_nodes_props
= (
230 ('CompositorNodeMath', 'MATH', 'Math'),
231 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
232 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
233 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
234 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
235 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
236 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
237 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
238 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
239 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
240 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
241 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
242 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
243 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
245 # (rna_type.identifier, type, rna_type.name)
246 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
247 compo_filter_nodes_props
= (
248 ('CompositorNodeBlur', 'BLUR', 'Blur'),
249 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
250 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
251 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
252 ('CompositorNodeFilter', 'FILTER', 'Filter'),
253 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
254 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
255 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
256 ('CompositorNodeGlare', 'GLARE', 'Glare'),
257 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
258 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
259 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
261 # (rna_type.identifier, type, rna_type.name)
262 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
263 compo_vector_nodes_props
= (
264 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
265 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
266 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
267 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
268 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
270 # (rna_type.identifier, type, rna_type.name)
271 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
272 compo_matte_nodes_props
= (
273 ('CompositorNodeKeying', 'KEYING', 'Keying'),
274 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
275 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
276 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
277 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
278 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
279 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
280 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
281 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
282 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
283 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
284 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
286 # (rna_type.identifier, type, rna_type.name)
287 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
288 compo_distort_nodes_props
= (
289 ('CompositorNodeScale', 'SCALE', 'Scale'),
290 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
291 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
292 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
293 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
294 ('CompositorNodeFlip', 'FLIP', 'Flip'),
295 ('CompositorNodeCrop', 'CROP', 'Crop'),
296 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
297 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
298 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
299 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
300 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
302 # (rna_type.identifier, type, rna_type.name)
303 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
304 compo_layout_nodes_props
= (
305 ('NodeFrame', 'FRAME', 'Frame'),
306 ('NodeReroute', 'REROUTE', 'Reroute'),
307 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
310 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
311 # used list, not tuple for easy merging with other lists.
313 ('MIX', 'Mix', 'Mix Mode'),
314 ('ADD', 'Add', 'Add Mode'),
315 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
316 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
317 ('SCREEN', 'Screen', 'Screen Mode'),
318 ('DIVIDE', 'Divide', 'Divide Mode'),
319 ('DIFFERENCE', 'Difference', 'Difference Mode'),
320 ('DARKEN', 'Darken', 'Darken Mode'),
321 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
322 ('OVERLAY', 'Overlay', 'Overlay Mode'),
323 ('DODGE', 'Dodge', 'Dodge Mode'),
324 ('BURN', 'Burn', 'Burn Mode'),
325 ('HUE', 'Hue', 'Hue Mode'),
326 ('SATURATION', 'Saturation', 'Saturation Mode'),
327 ('VALUE', 'Value', 'Value Mode'),
328 ('COLOR', 'Color', 'Color Mode'),
329 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
330 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
333 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
334 # used list, not tuple for easy merging with other lists.
336 ('ADD', 'Add', 'Add Mode'),
337 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
338 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
339 ('DIVIDE', 'Divide', 'Divide Mode'),
340 ('SINE', 'Sine', 'Sine Mode'),
341 ('COSINE', 'Cosine', 'Cosine Mode'),
342 ('TANGENT', 'Tangent', 'Tangent Mode'),
343 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
344 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
345 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
346 ('POWER', 'Power', 'Power Mode'),
347 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
348 ('MINIMUM', 'Minimum', 'Minimum Mode'),
349 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
350 ('ROUND', 'Round', 'Round Mode'),
351 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
352 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
355 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
356 # used list, not tuple for easy merging with other lists.
358 ('CURRENT', 'Current', 'Leave at current state'),
359 ('NEXT', 'Next', 'Next blend type/operation'),
360 ('PREV', 'Prev', 'Previous blend type/operation'),
365 (1.0, 1.0, 1.0, 0.7),
366 (1.0, 0.0, 0.0, 0.7),
370 (0.0, 0.0, 0.0, 1.0),
371 (0.38, 0.77, 0.38, 1.0),
372 (0.38, 0.77, 0.38, 1.0)
375 (0.0, 0.0, 0.0, 1.0),
376 (0.77, 0.77, 0.16, 1.0),
377 (0.77, 0.77, 0.16, 1.0)
380 (0.0, 0.0, 0.0, 1.0),
381 (0.38, 0.38, 0.77, 1.0),
382 (0.38, 0.38, 0.77, 1.0)
385 (0.0, 0.0, 0.0, 1.0),
386 (0.63, 0.63, 0.63, 1.0),
387 (0.63, 0.63, 0.63, 1.0)
390 (1.0, 1.0, 1.0, 0.7),
391 (0.0, 0.0, 0.0, 0.7),
397 def nice_hotkey_name(punc
):
398 # convert the ugly string name into the actual character
400 ('LEFTMOUSE', "LMB"),
401 ('MIDDLEMOUSE', "MMB"),
402 ('RIGHTMOUSE', "RMB"),
403 ('SELECTMOUSE', "Select"),
404 ('WHEELUPMOUSE', "Wheel Up"),
405 ('WHEELDOWNMOUSE', "Wheel Down"),
406 ('WHEELINMOUSE', "Wheel In"),
407 ('WHEELOUTMOUSE', "Wheel Out"),
420 ('LINE_FEED', "Enter"),
427 ('BACK_SLASH', "\\"),
429 ('NUMPAD_1', "Numpad 1"),
430 ('NUMPAD_2', "Numpad 2"),
431 ('NUMPAD_3', "Numpad 3"),
432 ('NUMPAD_4', "Numpad 4"),
433 ('NUMPAD_5', "Numpad 5"),
434 ('NUMPAD_6', "Numpad 6"),
435 ('NUMPAD_7', "Numpad 7"),
436 ('NUMPAD_8', "Numpad 8"),
437 ('NUMPAD_9', "Numpad 9"),
438 ('NUMPAD_0', "Numpad 0"),
439 ('NUMPAD_PERIOD', "Numpad ."),
440 ('NUMPAD_SLASH', "Numpad /"),
441 ('NUMPAD_ASTERIX', "Numpad *"),
442 ('NUMPAD_MINUS', "Numpad -"),
443 ('NUMPAD_ENTER', "Numpad Enter"),
444 ('NUMPAD_PLUS', "Numpad +"),
447 for (ugly
, nice
) in pairs
:
452 nice_punc
= punc
.replace("_", " ").title()
456 def hack_force_update(context
, nodes
):
457 if context
.space_data
.tree_type
== "ShaderNodeTree":
458 node
= nodes
.new('ShaderNodeMath')
459 node
.inputs
[0].default_value
= 0.0
465 return bpy
.context
.user_preferences
.system
.dpi
/72
468 def is_end_node(node
):
470 for output
in node
.outputs
:
477 def node_mid_pt(node
, axis
):
479 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
481 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
487 def autolink(node1
, node2
, links
):
490 for outp
in node1
.outputs
:
491 for inp
in node2
.inputs
:
492 if not inp
.is_linked
and inp
.name
== outp
.name
:
497 for outp
in node1
.outputs
:
498 for inp
in node2
.inputs
:
499 if not inp
.is_linked
and inp
.type == outp
.type:
504 # force some connection even if the type doesn't match
505 for outp
in node1
.outputs
:
506 for inp
in node2
.inputs
:
507 if not inp
.is_linked
:
512 # even if no sockets are open, force one of matching type
513 for outp
in node1
.outputs
:
514 for inp
in node2
.inputs
:
515 if inp
.type == outp
.type:
521 for outp
in node1
.outputs
:
522 for inp
in node2
.inputs
:
527 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
531 def node_at_pos(nodes
, context
, event
):
532 nodes_near_mouse
= []
533 nodes_under_mouse
= []
536 store_mouse_cursor(context
, event
)
537 x
, y
= context
.space_data
.cursor_location
541 # Make a list of each corner (and middle of border) for each node.
542 # Will be sorted to find nearest point and thus nearest node
543 node_points_with_dist
= []
546 if node
.type != 'FRAME': # no point trying to link to a frame node
547 locx
= node
.location
.x
548 locy
= node
.location
.y
549 dimx
= node
.dimensions
.x
/dpifac()
550 dimy
= node
.dimensions
.y
/dpifac()
552 locx
+= node
.parent
.location
.x
553 locy
+= node
.parent
.location
.y
554 if node
.parent
.parent
:
555 locx
+= node
.parent
.parent
.location
.x
556 locy
+= node
.parent
.parent
.location
.y
557 if node
.parent
.parent
.parent
:
558 locx
+= node
.parent
.parent
.parent
.location
.x
559 locy
+= node
.parent
.parent
.parent
.location
.y
560 if node
.parent
.parent
.parent
.parent
:
561 # Support three levels or parenting
562 # There's got to be a better way to do this...
565 node_points_with_dist
.append([node
, sqrt((x
- locx
) ** 2 + (y
- locy
) ** 2)]) # Top Left
566 node_points_with_dist
.append([node
, sqrt((x
- (locx
+dimx
)) ** 2 + (y
- locy
) ** 2)]) # Top Right
567 node_points_with_dist
.append([node
, sqrt((x
- locx
) ** 2 + (y
- (locy
-dimy
)) ** 2)]) # Bottom Left
568 node_points_with_dist
.append([node
, sqrt((x
- (locx
+dimx
)) ** 2 + (y
- (locy
-dimy
)) ** 2)]) # Bottom Right
570 node_points_with_dist
.append([node
, sqrt((x
- (locx
+(dimx
/2))) ** 2 + (y
- locy
) ** 2)]) # Mid Top
571 node_points_with_dist
.append([node
, sqrt((x
- (locx
+(dimx
/2))) ** 2 + (y
- (locy
-dimy
)) ** 2)]) # Mid Bottom
572 node_points_with_dist
.append([node
, sqrt((x
- locx
) ** 2 + (y
- (locy
-(dimy
/2))) ** 2)]) # Mid Left
573 node_points_with_dist
.append([node
, sqrt((x
- (locx
+dimx
)) ** 2 + (y
- (locy
-(dimy
/2))) ** 2)]) # Mid Right
575 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
578 if node
.type != 'FRAME' and skipnode
== False:
579 locx
= node
.location
.x
580 locy
= node
.location
.y
581 dimx
= node
.dimensions
.x
/dpifac()
582 dimy
= node
.dimensions
.y
/dpifac()
584 locx
+= node
.parent
.location
.x
585 locy
+= node
.parent
.location
.y
586 if (locx
<= x
<= locx
+ dimx
) and \
587 (locy
- dimy
<= y
<= locy
):
588 nodes_under_mouse
.append(node
)
590 if len(nodes_under_mouse
) == 1:
591 if nodes_under_mouse
[0] != nearest_node
:
592 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
594 target_node
= nearest_node
# else use the nearest node
596 target_node
= nearest_node
600 def store_mouse_cursor(context
, event
):
601 space
= context
.space_data
602 v2d
= context
.region
.view2d
603 tree
= space
.edit_tree
605 # convert mouse position to the View2D for later node placement
606 if context
.region
.type == 'WINDOW':
607 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
609 space
.cursor_location
= tree
.view_center
612 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=[1.0, 1.0, 1.0, 0.7]):
613 bgl
.glEnable(bgl
.GL_BLEND
)
614 bgl
.glLineWidth(size
)
615 bgl
.glShadeModel(bgl
.GL_SMOOTH
)
617 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
619 bgl
.glColor4f(colour
[0]+(1.0-colour
[0])/4, colour
[1]+(1.0-colour
[1])/4, colour
[2]+(1.0-colour
[2])/4, colour
[3]+(1.0-colour
[3])/4)
620 bgl
.glVertex2f(x1
, y1
)
621 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
622 bgl
.glVertex2f(x2
, y2
)
626 bgl
.glShadeModel(bgl
.GL_FLAT
)
629 def draw_circle(mx
, my
, radius
, colour
=[1.0, 1.0, 1.0, 0.7]):
630 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
631 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
634 for i
in range(sides
+ 1):
635 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
636 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
637 bgl
.glVertex2f(cosine
, sine
)
641 def draw_rounded_node_border(node
, radius
=8, colour
=[1.0, 1.0, 1.0, 0.7]):
642 bgl
.glEnable(bgl
.GL_BLEND
)
643 settings
= bpy
.context
.user_preferences
.addons
[__name__
].preferences
644 if settings
.bgl_antialiasing
:
645 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
647 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
649 nlocx
= (node
.location
.x
+1)*dpifac()
650 nlocy
= (node
.location
.y
+1)*dpifac()
651 ndimx
= node
.dimensions
.x
652 ndimy
= node
.dimensions
.y
654 nlocx
+= node
.parent
.location
.x
655 nlocy
+= node
.parent
.location
.y
656 if node
.parent
.parent
:
657 nlocx
+= node
.parent
.parent
.location
.x
658 nlocy
+= node
.parent
.parent
.location
.y
659 if node
.parent
.parent
.parent
:
660 nlocx
+= node
.parent
.parent
.parent
.location
.x
661 nlocy
+= node
.parent
.parent
.parent
.location
.y
664 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
665 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
)
666 bgl
.glVertex2f(mx
,my
)
667 for i
in range(sides
+1):
669 if mx
!= 12000 and my
!= 12000: # nodes that go over the view border give 12000 as coords
670 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
671 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
672 bgl
.glVertex2f(cosine
, sine
)
675 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
676 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
)
677 bgl
.glVertex2f(mx
,my
)
678 for i
in range(sides
+1):
680 if mx
!= 12000 and my
!= 12000:
681 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
682 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
683 bgl
.glVertex2f(cosine
, sine
)
686 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
687 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
)
688 bgl
.glVertex2f(mx
,my
)
689 for i
in range(sides
+1):
691 if mx
!= 12000 and my
!= 12000:
692 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
693 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
694 bgl
.glVertex2f(cosine
, sine
)
697 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
698 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
)
699 bgl
.glVertex2f(mx
,my
)
700 for i
in range(sides
+1):
702 if mx
!= 12000 and my
!= 12000:
703 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
704 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
705 bgl
.glVertex2f(cosine
, sine
)
709 bgl
.glBegin(bgl
.GL_QUADS
)
710 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
)
711 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
)
712 if m1x
!= 12000 and m1y
!= 12000 and m2x
!= 12000 and m2y
!= 12000:
713 bgl
.glVertex2f(m2x
-radius
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
714 bgl
.glVertex2f(m2x
,m2y
)
715 bgl
.glVertex2f(m1x
,m1y
)
716 bgl
.glVertex2f(m1x
-radius
,m1y
)
719 bgl
.glBegin(bgl
.GL_QUADS
)
720 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
)
721 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
)
722 if m1x
!= 12000 and m1y
!= 12000 and m2x
!= 12000 and m2y
!= 12000:
723 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
724 bgl
.glVertex2f(m2x
,m2y
)
725 bgl
.glVertex2f(m2x
,m1y
+radius
)
726 bgl
.glVertex2f(m1x
,m1y
+radius
)
729 bgl
.glBegin(bgl
.GL_QUADS
)
730 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
)
731 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
)
732 if m1x
!= 12000 and m1y
!= 12000 and m2x
!= 12000 and m2y
!= 12000:
733 bgl
.glVertex2f(m2x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
734 bgl
.glVertex2f(m2x
+radius
,m2y
)
735 bgl
.glVertex2f(m1x
+radius
,m1y
)
736 bgl
.glVertex2f(m1x
,m1y
)
739 bgl
.glBegin(bgl
.GL_QUADS
)
740 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
)
741 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
)
742 if m1x
!= 12000 and m1y
!= 12000 and m2x
!= 12000 and m2y
!= 12000:
743 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
744 bgl
.glVertex2f(m2x
,m2y
)
745 bgl
.glVertex2f(m2x
,m1y
-radius
)
746 bgl
.glVertex2f(m1x
,m1y
-radius
)
749 bgl
.glDisable(bgl
.GL_BLEND
)
750 if settings
.bgl_antialiasing
:
751 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
754 def draw_callback_mixnodes(self
, context
, mode
):
756 nodes
= context
.space_data
.node_tree
.nodes
757 settings
= context
.user_preferences
.addons
[__name__
].preferences
758 if settings
.bgl_antialiasing
:
759 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
762 col_outer
= [1.0, 0.2, 0.2, 0.4]
763 col_inner
= [0.0, 0.0, 0.0, 0.5]
764 col_circle_inner
= [0.3, 0.05, 0.05, 1.0]
765 if mode
== "LINKMENU":
766 col_outer
= [0.4, 0.6, 1.0, 0.4]
767 col_inner
= [0.0, 0.0, 0.0, 0.5]
768 col_circle_inner
= [0.08, 0.15, .3, 1.0]
770 col_outer
= [0.2, 1.0, 0.2, 0.4]
771 col_inner
= [0.0, 0.0, 0.0, 0.5]
772 col_circle_inner
= [0.05, 0.3, 0.05, 1.0]
774 m1x
= self
.mouse_path
[0][0]
775 m1y
= self
.mouse_path
[0][1]
776 m2x
= self
.mouse_path
[-1][0]
777 m2y
= self
.mouse_path
[-1][1]
779 n1
= nodes
[context
.scene
.NWLazySource
]
780 n2
= nodes
[context
.scene
.NWLazyTarget
]
782 draw_rounded_node_border(n1
, radius
=6, colour
=col_outer
) # outline
783 draw_rounded_node_border(n1
, radius
=5, colour
=col_inner
) # inner
784 draw_rounded_node_border(n2
, radius
=6, colour
=col_outer
) # outline
785 draw_rounded_node_border(n2
, radius
=5, colour
=col_inner
) # inner
787 draw_line(m1x
, m1y
, m2x
, m2y
, 4, col_outer
) # line outline
788 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
791 draw_circle(m1x
, m1y
, 6, col_outer
)
792 draw_circle(m2x
, m2y
, 6, col_outer
)
795 draw_circle(m1x
, m1y
, 5, col_circle_inner
)
796 draw_circle(m2x
, m2y
, 5, col_circle_inner
)
798 # restore opengl defaults
800 bgl
.glDisable(bgl
.GL_BLEND
)
801 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
803 if settings
.bgl_antialiasing
:
804 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
807 def get_nodes_links(context
):
808 space
= context
.space_data
809 tree
= space
.node_tree
812 active
= nodes
.active
813 context_active
= context
.active_node
814 # check if we are working on regular node tree or node group is currently edited.
815 # if group is edited - active node of space_tree is the group
816 # if context.active_node != space active node - it means that the group is being edited.
817 # in such case we set "nodes" to be nodes of this group, "links" to be links of this group
818 # if context.active_node == space.active_node it means that we are not currently editing group
821 is_main_tree
= context_active
== active
822 if not is_main_tree
: # if group is currently edited
823 tree
= active
.node_tree
831 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
834 merge_hide
= EnumProperty(
835 name
="Hide Mix nodes",
837 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
838 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
839 ("NEVER", "Never", "Never collapse the new merge nodes")
841 default
='NON_SHADER',
842 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy whether to collapse them or show the full node with options expanded")
843 merge_position
= EnumProperty(
844 name
="Mix Node Position",
846 ("CENTER", "Center", "Place the Mix node between the two nodes"),
847 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
850 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy the position of the new nodes")
851 bgl_antialiasing
= BoolProperty(
852 name
="Line Antialiasing",
854 description
="Remove aliasing artifacts on lines drawn in interactive modes such as Lazy Connect (Alt+LMB) and Lazy Merge (Alt+RMB) - this may cause issues on some systems"
857 show_hotkey_list
= BoolProperty(
858 name
="Show Hotkey List",
860 description
="Expand this box into a list of all the hotkeys for functions in this addon"
862 hotkey_list_filter
= StringProperty(
863 name
=" Filter by Name",
865 description
="Show only hotkeys that have this text in their name"
868 def draw(self
, context
):
870 col
= layout
.column()
871 col
.prop(self
, "merge_position")
872 col
.prop(self
, "merge_hide")
873 col
.prop(self
, "bgl_antialiasing")
876 col
= box
.column(align
=True)
878 hotkey_button_name
= "Show Hotkey List"
879 if self
.show_hotkey_list
:
880 hotkey_button_name
= "Hide Hotkey List"
881 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
882 if self
.show_hotkey_list
:
883 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
885 for hotkey
in kmi_defs
:
887 hotkey_name
= hotkey
[6]
889 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
890 row
= col
.row(align
=True)
891 row
.label(hotkey_name
)
892 keystr
= nice_hotkey_name(hotkey
[1])
894 keystr
= "Shift " + keystr
896 keystr
= "Alt " + keystr
898 keystr
= "Ctrl " + keystr
904 def poll(cls
, context
):
905 space
= context
.space_data
906 return space
.type == 'NODE_EDITOR' and space
.node_tree
is not None
910 class NWLazyMix(Operator
, NWBase
):
911 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
912 bl_idname
= "node.nw_lazy_mix"
913 bl_label
= "Mix Nodes"
914 bl_options
= {'REGISTER', 'UNDO'}
916 def modal(self
, context
, event
):
917 context
.area
.tag_redraw()
918 nodes
, links
= get_nodes_links(context
)
921 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
924 if not context
.scene
.NWBusyDrawing
:
925 node1
= node_at_pos(nodes
, context
, event
)
927 context
.scene
.NWBusyDrawing
= node1
.name
929 if context
.scene
.NWBusyDrawing
!= 'STOP':
930 node1
= nodes
[context
.scene
.NWBusyDrawing
]
932 context
.scene
.NWLazySource
= node1
.name
933 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
935 if event
.type == 'MOUSEMOVE':
936 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
938 elif event
.type == 'RIGHTMOUSE':
939 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
940 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
943 node2
= node_at_pos(nodes
, context
, event
)
945 context
.scene
.NWBusyDrawing
= node2
.name
957 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
959 context
.scene
.NWBusyDrawing
= ""
962 elif event
.type == 'ESC':
964 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
967 return {'RUNNING_MODAL'}
969 def invoke(self
, context
, event
):
970 if context
.area
.type == 'NODE_EDITOR':
971 # the arguments we pass the the callback
972 args
= (self
, context
, 'MIX')
973 # Add the region OpenGL drawing callback
974 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
975 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_mixnodes
, args
, 'WINDOW', 'POST_PIXEL')
979 context
.window_manager
.modal_handler_add(self
)
980 return {'RUNNING_MODAL'}
982 self
.report({'WARNING'}, "View3D not found, cannot run operator")
986 class NWLazyConnect(Operator
, NWBase
):
987 """Connect two nodes without clicking a specific socket (automatically determined"""
988 bl_idname
= "node.nw_lazy_connect"
989 bl_label
= "Lazy Connect"
990 bl_options
= {'REGISTER', 'UNDO'}
991 with_menu
= BoolProperty()
993 def modal(self
, context
, event
):
994 context
.area
.tag_redraw()
995 nodes
, links
= get_nodes_links(context
)
998 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1001 if not context
.scene
.NWBusyDrawing
:
1002 node1
= node_at_pos(nodes
, context
, event
)
1004 context
.scene
.NWBusyDrawing
= node1
.name
1006 if context
.scene
.NWBusyDrawing
!= 'STOP':
1007 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1009 context
.scene
.NWLazySource
= node1
.name
1010 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1012 if event
.type == 'MOUSEMOVE':
1013 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1015 elif event
.type == 'RIGHTMOUSE':
1016 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1017 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1020 node2
= node_at_pos(nodes
, context
, event
)
1022 context
.scene
.NWBusyDrawing
= node2
.name
1027 link_success
= False
1033 if node
.select
== True:
1035 original_sel
.append(node
)
1037 original_unsel
.append(node
)
1041 #link_success = autolink(node1, node2, links)
1043 if len(node1
.outputs
) > 1 and node2
.inputs
:
1044 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1045 elif len(node1
.outputs
) == 1:
1046 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1048 link_success
= autolink(node1
, node2
, links
)
1050 for node
in original_sel
:
1052 for node
in original_unsel
:
1056 hack_force_update(context
, nodes
)
1057 context
.scene
.NWBusyDrawing
= ""
1060 elif event
.type == 'ESC':
1061 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1062 return {'CANCELLED'}
1064 return {'RUNNING_MODAL'}
1066 def invoke(self
, context
, event
):
1067 if context
.area
.type == 'NODE_EDITOR':
1068 nodes
, links
= get_nodes_links(context
)
1069 node
= node_at_pos(nodes
, context
, event
)
1071 context
.scene
.NWBusyDrawing
= node
.name
1073 # the arguments we pass the the callback
1077 args
= (self
, context
, mode
)
1078 # Add the region OpenGL drawing callback
1079 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1080 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_mixnodes
, args
, 'WINDOW', 'POST_PIXEL')
1082 self
.mouse_path
= []
1084 context
.window_manager
.modal_handler_add(self
)
1085 return {'RUNNING_MODAL'}
1087 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1088 return {'CANCELLED'}
1091 class NWDeleteUnused(Operator
, NWBase
):
1092 """Delete all nodes whose output is not used"""
1093 bl_idname
= 'node.nw_del_unused'
1094 bl_label
= 'Delete Unused Nodes'
1095 bl_options
= {'REGISTER', 'UNDO'}
1098 def poll(cls
, context
):
1100 if context
.space_data
:
1101 if context
.space_data
.node_tree
:
1102 if context
.space_data
.node_tree
.nodes
:
1106 def execute(self
, context
):
1107 nodes
, links
= get_nodes_links(context
)
1108 end_types
= 'OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1109 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LAMP', \
1110 'OUTPUT_WORLD', 'GROUP', 'GROUP_INPUT', 'GROUP_OUTPUT'
1115 if node
.select
== True:
1116 selection
.append(node
.name
)
1119 temp_deleted_nodes
= []
1120 del_unused_iterations
= len(nodes
)
1121 for it
in range(0, del_unused_iterations
):
1122 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1126 if is_end_node(node
) and not node
.type in end_types
and node
.type != 'FRAME':
1128 deleted_nodes
.append(node
.name
)
1129 bpy
.ops
.node
.delete()
1131 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1133 # get unique list of deleted nodes (iterations would count the same node more than once)
1134 deleted_nodes
= list(set(deleted_nodes
))
1135 for n
in deleted_nodes
:
1136 self
.report({'INFO'}, "Node " + n
+ " deleted")
1137 num_deleted
= len(deleted_nodes
)
1142 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1144 self
.report({'INFO'}, "Nothing deleted")
1147 nodes
, links
= get_nodes_links(context
)
1149 if node
.name
in selection
:
1153 def invoke(self
, context
, event
):
1154 return context
.window_manager
.invoke_confirm(self
, event
)
1157 class NWSwapLinks(Operator
, NWBase
):
1158 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1159 bl_idname
= 'node.nw_swap_links'
1160 bl_label
= 'Swap Links'
1161 bl_options
= {'REGISTER', 'UNDO'}
1164 def poll(cls
, context
):
1165 snode
= context
.space_data
1166 if context
.selected_nodes
:
1167 return len(context
.selected_nodes
) <= 2
1171 def execute(self
, context
):
1172 nodes
, links
= get_nodes_links(context
)
1173 selected_nodes
= context
.selected_nodes
1174 n1
= selected_nodes
[0]
1177 if len(selected_nodes
) == 2:
1178 n2
= selected_nodes
[1]
1179 if n1
.outputs
and n2
.outputs
:
1184 for output
in n1
.outputs
:
1186 for link
in output
.links
:
1187 n1_outputs
.append([out_index
, link
.to_socket
])
1192 for output
in n2
.outputs
:
1194 for link
in output
.links
:
1195 n2_outputs
.append([out_index
, link
.to_socket
])
1199 for connection
in n1_outputs
:
1201 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1203 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1204 for connection
in n2_outputs
:
1206 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1208 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1210 if n1
.outputs
or n2
.outputs
:
1211 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1213 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1216 elif len(selected_nodes
) == 1:
1220 for i1
in n1
.inputs
:
1223 for i2
in n1
.inputs
:
1224 if i1
.type == i2
.type and i2
.is_linked
:
1226 types
.append ([i1
, similar_types
, i
])
1228 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1233 for i2
in n1
.inputs
:
1234 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1236 i1f
= pair
[0].links
[0].from_socket
1237 i1t
= pair
[0].links
[0].to_socket
1238 i2f
= pair
[1].links
[0].from_socket
1239 i2t
= pair
[1].links
[0].to_socket
1244 fs
= t
[0].links
[0].from_socket
1246 links
.remove(t
[0].links
[0])
1247 if i
+1 == len(n1
.inputs
):
1250 while n1
.inputs
[i
].is_linked
:
1252 links
.new(fs
, n1
.inputs
[i
])
1253 elif len(types
) == 2:
1254 i1f
= types
[0][0].links
[0].from_socket
1255 i1t
= types
[0][0].links
[0].to_socket
1256 i2f
= types
[1][0].links
[0].from_socket
1257 i2t
= types
[1][0].links
[0].to_socket
1262 self
.report({'WARNING'}, "This node has no input connections to swap!")
1264 self
.report({'WARNING'}, "This node has no inputs to swap!")
1266 hack_force_update(context
, nodes
)
1270 class NWResetBG(Operator
, NWBase
):
1271 """Reset the zoom and position of the background image"""
1272 bl_idname
= 'node.nw_bg_reset'
1273 bl_label
= 'Reset Backdrop'
1274 bl_options
= {'REGISTER', 'UNDO'}
1277 def poll(cls
, context
):
1278 snode
= context
.space_data
1279 return snode
.tree_type
== 'CompositorNodeTree'
1281 def execute(self
, context
):
1282 context
.space_data
.backdrop_zoom
= 1
1283 context
.space_data
.backdrop_x
= 0
1284 context
.space_data
.backdrop_y
= 0
1288 class NWAddAttrNode(Operator
, NWBase
):
1289 """Add an Attribute node with this name"""
1290 bl_idname
= 'node.nw_add_attr_node'
1291 bl_label
= 'Add UV map'
1292 attr_name
= StringProperty()
1293 bl_options
= {'REGISTER', 'UNDO'}
1295 def execute(self
, context
):
1296 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1297 nodes
, links
= get_nodes_links(context
)
1298 nodes
.active
.attribute_name
= self
.attr_name
1302 class NWEmissionViewer(Operator
, NWBase
):
1303 bl_idname
= "node.nw_emission_viewer"
1304 bl_label
= "Emission Viewer"
1305 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1306 bl_options
= {'REGISTER', 'UNDO'}
1309 def poll(cls
, context
):
1310 space
= context
.space_data
1312 if space
.type == 'NODE_EDITOR':
1313 if space
.tree_type
== 'ShaderNodeTree' and space
.node_tree
is not None and (context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD"):
1317 def invoke(self
, context
, event
):
1318 shader_type
= context
.space_data
.shader_type
1319 if shader_type
== 'OBJECT':
1320 shader_output_type
= "OUTPUT_MATERIAL"
1321 shader_output_ident
= "ShaderNodeOutputMaterial"
1322 shader_viewer_ident
= "ShaderNodeEmission"
1323 elif shader_type
== 'WORLD':
1324 shader_output_type
= "OUTPUT_WORLD"
1325 shader_output_ident
= "ShaderNodeOutputWorld"
1326 shader_viewer_ident
= "ShaderNodeBackground"
1327 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1328 mlocx
= event
.mouse_region_x
1329 mlocy
= event
.mouse_region_y
1330 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1331 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1332 nodes
, links
= get_nodes_links(context
)
1333 in_group
= context
.active_node
!= context
.space_data
.node_tree
.nodes
.active
1334 active
= nodes
.active
1336 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1338 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1340 if active
.type not in shader_types
:
1343 # get material_output node
1344 materialout_exists
= False
1345 materialout
= None # placeholder node
1347 if node
.type == shader_output_type
:
1348 materialout_exists
= True
1351 materialout
= nodes
.new(shader_output_ident
)
1352 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1353 max_xloc_node
= sorted_by_xloc
[-1]
1354 if max_xloc_node
.name
== 'Emission Viewer':
1355 max_xloc_node
= sorted_by_xloc
[-2]
1356 materialout
.location
.x
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1359 sum_yloc
+= node
.location
.y
1360 materialout
.location
.y
= sum_yloc
/ len(nodes
) # put material output at average y location
1361 materialout
.select
= False
1362 # get Emission Viewer node
1363 emission_exists
= False
1364 emission_placeholder
= nodes
[0]
1366 if "Emission Viewer" in node
.name
:
1367 emission_exists
= True
1368 emission_placeholder
= node
1371 for link
in links
: # check if Emission Viewer is already connected to active node
1372 if link
.from_node
.name
== active
.name
and "Emission Viewer" in link
.to_node
.name
and "Emission Viewer" in materialout
.inputs
[0].links
[0].from_node
.name
:
1373 num_outputs
= len(link
.from_node
.outputs
)
1375 for output
in link
.from_node
.outputs
:
1376 if link
.from_socket
== output
:
1379 position
= position
+ 1
1380 if position
>= num_outputs
:
1386 if node
.select
== True:
1387 selection
.append(node
.name
)
1389 locx
= active
.location
.x
1390 locy
= active
.location
.y
1391 dimx
= active
.dimensions
.x
1392 dimy
= active
.dimensions
.y
1393 if not emission_exists
:
1394 emission
= nodes
.new(shader_viewer_ident
)
1395 emission
.hide
= True
1396 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1397 emission
.label
= "Viewer"
1398 emission
.name
= "Emission Viewer"
1399 emission
.use_custom_color
= True
1400 emission
.color
= (0.6, 0.5, 0.4)
1402 emission
= emission_placeholder
1404 nodes
.active
= emission
1405 links
.new(active
.outputs
[position
], emission
.inputs
[0])
1406 bpy
.ops
.node
.nw_link_out()
1409 emission
.select
= False
1410 nodes
.active
= active
1412 if node
.name
in selection
:
1414 else: # if active node is a shader, connect to output
1415 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1416 bpy
.ops
.node
.nw_link_out()
1418 # ----Delete Emission Viewer----
1419 if [x
for x
in nodes
if x
.name
== 'Emission Viewer']:
1423 if node
.select
== True:
1424 selection
.append(node
.name
)
1427 nodes
['Emission Viewer'].select
= True
1428 bpy
.ops
.node
.delete()
1431 if node
.name
in selection
:
1436 return {'CANCELLED'}
1439 class NWFrameSelected(Operator
, NWBase
):
1440 bl_idname
= "node.nw_frame_selected"
1441 bl_label
= "Frame Selected"
1442 bl_description
= "Add a frame node and parent the selected nodes to it"
1443 bl_options
= {'REGISTER', 'UNDO'}
1444 label_prop
= StringProperty(name
='Label', default
=' ', description
='The visual name of the frame node')
1445 color_prop
= FloatVectorProperty(name
="Color", description
="The color of the frame node", default
=(0.6, 0.6, 0.6),
1446 min=0, max=1, step
=1, precision
=3, subtype
='COLOR_GAMMA', size
=3)
1449 def poll(cls
, context
):
1450 space
= context
.space_data
1452 if space
.type == 'NODE_EDITOR':
1453 if space
.node_tree
is not None:
1457 def execute(self
, context
):
1458 nodes
, links
= get_nodes_links(context
)
1461 if node
.select
== True:
1462 selected
.append(node
)
1464 bpy
.ops
.node
.add_node(type='NodeFrame')
1466 frm
.label
= self
.label_prop
1467 frm
.use_custom_color
= True
1468 frm
.color
= self
.color_prop
1470 for node
in selected
:
1476 class NWReloadImages(Operator
, NWBase
):
1477 bl_idname
= "node.nw_reload_images"
1478 bl_label
= "Reload Images"
1479 bl_description
= "Update all the image nodes to match their files on disk"
1482 def poll(cls
, context
):
1483 space
= context
.space_data
1485 if space
.type == 'NODE_EDITOR':
1486 if space
.node_tree
is not None:
1490 def execute(self
, context
):
1491 nodes
, links
= get_nodes_links(context
)
1492 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1495 if node
.type in image_types
:
1496 if node
.type == "TEXTURE":
1497 if node
.texture
: # node has texture assigned
1498 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1499 if node
.texture
.image
: # texture has image assigned
1500 node
.texture
.image
.reload()
1508 self
.report({'INFO'}, "Reloaded images")
1509 print("Reloaded " + str(num_reloaded
) + " images")
1510 hack_force_update(context
, nodes
)
1513 self
.report({'WARNING'}, "No images found to reload in this node tree")
1514 return {'CANCELLED'}
1517 class NWSwitchNodeType(Operator
, NWBase
):
1518 """Switch type of selected nodes """
1519 bl_idname
= "node.nw_swtch_node_type"
1520 bl_label
= "Switch Node Type"
1521 bl_options
= {'REGISTER', 'UNDO'}
1523 to_type
= EnumProperty(
1524 name
="Switch to type",
1525 items
=list(shaders_input_nodes_props
) +
1526 list(shaders_output_nodes_props
) +
1527 list(shaders_shader_nodes_props
) +
1528 list(shaders_texture_nodes_props
) +
1529 list(shaders_color_nodes_props
) +
1530 list(shaders_vector_nodes_props
) +
1531 list(shaders_converter_nodes_props
) +
1532 list(shaders_layout_nodes_props
) +
1533 list(compo_input_nodes_props
) +
1534 list(compo_output_nodes_props
) +
1535 list(compo_color_nodes_props
) +
1536 list(compo_converter_nodes_props
) +
1537 list(compo_filter_nodes_props
) +
1538 list(compo_vector_nodes_props
) +
1539 list(compo_matte_nodes_props
) +
1540 list(compo_distort_nodes_props
) +
1541 list(compo_layout_nodes_props
),
1544 def execute(self
, context
):
1545 nodes
, links
= get_nodes_links(context
)
1546 to_type
= self
.to_type
1547 # Those types of nodes will not swap.
1548 src_excludes
= ('NodeFrame')
1549 # Those attributes of nodes will be copied if possible
1550 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1551 'show_options', 'show_preview', 'show_texture',
1552 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1554 selected
= [n
for n
in nodes
if n
.select
]
1556 for node
in [n
for n
in selected
if
1557 n
.rna_type
.identifier
not in src_excludes
and
1558 n
.rna_type
.identifier
!= to_type
]:
1559 new_node
= nodes
.new(to_type
)
1560 for attr
in attrs_to_pass
:
1561 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1562 setattr(new_node
, attr
, getattr(node
, attr
))
1563 # set image datablock of dst to image of src
1564 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1566 new_node
.image
= node
.image
1568 if new_node
.type == 'SWITCH':
1569 new_node
.hide
= True
1570 # Dictionaries: src_sockets and dst_sockets:
1571 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1572 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1573 # in 'INPUTS' and 'OUTPUTS':
1574 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1576 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1578 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1579 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1582 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1583 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1585 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1586 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1587 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1588 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1589 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1590 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1591 # enumerate in inputs, then in outputs
1592 # find name, default value and links of socket
1593 for i
, socket
in enumerate(in_out
):
1594 the_name
= socket
.name
1596 # Not every socket, especially in outputs has "default_value"
1597 if hasattr(socket
, 'default_value'):
1598 dval
= socket
.default_value
1600 for lnk
in socket
.links
:
1601 socket_links
.append(lnk
)
1602 # check type of socket to fill proper keys.
1603 for the_type
in types_order_one
:
1604 if socket
.type == the_type
:
1605 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1606 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1607 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1608 # Check which of the types in inputs/outputs is considered to be "main".
1609 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1610 for type_check
in types_order_one
:
1611 if sockets
[in_out_name
][type_check
]:
1612 sockets
[in_out_name
]['MAIN'] = type_check
1616 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1617 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1620 for inout
, soctype
in (
1621 ('INPUTS', 'MAIN',),
1622 ('INPUTS', 'SHADER',),
1623 ('INPUTS', 'RGBA',),
1624 ('INPUTS', 'VECTOR',),
1625 ('INPUTS', 'VALUE',),
1626 ('OUTPUTS', 'MAIN',),
1627 ('OUTPUTS', 'SHADER',),
1628 ('OUTPUTS', 'RGBA',),
1629 ('OUTPUTS', 'VECTOR',),
1630 ('OUTPUTS', 'VALUE',),
1632 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1633 if soctype
== 'MAIN':
1634 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1635 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1637 sc
= src_sockets
[inout
][soctype
]
1638 dt
= dst_sockets
[inout
][soctype
]
1639 # start with 'dt' to determine number of possibilities.
1640 for i
, soc
in enumerate(dt
):
1641 # if src main has enough entries - match them with dst main sockets by indexes.
1643 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1644 # add 'VALUE_NAME' criterion to inputs.
1645 if inout
== 'INPUTS' and soctype
== 'VALUE':
1647 if s
[2] == soc
[2]: # if names match
1648 # append src (index, dval), dst (index, dval)
1649 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1651 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1652 # This creates better links when relinking textures.
1653 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1654 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1656 # Pass default values and RELINK:
1657 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1658 # INPUTS: Base on matches in proper order.
1659 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1661 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1662 new_node
.inputs
[dst_i
].default_value
= src_dval
1663 # Special case: switch to math
1664 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1665 new_node
.type == 'MATH' and\
1667 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1668 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1669 if node
.type == 'MIX_RGB':
1670 if node
.blend_type
in [o
[0] for o
in operations
]:
1671 new_node
.operation
= node
.blend_type
1672 # Special case: switch from math to some types
1673 if node
.type == 'MATH' and\
1674 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1677 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1678 if new_node
.type == 'MIX_RGB':
1679 if node
.operation
in [t
[0] for t
in blend_types
]:
1680 new_node
.blend_type
= node
.operation
1681 # Set Fac of MIX_RGB to 1.0
1682 new_node
.inputs
[0].default_value
= 1.0
1683 # make link only when dst matching input is not linked already.
1684 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1685 in_src_link
= node
.inputs
[src_i
].links
[0]
1686 in_dst_socket
= new_node
.inputs
[dst_i
]
1687 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1688 links
.remove(in_src_link
)
1689 # OUTPUTS: Base on matches in proper order.
1690 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1691 for out_src_link
in node
.outputs
[src_i
].links
:
1692 out_dst_socket
= new_node
.outputs
[dst_i
]
1693 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1694 # relink rest inputs if possible, no criteria
1695 for src_inp
in node
.inputs
:
1696 for dst_inp
in new_node
.inputs
:
1697 if src_inp
.links
and not dst_inp
.links
:
1698 src_link
= src_inp
.links
[0]
1699 links
.new(src_link
.from_socket
, dst_inp
)
1700 links
.remove(src_link
)
1701 # relink rest outputs if possible, base on node kind if any left.
1702 for src_o
in node
.outputs
:
1703 for out_src_link
in src_o
.links
:
1704 for dst_o
in new_node
.outputs
:
1705 if src_o
.type == dst_o
.type:
1706 links
.new(dst_o
, out_src_link
.to_socket
)
1707 # relink rest outputs no criteria if any left. Link all from first output.
1708 for src_o
in node
.outputs
:
1709 for out_src_link
in src_o
.links
:
1710 if new_node
.outputs
:
1711 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
1716 class NWMergeNodes(Operator
, NWBase
):
1717 bl_idname
= "node.nw_merge_nodes"
1718 bl_label
= "Merge Nodes"
1719 bl_description
= "Merge Selected Nodes"
1720 bl_options
= {'REGISTER', 'UNDO'}
1722 mode
= EnumProperty(
1724 description
="All possible blend types and math operations",
1725 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
1727 merge_type
= EnumProperty(
1729 description
="Type of Merge to be used",
1731 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
1732 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
1733 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
1734 ('MATH', 'Math Node', 'Merge using Math Nodes'),
1735 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes')
1739 def execute(self
, context
):
1740 settings
= context
.user_preferences
.addons
[__name__
].preferences
1741 merge_hide
= settings
.merge_hide
1742 merge_position
= settings
.merge_position
# 'center' or 'bottom'
1745 do_hide_shader
= False
1746 if merge_hide
== 'ALWAYS':
1748 do_hide_shader
= True
1749 elif merge_hide
== 'NON_SHADER':
1752 tree_type
= context
.space_data
.node_tree
.type
1753 if tree_type
== 'COMPOSITING':
1754 node_type
= 'CompositorNode'
1755 elif tree_type
== 'SHADER':
1756 node_type
= 'ShaderNode'
1757 nodes
, links
= get_nodes_links(context
)
1759 merge_type
= self
.merge_type
1760 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
1761 # 'ZCOMBINE' works only if mode == 'MIX'
1762 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
1763 if merge_type
== 'ZCOMBINE' and tree_type
!= 'COMPOSITING':
1765 selected_mix
= [] # entry = [index, loc]
1766 selected_shader
= [] # entry = [index, loc]
1767 selected_math
= [] # entry = [index, loc]
1768 selected_z
= [] # entry = [index, loc]
1770 for i
, node
in enumerate(nodes
):
1771 if node
.select
and node
.outputs
:
1772 if merge_type
== 'AUTO':
1773 for (type, types_list
, dst
) in (
1774 ('SHADER', ('MIX', 'ADD'), selected_shader
),
1775 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
1776 ('VALUE', [t
[0] for t
in operations
], selected_math
),
1778 output_type
= node
.outputs
[0].type
1779 valid_mode
= mode
in types_list
1780 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
1781 # Cheat that output type is 'RGBA',
1782 # and that 'MIX' exists in math operations list.
1783 # This way when selected_mix list is analyzed:
1784 # Node data will be appended even though it doesn't meet requirements.
1785 if output_type
!= 'SHADER' and mode
== 'MIX':
1786 output_type
= 'RGBA'
1788 if output_type
== type and valid_mode
:
1789 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
1791 for (type, types_list
, dst
) in (
1792 ('SHADER', ('MIX', 'ADD'), selected_shader
),
1793 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
1794 ('MATH', [t
[0] for t
in operations
], selected_math
),
1795 ('ZCOMBINE', ('MIX', ), selected_z
),
1797 if merge_type
== type and mode
in types_list
:
1798 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
1799 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
1800 # use only 'Mix' nodes for merging.
1801 # For that we add selected_math list to selected_mix list and clear selected_math.
1802 if selected_mix
and selected_math
and merge_type
== 'AUTO':
1803 selected_mix
+= selected_math
1806 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
]:
1808 count_before
= len(nodes
)
1809 # sort list by loc_x - reversed
1810 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
1812 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
1813 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
1814 if merge_position
== 'CENTER':
1815 loc_y
= ((nodes_list
[len(nodes_list
) - 1][2]) + (nodes_list
[len(nodes_list
) - 2][2])) / 2 # average yloc of last two nodes (lowest two)
1816 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
1822 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
1826 if nodes_list
== selected_shader
and not do_hide_shader
:
1828 the_range
= len(nodes_list
) - 1
1829 if len(nodes_list
) == 1:
1831 for i
in range(the_range
):
1832 if nodes_list
== selected_mix
:
1833 add_type
= node_type
+ 'MixRGB'
1834 add
= nodes
.new(add_type
)
1835 add
.blend_type
= mode
1836 add
.show_preview
= False
1842 add
.width_hidden
= 100.0
1843 elif nodes_list
== selected_math
:
1844 add_type
= node_type
+ 'Math'
1845 add
= nodes
.new(add_type
)
1846 add
.operation
= mode
1852 add
.width_hidden
= 100.0
1853 elif nodes_list
== selected_shader
:
1855 add_type
= node_type
+ 'MixShader'
1856 add
= nodes
.new(add_type
)
1857 add
.hide
= do_hide_shader
1862 add
.width_hidden
= 100.0
1864 add_type
= node_type
+ 'AddShader'
1865 add
= nodes
.new(add_type
)
1866 add
.hide
= do_hide_shader
1871 add
.width_hidden
= 100.0
1872 elif nodes_list
== selected_z
:
1873 add
= nodes
.new('CompositorNodeZcombine')
1874 add
.show_preview
= False
1880 add
.width_hidden
= 100.0
1881 add
.location
= loc_x
, loc_y
1885 count_after
= len(nodes
)
1886 index
= count_after
- 1
1887 first_selected
= nodes
[nodes_list
[0][0]]
1888 # "last" node has been added as first, so its index is count_before.
1889 last_add
= nodes
[count_before
]
1890 # add links from last_add to all links 'to_socket' of out links of first selected.
1891 for fs_link
in first_selected
.outputs
[0].links
:
1892 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
1893 # Create list of invalid indexes.
1894 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
1895 # Link only if "to_node" index not in invalid indexes list.
1896 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
1897 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
1898 # add link from "first" selected and "first" add node
1899 node_to
= nodes
[count_after
- 1]
1900 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
1901 if node_to
.type == 'ZCOMBINE':
1902 for fs_out
in first_selected
.outputs
:
1903 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
1904 links
.new(fs_out
, node_to
.inputs
[1])
1906 # add links between added ADD nodes and between selected and ADD nodes
1907 for i
in range(count_adds
):
1908 if i
< count_adds
- 1:
1909 node_from
= nodes
[index
]
1910 node_to
= nodes
[index
- 1]
1911 node_to_input_i
= first
1912 node_to_z_i
= 1 # if z combine - link z to first z input
1913 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
1914 if node_to
.type == 'ZCOMBINE':
1915 for from_out
in node_from
.outputs
:
1916 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
1917 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
1918 if len(nodes_list
) > 1:
1919 node_from
= nodes
[nodes_list
[i
+ 1][0]]
1920 node_to
= nodes
[index
]
1921 node_to_input_i
= second
1922 node_to_z_i
= 3 # if z combine - link z to second z input
1923 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
1924 if node_to
.type == 'ZCOMBINE':
1925 for from_out
in node_from
.outputs
:
1926 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
1927 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
1929 # set "last" of added nodes as active
1930 nodes
.active
= last_add
1931 for i
, x
, y
, dx
, h
in nodes_list
:
1932 nodes
[i
].select
= False
1937 class NWBatchChangeNodes(Operator
, NWBase
):
1938 bl_idname
= "node.nw_batch_change"
1939 bl_label
= "Batch Change"
1940 bl_description
= "Batch Change Blend Type and Math Operation"
1941 bl_options
= {'REGISTER', 'UNDO'}
1943 blend_type
= EnumProperty(
1945 items
=blend_types
+ navs
,
1947 operation
= EnumProperty(
1949 items
=operations
+ navs
,
1952 def execute(self
, context
):
1954 nodes
, links
= get_nodes_links(context
)
1955 blend_type
= self
.blend_type
1956 operation
= self
.operation
1957 for node
in context
.selected_nodes
:
1958 if node
.type == 'MIX_RGB':
1959 if not blend_type
in [nav
[0] for nav
in navs
]:
1960 node
.blend_type
= blend_type
1962 if blend_type
== 'NEXT':
1963 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
1964 #index = blend_types.index(node.blend_type)
1965 if index
== len(blend_types
) - 1:
1966 node
.blend_type
= blend_types
[0][0]
1968 node
.blend_type
= blend_types
[index
+ 1][0]
1970 if blend_type
== 'PREV':
1971 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
1973 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
1975 node
.blend_type
= blend_types
[index
- 1][0]
1977 if node
.type == 'MATH':
1978 if not operation
in [nav
[0] for nav
in navs
]:
1979 node
.operation
= operation
1981 if operation
== 'NEXT':
1982 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
1983 #index = operations.index(node.operation)
1984 if index
== len(operations
) - 1:
1985 node
.operation
= operations
[0][0]
1987 node
.operation
= operations
[index
+ 1][0]
1989 if operation
== 'PREV':
1990 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
1991 #index = operations.index(node.operation)
1993 node
.operation
= operations
[len(operations
) - 1][0]
1995 node
.operation
= operations
[index
- 1][0]
2000 class NWChangeMixFactor(Operator
, NWBase
):
2001 bl_idname
= "node.nw_factor"
2002 bl_label
= "Change Factor"
2003 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2004 bl_options
= {'REGISTER', 'UNDO'}
2006 # option: Change factor.
2007 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2008 # Else - change factor by option value.
2009 option
= FloatProperty()
2011 def execute(self
, context
):
2012 nodes
, links
= get_nodes_links(context
)
2013 option
= self
.option
2014 selected
= [] # entry = index
2015 for si
, node
in enumerate(nodes
):
2017 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2021 fac
= nodes
[si
].inputs
[0]
2022 nodes
[si
].hide
= False
2023 if option
in {0.0, 1.0}:
2024 fac
.default_value
= option
2026 fac
.default_value
+= option
2031 class NWCopySettings(Operator
, NWBase
):
2032 bl_idname
= "node.nw_copy_settings"
2033 bl_label
= "Copy Settings"
2034 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2035 bl_options
= {'REGISTER', 'UNDO'}
2038 def poll(cls
, context
):
2039 space
= context
.space_data
2041 if (space
.type == 'NODE_EDITOR' and
2042 space
.node_tree
is not None and
2043 context
.active_node
is not None and
2044 context
.active_node
.type is not 'FRAME'
2049 def execute(self
, context
):
2050 nodes
, links
= get_nodes_links(context
)
2051 selected
= [n
for n
in nodes
if n
.select
]
2052 reselect
= [] # duplicated nodes will be selected after execution
2053 active
= nodes
.active
2055 reselect
.append(active
)
2057 for node
in selected
:
2058 if node
.type == active
.type and node
!= active
:
2059 # duplicate active, relink links as in 'node', append copy to 'reselect', delete node
2060 bpy
.ops
.node
.select_all(action
='DESELECT')
2061 nodes
.active
= active
2062 active
.select
= True
2063 bpy
.ops
.node
.duplicate()
2064 copied
= nodes
.active
2065 # Copied active should however inherit some properties from 'node'
2067 'hide', 'show_preview', 'mute', 'label',
2068 'use_custom_color', 'color', 'width', 'width_hidden',
2070 for attr
in attributes
:
2071 setattr(copied
, attr
, getattr(node
, attr
))
2072 # Handle scenario when 'node' is in frame. 'copied' is in same frame then.
2074 bpy
.ops
.node
.parent_clear()
2075 locx
= node
.location
.x
2076 locy
= node
.location
.y
2077 # get absolute node location
2078 parent
= node
.parent
2080 locx
+= parent
.location
.x
2081 locy
+= parent
.location
.y
2082 parent
= parent
.parent
2083 copied
.location
= [locx
, locy
]
2084 # reconnect links from node to copied
2085 for i
, input in enumerate(node
.inputs
):
2087 link
= input.links
[0]
2088 links
.new(link
.from_socket
, copied
.inputs
[i
])
2089 for out
, output
in enumerate(node
.outputs
):
2091 out_links
= output
.links
2092 for link
in out_links
:
2093 links
.new(copied
.outputs
[out
], link
.to_socket
)
2094 bpy
.ops
.node
.select_all(action
='DESELECT')
2096 bpy
.ops
.node
.delete()
2097 reselect
.append(copied
)
2098 else: # If selected wasn't copied, need to reselect it afterwards.
2099 reselect
.append(node
)
2101 bpy
.ops
.node
.select_all(action
='DESELECT')
2102 for node
in reselect
:
2104 nodes
.active
= active
2109 class NWCopyLabel(Operator
, NWBase
):
2110 bl_idname
= "node.nw_copy_label"
2111 bl_label
= "Copy Label"
2112 bl_options
= {'REGISTER', 'UNDO'}
2114 option
= EnumProperty(
2116 description
="Source of name of label",
2118 ('FROM_ACTIVE', 'from active', 'from active node',),
2119 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2120 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2124 def execute(self
, context
):
2125 nodes
, links
= get_nodes_links(context
)
2126 option
= self
.option
2127 active
= nodes
.active
2128 if option
== 'FROM_ACTIVE':
2130 src_label
= active
.label
2131 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2132 node
.label
= src_label
2133 elif option
== 'FROM_NODE':
2134 selected
= [n
for n
in nodes
if n
.select
]
2135 for node
in selected
:
2136 for input in node
.inputs
:
2138 src
= input.links
[0].from_node
2139 node
.label
= src
.label
2141 elif option
== 'FROM_SOCKET':
2142 selected
= [n
for n
in nodes
if n
.select
]
2143 for node
in selected
:
2144 for input in node
.inputs
:
2146 src
= input.links
[0].from_socket
2147 node
.label
= src
.name
2153 class NWClearLabel(Operator
, NWBase
):
2154 bl_idname
= "node.nw_clear_label"
2155 bl_label
= "Clear Label"
2156 bl_options
= {'REGISTER', 'UNDO'}
2158 option
= BoolProperty()
2160 def execute(self
, context
):
2161 nodes
, links
= get_nodes_links(context
)
2162 for node
in [n
for n
in nodes
if n
.select
]:
2167 def invoke(self
, context
, event
):
2169 return self
.execute(context
)
2171 return context
.window_manager
.invoke_confirm(self
, event
)
2174 class NWModifyLabels(Operator
, NWBase
):
2175 """Modify Labels of all selected nodes."""
2176 bl_idname
= "node.nw_modify_labels"
2177 bl_label
= "Modify Labels"
2178 bl_options
= {'REGISTER', 'UNDO'}
2180 prepend
= StringProperty(
2181 name
="Add to Beginning"
2183 append
= StringProperty(
2186 replace_from
= StringProperty(
2187 name
="Text to Replace"
2189 replace_to
= StringProperty(
2193 def execute(self
, context
):
2194 nodes
, links
= get_nodes_links(context
)
2195 for node
in [n
for n
in nodes
if n
.select
]:
2196 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2200 def invoke(self
, context
, event
):
2204 return context
.window_manager
.invoke_props_dialog(self
)
2207 class NWAddTextureSetup(Operator
, NWBase
):
2208 bl_idname
= "node.nw_add_texture"
2209 bl_label
= "Texture Setup"
2210 bl_description
= "Add Texture Node Setup to Selected Shaders"
2211 bl_options
= {'REGISTER', 'UNDO'}
2214 def poll(cls
, context
):
2215 space
= context
.space_data
2217 if space
.type == 'NODE_EDITOR':
2218 if space
.tree_type
== 'ShaderNodeTree' and space
.node_tree
is not None:
2222 def execute(self
, context
):
2223 nodes
, links
= get_nodes_links(context
)
2224 active
= nodes
.active
2225 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2226 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2230 if active
.type in shader_types
or active
.type in texture_types
:
2231 if not active
.inputs
[0].is_linked
:
2234 locx
= active
.location
.x
2235 locy
= active
.location
.y
2237 xoffset
= [500.0, 700.0]
2239 if active
.type not in shader_types
:
2240 xoffset
= [290.0, 500.0]
2244 image_type
= 'ShaderNodeTexImage'
2246 if (active
.type in texture_types
and active
.type != 'TEX_IMAGE') or (active
.type == 'BACKGROUND'):
2247 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2248 if active
.type == 'BACKGROUND':
2249 image_type
= 'ShaderNodeTexEnvironment'
2252 tex
= nodes
.new(image_type
)
2253 tex
.location
= [locx
- 200.0, locy
+ 28.0]
2255 map = nodes
.new('ShaderNodeMapping')
2256 map.location
= [locx
- xoffset
[0], locy
+ 80.0]
2258 coord
= nodes
.new('ShaderNodeTexCoord')
2259 coord
.location
= [locx
- xoffset
[1], locy
+ 40.0]
2260 active
.select
= False
2264 links
.new(tex
.outputs
[0], active
.inputs
[0])
2265 links
.new(map.outputs
[0], tex
.inputs
[0])
2266 links
.new(coord
.outputs
[coordout
], map.inputs
[0])
2270 links
.new(map.outputs
[0], active
.inputs
[0])
2271 links
.new(coord
.outputs
[coordout
], map.inputs
[0])
2276 class NWAddReroutes(Operator
, NWBase
):
2277 """Add Reroute Nodes and link them to outputs of selected nodes"""
2278 bl_idname
= "node.nw_add_reroutes"
2279 bl_label
= "Add Reroutes"
2280 bl_description
= "Add Reroutes to Outputs"
2281 bl_options
= {'REGISTER', 'UNDO'}
2283 option
= EnumProperty(
2286 ('ALL', 'to all', 'Add to all outputs'),
2287 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2288 ('LINKED', 'to linked', 'Add only to linked outputs'),
2292 def execute(self
, context
):
2293 tree_type
= context
.space_data
.node_tree
.type
2294 option
= self
.option
2295 nodes
, links
= get_nodes_links(context
)
2296 # output valid when option is 'all' or when 'loose' output has no links
2298 post_select
= [] # nodes to be selected after execution
2299 # create reroutes and recreate links
2300 for node
in [n
for n
in nodes
if n
.select
]:
2305 # unhide 'REROUTE' nodes to avoid issues with location.y
2306 if node
.type == 'REROUTE':
2308 # When node is hidden - width_hidden not usable.
2309 # Hack needed to calculate real width
2311 bpy
.ops
.node
.select_all(action
='DESELECT')
2312 helper
= nodes
.new('NodeReroute')
2313 helper
.select
= True
2315 # resize node and helper to zero. Then check locations to calculate width
2316 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2317 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2318 # restore node location
2319 node
.location
= x
, y
2322 # only helper is selected now
2323 bpy
.ops
.node
.delete()
2324 x
= node
.location
.x
+ width
+ 20.0
2325 if node
.type != 'REROUTE':
2329 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2330 for out_i
, output
in enumerate(node
.outputs
):
2331 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2332 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2333 if node
.type != 'R_LAYERS':
2335 else: # if 'R_LAYERS' check if output represent used render pass
2336 node_scene
= node
.scene
2337 node_layer
= node
.layer
2338 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2339 if output
.name
== 'Alpha':
2342 # check entries in global 'rl_outputs' variable
2343 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
2344 if output
.name
== out_name
:
2345 pass_used
= getattr(node_scene
.render
.layers
[node_layer
], render_pass
)
2348 valid
= ((option
== 'ALL') or
2349 (option
== 'LOOSE' and not output
.links
) or
2350 (option
== 'LINKED' and output
.links
))
2351 # Add reroutes only if valid, but offset location in all cases.
2353 n
= nodes
.new('NodeReroute')
2355 for link
in output
.links
:
2356 links
.new(n
.outputs
[0], link
.to_socket
)
2357 links
.new(output
, n
.inputs
[0])
2359 post_select
.append(n
)
2363 # disselect the node so that after execution of script only newly created nodes are selected
2365 # nicer reroutes distribution along y when node.hide
2367 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2368 for reroute
in [r
for r
in nodes
if r
.select
]:
2369 reroute
.location
.y
-= y_translate
2370 for node
in post_select
:
2376 class NWLinkActiveToSelected(Operator
, NWBase
):
2377 """Link active node to selected nodes basing on various criteria"""
2378 bl_idname
= "node.nw_link_active_to_selected"
2379 bl_label
= "Link Active Node to Selected"
2380 bl_options
= {'REGISTER', 'UNDO'}
2382 replace
= BoolProperty()
2383 use_node_name
= BoolProperty()
2384 use_outputs_names
= BoolProperty()
2387 def poll(cls
, context
):
2388 space
= context
.space_data
2390 if space
.type == 'NODE_EDITOR':
2391 if space
.node_tree
is not None and context
.active_node
is not None:
2392 if context
.active_node
.select
:
2396 def execute(self
, context
):
2397 nodes
, links
= get_nodes_links(context
)
2398 replace
= self
.replace
2399 use_node_name
= self
.use_node_name
2400 use_outputs_names
= self
.use_outputs_names
2401 active
= nodes
.active
2402 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
2403 outputs
= [] # Only usable outputs of active nodes will be stored here.
2404 for out
in active
.outputs
:
2405 if active
.type != 'R_LAYERS':
2408 # 'R_LAYERS' node type needs special handling.
2409 # outputs of 'R_LAYERS' are callable even if not seen in UI.
2410 # Only outputs that represent used passes should be taken into account
2411 # Check if pass represented by output is used.
2412 # global 'rl_outputs' list will be used for that
2413 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
2414 pass_used
= False # initial value. Will be set to True if pass is used
2415 if out
.name
== 'Alpha':
2416 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
2418 elif out
.name
== out_name
:
2419 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
2420 pass_used
= getattr(active
.scene
.render
.layers
[active
.layer
], render_pass
)
2424 doit
= True # Will be changed to False when links successfully added to previous output.
2427 for node
in selected
:
2428 dst_name
= node
.name
# Will be compared with src_name if needed.
2429 # When node has label - use it as dst_name
2431 dst_name
= node
.label
2432 valid
= True # Initial value. Will be changed to False if names don't match.
2433 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
2435 # Set src_name to source node name or label
2436 src_name
= active
.name
2438 src_name
= active
.label
2439 elif use_outputs_names
:
2440 src_name
= (out
.name
, )
2441 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
2442 if out
.name
in {out_name
, exr_name
}:
2443 src_name
= (out_name
, exr_name
)
2444 if dst_name
not in src_name
:
2447 for input in node
.inputs
:
2448 if input.type == out
.type or node
.type == 'REROUTE':
2449 if replace
or not input.is_linked
:
2450 links
.new(out
, input)
2451 if not use_node_name
and not use_outputs_names
:
2458 class NWAlignNodes(Operator
, NWBase
):
2459 bl_idname
= "node.nw_align_nodes"
2460 bl_label
= "Align nodes"
2461 bl_options
= {'REGISTER', 'UNDO'}
2463 # option: 'Vertically', 'Horizontally'
2464 option
= EnumProperty(
2466 description
="Direction",
2468 ('AXIS_X', "Align Vertically", 'Align Vertically'),
2469 ('AXIS_Y', "Aligh Horizontally", 'Aligh Horizontally'),
2473 def execute(self
, context
):
2474 nodes
, links
= get_nodes_links(context
)
2475 selected
= [] # entry = [index, loc.x, loc.y, width, height]
2476 frames_reselect
= [] # entry = frame node. will be used to reselect all selected frames
2477 active
= nodes
.active
2478 for i
, node
in enumerate(nodes
):
2479 total_w
= 0.0 # total width of all nodes. Will be calculated later.
2480 total_h
= 0.0 # total height of all nodes. Will be calculated later
2482 if node
.type == 'FRAME':
2484 frames_reselect
.append(i
)
2486 locx
= node
.location
.x
2487 locy
= node
.location
.y
2488 width
= node
.dimensions
[0]
2489 height
= node
.dimensions
[1]
2490 total_w
+= width
# add nodes[i] width to total width of all nodes
2491 total_h
+= height
# add nodes[i] height to total height of all nodes
2492 # calculate relative locations
2493 parent
= node
.parent
2494 while parent
is not None:
2495 locx
+= parent
.location
.x
2496 locy
+= parent
.location
.y
2497 parent
= parent
.parent
2498 selected
.append([i
, locx
, locy
, width
, height
])
2499 count
= len(selected
)
2500 if count
> 1: # aligning makes sense only if at least 2 nodes are selected
2501 selected_sorted_x
= sorted(selected
, key
=lambda k
: (k
[1], -k
[2]))
2502 selected_sorted_y
= sorted(selected
, key
=lambda k
: (-k
[2], k
[1]))
2503 min_x
= selected_sorted_x
[0][1] # min loc.x
2504 min_x_loc_y
= selected_sorted_x
[0][2] # loc y of node with min loc x
2505 min_x_w
= selected_sorted_x
[0][3] # width of node with max loc x
2506 max_x
= selected_sorted_x
[count
- 1][1] # max loc.x
2507 max_x_loc_y
= selected_sorted_x
[count
- 1][2] # loc y of node with max loc.x
2508 max_x_w
= selected_sorted_x
[count
- 1][3] # width of node with max loc.x
2509 min_y
= selected_sorted_y
[0][2] # min loc.y
2510 min_y_loc_x
= selected_sorted_y
[0][1] # loc.x of node with min loc.y
2511 min_y_h
= selected_sorted_y
[0][4] # height of node with min loc.y
2512 min_y_w
= selected_sorted_y
[0][3] # width of node with min loc.y
2513 max_y
= selected_sorted_y
[count
- 1][2] # max loc.y
2514 max_y_loc_x
= selected_sorted_y
[count
- 1][1] # loc x of node with max loc.y
2515 max_y_w
= selected_sorted_y
[count
- 1][3] # width of node with max loc.y
2516 max_y_h
= selected_sorted_y
[count
- 1][4] # height of node with max loc.y
2518 if self
.option
== 'AXIS_Y': # Horizontally. Equivelent of s -> x -> 0 with even spacing.
2520 #loc_y = (max_x_loc_y + min_x_loc_y) / 2.0
2521 loc_y
= (max_y
- max_y_h
/ 2.0 + min_y
- min_y_h
/ 2.0) / 2.0
2522 offset_x
= (max_x
- min_x
- total_w
+ max_x_w
) / (count
- 1)
2523 for i
, x
, y
, w
, h
in selected_sorted_x
:
2524 nodes
[i
].location
.x
= loc_x
2525 nodes
[i
].location
.y
= loc_y
+ h
/ 2.0
2526 parent
= nodes
[i
].parent
2527 while parent
is not None:
2528 nodes
[i
].location
.x
-= parent
.location
.x
2529 nodes
[i
].location
.y
-= parent
.location
.y
2530 parent
= parent
.parent
2531 loc_x
+= offset_x
+ w
2532 else: # if self.option == 'AXIS_Y'
2533 loc_x
= (max_x
+ max_x_w
/ 2.0 + min_x
+ min_x_w
/ 2.0) / 2.0
2535 offset_y
= (max_y
- min_y
+ total_h
- min_y_h
) / (count
- 1)
2536 for i
, x
, y
, w
, h
in selected_sorted_y
:
2537 nodes
[i
].location
.x
= loc_x
- w
/ 2.0
2538 nodes
[i
].location
.y
= loc_y
2539 parent
= nodes
[i
].parent
2540 while parent
is not None:
2541 nodes
[i
].location
.x
-= parent
.location
.x
2542 nodes
[i
].location
.y
-= parent
.location
.y
2543 parent
= parent
.parent
2544 loc_y
+= offset_y
- h
2546 # reselect selected frames
2547 for i
in frames_reselect
:
2548 nodes
[i
].select
= True
2549 # restore active node
2550 nodes
.active
= active
2555 class NWSelectParentChildren(Operator
, NWBase
):
2556 bl_idname
= "node.nw_select_parent_child"
2557 bl_label
= "Select Parent or Children"
2558 bl_options
= {'REGISTER', 'UNDO'}
2560 option
= EnumProperty(
2563 ('PARENT', 'Select Parent', 'Select Parent Frame'),
2564 ('CHILD', 'Select Children', 'Select members of selected frame'),
2568 def execute(self
, context
):
2569 nodes
, links
= get_nodes_links(context
)
2570 option
= self
.option
2571 selected
= [node
for node
in nodes
if node
.select
]
2572 if option
== 'PARENT':
2573 for sel
in selected
:
2576 parent
.select
= True
2577 else: # option == 'CHILD'
2578 for sel
in selected
:
2579 children
= [node
for node
in nodes
if node
.parent
== sel
]
2580 for kid
in children
:
2586 class NWDetachOutputs(Operator
, NWBase
):
2587 """Detach outputs of selected node leaving inluts liked"""
2588 bl_idname
= "node.nw_detach_outputs"
2589 bl_label
= "Detach Outputs"
2590 bl_options
= {'REGISTER', 'UNDO'}
2592 def execute(self
, context
):
2593 nodes
, links
= get_nodes_links(context
)
2594 selected
= context
.selected_nodes
2595 bpy
.ops
.node
.duplicate_move_keep_inputs()
2596 new_nodes
= context
.selected_nodes
2597 bpy
.ops
.node
.select_all(action
="DESELECT")
2598 for node
in selected
:
2600 bpy
.ops
.node
.delete_reconnect()
2601 for new_node
in new_nodes
:
2602 new_node
.select
= True
2603 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
2608 class NWLinkToOutputNode(Operator
, NWBase
):
2609 """Link to Composite node or Material Output node"""
2610 bl_idname
= "node.nw_link_out"
2611 bl_label
= "Connect to Output"
2612 bl_options
= {'REGISTER', 'UNDO'}
2615 def poll(cls
, context
):
2616 space
= context
.space_data
2617 return (space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and context
.active_node
is not None)
2619 def execute(self
, context
):
2620 nodes
, links
= get_nodes_links(context
)
2621 active
= nodes
.active
2623 tree_type
= context
.space_data
.tree_type
2624 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
2625 output_types_compo
= ['COMPOSITE']
2626 output_types
= output_types_shaders
+ output_types_compo
2628 if node
.type in output_types
:
2632 bpy
.ops
.node
.select_all(action
="DESELECT")
2633 if tree_type
== 'ShaderNodeTree':
2634 output_node
= nodes
.new('ShaderNodeOutputMaterial')
2635 elif tree_type
== 'CompositorNodeTree':
2636 output_node
= nodes
.new('CompositorNodeComposite')
2637 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
2638 output_node
.location
.y
= active
.location
.y
2639 if (output_node
and active
.outputs
):
2641 for i
, output
in enumerate(active
.outputs
):
2642 if output
.type == output_node
.inputs
[0].type:
2647 if tree_type
== 'ShaderNodeTree':
2648 if active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
2650 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
2652 hack_force_update(context
, nodes
) # viewport render does not update
2657 class NWMakeLink(Operator
, NWBase
):
2658 """Make a link from one socket to another"""
2659 bl_idname
= 'node.nw_make_link'
2660 bl_label
= 'Make Link'
2661 bl_options
= {'REGISTER', 'UNDO'}
2662 from_socket
= IntProperty()
2663 to_socket
= IntProperty()
2666 def poll(cls
, context
):
2667 snode
= context
.space_data
2668 return (snode
.type == 'NODE_EDITOR' and snode
.node_tree
is not None)
2670 def execute(self
, context
):
2671 nodes
, links
= get_nodes_links(context
)
2673 n1
= nodes
[context
.scene
.NWLazySource
]
2674 n2
= nodes
[context
.scene
.NWLazyTarget
]
2676 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
2678 hack_force_update(context
, nodes
)
2683 class NWCallInputsMenu(Operator
, NWBase
):
2684 """Link from this output"""
2685 bl_idname
= 'node.nw_call_inputs_menu'
2686 bl_label
= 'Make Link'
2687 bl_options
= {'REGISTER', 'UNDO'}
2688 from_socket
= IntProperty()
2691 def poll(cls
, context
):
2692 snode
= context
.space_data
2693 return (snode
.type == 'NODE_EDITOR' and snode
.node_tree
is not None)
2695 def execute(self
, context
):
2696 nodes
, links
= get_nodes_links(context
)
2698 context
.scene
.NWSourceSocket
= self
.from_socket
2700 n1
= nodes
[context
.scene
.NWLazySource
]
2701 n2
= nodes
[context
.scene
.NWLazyTarget
]
2702 if len(n2
.inputs
) > 1:
2703 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
2704 elif len(n2
.inputs
) == 1:
2705 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
2709 class NWAddSequence(Operator
, ImportHelper
):
2710 """Add an Image Sequence"""
2711 bl_idname
= 'node.nw_add_sequence'
2712 bl_label
= 'Import Image Sequence'
2713 bl_options
= {'REGISTER', 'UNDO'}
2714 directory
= StringProperty(subtype
="DIR_PATH")
2715 filename
= StringProperty(subtype
="FILE_NAME")
2718 def poll(cls
, context
):
2719 snode
= context
.space_data
2720 return (snode
.type == 'NODE_EDITOR' and snode
.node_tree
is not None)
2722 def execute(self
, context
):
2723 nodes
, links
= get_nodes_links(context
)
2724 directory
= self
.directory
2725 filename
= self
.filename
2728 if context
.space_data
.node_tree
.type == 'SHADER':
2729 node_type
= "ShaderNodeTexImage"
2730 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
2731 node_type
= "CompositorNodeImage"
2733 self
.report({'ERROR'}, "Unsupported Node Tree type!")
2734 return {'CANCELLED'}
2736 # if last digit isn't a number, it's not a sequence
2737 without_ext
= '.'.join(filename
.split('.')[:-1])
2738 if without_ext
[-1].isdigit():
2739 without_ext
= without_ext
[:-1] + '1'
2741 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
2742 return {'CANCELLED'}
2744 reverse
= without_ext
[::-1] # reverse string
2749 for char
in reverse
:
2750 if char
.isdigit() and stop
==False:
2752 newreverse
+= '0' # replace numbers of image sequence with zeros
2756 non_numbers
= char
+ non_numbers
2758 newreverse
= '1' + newreverse
[1:]
2759 without_ext
= newreverse
[::-1] # reverse string
2761 # print (without_ext+'.'+filename.split('.')[-1])
2762 # print (non_numbers)
2763 extension
= filename
.split('.')[-1]
2765 num_frames
= len(list(f
for f
in listdir(directory
) if f
.startswith(non_numbers
)))
2767 for x
in range(count_numbers
):
2770 nodes_list
= [node
for node
in nodes
]
2772 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
2773 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
2777 yloc
+= node_mid_pt(node
, 'y')
2778 yloc
= yloc
/len(nodes
)
2783 node
= nodes
.new(node_type
)
2784 node
.location
.x
= xloc
2785 node
.location
.y
= yloc
+ 110
2786 node
.label
= non_numbers
+'.'+extension
2788 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
2789 img
.source
= 'SEQUENCE'
2791 if context
.space_data
.node_tree
.type == 'SHADER':
2792 node
.image_user
.frame_duration
= num_frames
2794 node
.frame_duration
= num_frames
2799 class NWAddMultipleImages(Operator
, ImportHelper
):
2800 """Add multiple images at once"""
2801 bl_idname
= 'node.nw_add_multiple_images'
2802 bl_label
= 'Open Selected Images'
2803 bl_options
= {'REGISTER', 'UNDO'}
2804 directory
= StringProperty(subtype
="DIR_PATH")
2805 files
= CollectionProperty(type=bpy
.types
.OperatorFileListElement
, options
={'HIDDEN', 'SKIP_SAVE'})
2808 def poll(cls
, context
):
2809 snode
= context
.space_data
2810 return (snode
.type == 'NODE_EDITOR' and snode
.node_tree
is not None)
2812 def execute(self
, context
):
2813 nodes
, links
= get_nodes_links(context
)
2814 nodes_list
= [node
for node
in nodes
]
2816 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
2817 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
2821 yloc
+= node_mid_pt(node
, 'y')
2822 yloc
= yloc
/len(nodes
)
2827 if context
.space_data
.node_tree
.type == 'SHADER':
2828 node_type
= "ShaderNodeTexImage"
2829 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
2830 node_type
= "CompositorNodeImage"
2832 self
.report({'ERROR'}, "Unsupported Node Tree type!")
2833 return {'CANCELLED'}
2836 for f
in self
.files
:
2839 node
= nodes
.new(node_type
)
2840 new_nodes
.append(node
)
2843 node
.width_hidden
= 100
2844 node
.location
.x
= xloc
2845 node
.location
.y
= yloc
2848 img
= bpy
.data
.images
.load(self
.directory
+fname
)
2851 # shift new nodes up to center of tree
2852 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
2853 for node
in new_nodes
:
2855 node
.location
.y
+= (list_size
/2)
2863 def drawlayout(context
, layout
, mode
='non-panel'):
2864 tree_type
= context
.space_data
.tree_type
2866 col
= layout
.column(align
=True)
2867 col
.menu(NWMergeNodesMenu
.bl_idname
)
2870 col
= layout
.column(align
=True)
2871 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
2874 if tree_type
== 'ShaderNodeTree':
2875 col
= layout
.column(align
=True)
2876 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
2879 col
= layout
.column(align
=True)
2880 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
2881 col
.operator(NWSwapLinks
.bl_idname
)
2882 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
2885 col
= layout
.column(align
=True)
2886 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
2887 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
2890 col
= layout
.column(align
=True)
2892 row
= col
.row(align
=True)
2893 row
.operator(NWClearLabel
.bl_idname
).option
= True
2894 row
.operator(NWModifyLabels
.bl_idname
)
2896 col
.operator(NWClearLabel
.bl_idname
).option
= True
2897 col
.operator(NWModifyLabels
.bl_idname
)
2898 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
2900 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
2903 col
= layout
.column(align
=True)
2904 if tree_type
== 'CompositorNodeTree':
2905 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
2906 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
2909 col
= layout
.column(align
=True)
2910 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
2913 col
= layout
.column(align
=True)
2914 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
2918 class NodeWranglerPanel(Panel
, NWBase
):
2919 bl_idname
= "NODE_PT_nw_node_wrangler"
2920 bl_space_type
= 'NODE_EDITOR'
2921 bl_region_type
= 'UI'
2922 bl_label
= "Node Wrangler"
2924 prepend
= StringProperty(
2927 append
= StringProperty()
2928 remove
= StringProperty()
2930 def draw(self
, context
):
2931 self
.layout
.label(text
="(Quick access: Ctrl+Space)")
2932 drawlayout(context
, self
.layout
, mode
='panel')
2938 class NodeWranglerMenu(Menu
, NWBase
):
2939 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
2940 bl_label
= "Node Wrangler"
2942 def draw(self
, context
):
2943 drawlayout(context
, self
.layout
)
2946 class NWMergeNodesMenu(Menu
, NWBase
):
2947 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
2948 bl_label
= "Merge Selected Nodes"
2950 def draw(self
, context
):
2951 type = context
.space_data
.tree_type
2952 layout
= self
.layout
2953 if type == 'ShaderNodeTree':
2954 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
2955 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
2956 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
2957 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
2959 props
.merge_type
= 'ZCOMBINE'
2962 class NWMergeShadersMenu(Menu
, NWBase
):
2963 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
2964 bl_label
= "Merge Selected Nodes using Shaders"
2966 def draw(self
, context
):
2967 layout
= self
.layout
2968 for type in ('MIX', 'ADD'):
2969 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
2971 props
.merge_type
= 'SHADER'
2974 class NWMergeMixMenu(Menu
, NWBase
):
2975 bl_idname
= "NODE_MT_nw_merge_mix_menu"
2976 bl_label
= "Merge Selected Nodes using Mix"
2978 def draw(self
, context
):
2979 layout
= self
.layout
2980 for type, name
, description
in blend_types
:
2981 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
2983 props
.merge_type
= 'MIX'
2986 class NWConnectionListOutputs(Menu
, NWBase
):
2987 bl_idname
= "NODE_MT_nw_connection_list_out"
2990 def draw(self
, context
):
2991 layout
= self
.layout
2992 nodes
, links
= get_nodes_links(context
)
2994 n1
= nodes
[context
.scene
.NWLazySource
]
2996 if n1
.type == "R_LAYERS":
2998 for o
in n1
.outputs
:
2999 if o
.enabled
: # Check which passes the render layer has enabled
3000 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3004 for o
in n1
.outputs
:
3005 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3009 class NWConnectionListInputs(Menu
, NWBase
):
3010 bl_idname
= "NODE_MT_nw_connection_list_in"
3013 def draw(self
, context
):
3014 layout
= self
.layout
3015 nodes
, links
= get_nodes_links(context
)
3017 n2
= nodes
[context
.scene
.NWLazyTarget
]
3019 #print (self.from_socket)
3023 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3024 op
.from_socket
= context
.scene
.NWSourceSocket
3025 op
.to_socket
= index
3029 class NWMergeMathMenu(Menu
, NWBase
):
3030 bl_idname
= "NODE_MT_nw_merge_math_menu"
3031 bl_label
= "Merge Selected Nodes using Math"
3033 def draw(self
, context
):
3034 layout
= self
.layout
3035 for type, name
, description
in operations
:
3036 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3038 props
.merge_type
= 'MATH'
3041 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3042 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3043 bl_label
= "Batch Change Selected Nodes"
3045 def draw(self
, context
):
3046 layout
= self
.layout
3047 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3048 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3051 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3052 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3053 bl_label
= "Batch Change Blend Type"
3055 def draw(self
, context
):
3056 layout
= self
.layout
3057 for type, name
, description
in blend_types
:
3058 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3059 props
.blend_type
= type
3060 props
.operation
= 'CURRENT'
3063 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3064 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3065 bl_label
= "Batch Change Math Operation"
3067 def draw(self
, context
):
3068 layout
= self
.layout
3069 for type, name
, description
in operations
:
3070 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3071 props
.blend_type
= 'CURRENT'
3072 props
.operation
= type
3075 class NWCopyToSelectedMenu(Menu
, NWBase
):
3076 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3077 bl_label
= "Copy to Selected"
3079 def draw(self
, context
):
3080 layout
= self
.layout
3081 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3082 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3085 class NWCopyLabelMenu(Menu
, NWBase
):
3086 bl_idname
= "NODE_MT_nw_copy_label_menu"
3087 bl_label
= "Copy Label"
3089 def draw(self
, context
):
3090 layout
= self
.layout
3091 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3092 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3093 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3096 class NWAddReroutesMenu(Menu
, NWBase
):
3097 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3098 bl_label
= "Add Reroutes"
3099 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3101 def draw(self
, context
):
3102 layout
= self
.layout
3103 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3104 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3105 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3108 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3109 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3110 bl_label
= "Link Active to Selected"
3112 def draw(self
, context
):
3113 layout
= self
.layout
3114 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3115 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3116 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3119 class NWLinkStandardMenu(Menu
, NWBase
):
3120 bl_idname
= "NODE_MT_nw_link_standard_menu"
3121 bl_label
= "To All Selected"
3123 def draw(self
, context
):
3124 layout
= self
.layout
3125 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3126 props
.replace
= False
3127 props
.use_node_name
= False
3128 props
.use_outputs_names
= False
3129 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3130 props
.replace
= True
3131 props
.use_node_name
= False
3132 props
.use_outputs_names
= False
3135 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3136 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3137 bl_label
= "Use Node Name/Label"
3139 def draw(self
, context
):
3140 layout
= self
.layout
3141 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3142 props
.replace
= False
3143 props
.use_node_name
= True
3144 props
.use_outputs_names
= False
3145 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3146 props
.replace
= True
3147 props
.use_node_name
= True
3148 props
.use_outputs_names
= False
3151 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3152 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
3153 bl_label
= "Use Outputs Names"
3155 def draw(self
, context
):
3156 layout
= self
.layout
3157 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3158 props
.replace
= False
3159 props
.use_node_name
= False
3160 props
.use_outputs_names
= True
3161 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3162 props
.replace
= True
3163 props
.use_node_name
= False
3164 props
.use_outputs_names
= True
3167 class NWNodeAlignMenu(Menu
, NWBase
):
3168 bl_idname
= "NODE_MT_nw_node_align_menu"
3169 bl_label
= "Align Nodes"
3171 def draw(self
, context
):
3172 layout
= self
.layout
3173 layout
.operator(NWAlignNodes
.bl_idname
, text
="Horizontally").option
= 'AXIS_X'
3174 layout
.operator(NWAlignNodes
.bl_idname
, text
="Vertically").option
= 'AXIS_Y'
3177 class NWVertColMenu(bpy
.types
.Menu
):
3178 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
3179 bl_label
= "Vertex Colors"
3182 def poll(cls
, context
):
3183 if context
.area
.spaces
[0].node_tree
:
3184 if context
.area
.spaces
[0].node_tree
.type == 'SHADER':
3191 def draw(self
, context
):
3193 nodes
, links
= get_nodes_links(context
)
3194 mat
= context
.object.active_material
3197 for obj
in bpy
.data
.objects
:
3198 for slot
in obj
.material_slots
:
3199 if slot
.material
== mat
:
3203 if obj
.data
.vertex_colors
:
3204 for vcol
in obj
.data
.vertex_colors
:
3205 vcols
.append(vcol
.name
)
3206 vcols
= list(set(vcols
)) # get a unique list
3210 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
3212 l
.label("No Vertex Color layers on objects with this material")
3215 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
3216 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
3217 bl_label
= "Switch Type to..."
3219 def draw(self
, context
):
3220 layout
= self
.layout
3221 tree
= context
.space_data
.node_tree
3222 if tree
.type == 'SHADER':
3223 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
3224 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
3225 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
3226 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
3227 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
3228 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
3229 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
3230 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
3231 if tree
.type == 'COMPOSITING':
3232 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
3233 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
3234 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
3235 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
3236 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
3237 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
3238 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
3239 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
3240 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
3243 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
3244 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
3247 def draw(self
, context
):
3248 layout
= self
.layout
3249 for ident
, type, rna_name
in shaders_input_nodes_props
:
3250 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3251 props
.to_type
= ident
3254 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
3255 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
3258 def draw(self
, context
):
3259 layout
= self
.layout
3260 for ident
, type, rna_name
in shaders_output_nodes_props
:
3261 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3262 props
.to_type
= ident
3265 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
3266 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
3269 def draw(self
, context
):
3270 layout
= self
.layout
3271 for ident
, type, rna_name
in shaders_shader_nodes_props
:
3272 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3273 props
.to_type
= ident
3276 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
3277 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
3278 bl_label
= "Texture"
3280 def draw(self
, context
):
3281 layout
= self
.layout
3282 for ident
, type, rna_name
in shaders_texture_nodes_props
:
3283 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3284 props
.to_type
= ident
3287 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
3288 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
3291 def draw(self
, context
):
3292 layout
= self
.layout
3293 for ident
, type, rna_name
in shaders_color_nodes_props
:
3294 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3295 props
.to_type
= ident
3298 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
3299 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
3302 def draw(self
, context
):
3303 layout
= self
.layout
3304 for ident
, type, rna_name
in shaders_vector_nodes_props
:
3305 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3306 props
.to_type
= ident
3309 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
3310 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
3311 bl_label
= "Converter"
3313 def draw(self
, context
):
3314 layout
= self
.layout
3315 for ident
, type, rna_name
in shaders_converter_nodes_props
:
3316 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3317 props
.to_type
= ident
3320 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
3321 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
3324 def draw(self
, context
):
3325 layout
= self
.layout
3326 for ident
, type, rna_name
in shaders_layout_nodes_props
:
3328 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3329 props
.to_type
= ident
3332 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
3333 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
3336 def draw(self
, context
):
3337 layout
= self
.layout
3338 for ident
, type, rna_name
in compo_input_nodes_props
:
3339 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3340 props
.to_type
= ident
3343 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
3344 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
3347 def draw(self
, context
):
3348 layout
= self
.layout
3349 for ident
, type, rna_name
in compo_output_nodes_props
:
3350 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3351 props
.to_type
= ident
3354 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
3355 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
3358 def draw(self
, context
):
3359 layout
= self
.layout
3360 for ident
, type, rna_name
in compo_color_nodes_props
:
3361 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3362 props
.to_type
= ident
3365 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
3366 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
3367 bl_label
= "Converter"
3369 def draw(self
, context
):
3370 layout
= self
.layout
3371 for ident
, type, rna_name
in compo_converter_nodes_props
:
3372 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3373 props
.to_type
= ident
3376 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
3377 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
3380 def draw(self
, context
):
3381 layout
= self
.layout
3382 for ident
, type, rna_name
in compo_filter_nodes_props
:
3383 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3384 props
.to_type
= ident
3387 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
3388 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
3391 def draw(self
, context
):
3392 layout
= self
.layout
3393 for ident
, type, rna_name
in compo_vector_nodes_props
:
3394 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3395 props
.to_type
= ident
3398 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
3399 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
3402 def draw(self
, context
):
3403 layout
= self
.layout
3404 for ident
, type, rna_name
in compo_matte_nodes_props
:
3405 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3406 props
.to_type
= ident
3409 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
3410 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
3411 bl_label
= "Distort"
3413 def draw(self
, context
):
3414 layout
= self
.layout
3415 for ident
, type, rna_name
in compo_distort_nodes_props
:
3416 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3417 props
.to_type
= ident
3420 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
3421 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
3424 def draw(self
, context
):
3425 layout
= self
.layout
3426 for ident
, type, rna_name
in compo_layout_nodes_props
:
3428 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
3429 props
.to_type
= ident
3433 # APPENDAGES TO EXISTING UI
3437 def select_parent_children_buttons(self
, context
):
3438 layout
= self
.layout
3439 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
3440 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
3443 def attr_nodes_menu_func(self
, context
):
3444 col
= self
.layout
.column(align
=True)
3445 col
.menu("NODE_MT_nw_node_vertex_color_menu")
3449 def multipleimages_menu_func(self
, context
):
3450 col
= self
.layout
.column(align
=True)
3451 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
3452 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
3456 def bgreset_menu_func(self
, context
):
3457 self
.layout
.operator(NWResetBG
.bl_idname
)
3461 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
3465 # kmi_defs entry: (identifier, key, CTRL, SHIFT, ALT, props, nice name)
3466 # props entry: (property name, property value)
3469 # NWMergeNodes with Ctrl (AUTO).
3470 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', True, False, False,
3471 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
3472 (NWMergeNodes
.bl_idname
, 'ZERO', True, False, False,
3473 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
3474 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', True, False, False,
3475 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
3476 (NWMergeNodes
.bl_idname
, 'EQUAL', True, False, False,
3477 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
3478 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', True, False, False,
3479 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
3480 (NWMergeNodes
.bl_idname
, 'EIGHT', True, False, False,
3481 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
3482 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', True, False, False,
3483 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
3484 (NWMergeNodes
.bl_idname
, 'MINUS', True, False, False,
3485 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
3486 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', True, False, False,
3487 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
3488 (NWMergeNodes
.bl_idname
, 'SLASH', True, False, False,
3489 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
3490 (NWMergeNodes
.bl_idname
, 'COMMA', True, False, False,
3491 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
3492 (NWMergeNodes
.bl_idname
, 'PERIOD', True, False, False,
3493 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
3494 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', True, False, False,
3495 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
3496 # NWMergeNodes with Ctrl Alt (MIX)
3497 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', True, False, True,
3498 (('mode', 'MIX'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Mix)"),
3499 (NWMergeNodes
.bl_idname
, 'ZERO', True, False, True,
3500 (('mode', 'MIX'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Mix)"),
3501 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', True, False, True,
3502 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
3503 (NWMergeNodes
.bl_idname
, 'EQUAL', True, False, True,
3504 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
3505 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', True, False, True,
3506 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
3507 (NWMergeNodes
.bl_idname
, 'EIGHT', True, False, True,
3508 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
3509 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', True, False, True,
3510 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
3511 (NWMergeNodes
.bl_idname
, 'MINUS', True, False, True,
3512 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
3513 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', True, False, True,
3514 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
3515 (NWMergeNodes
.bl_idname
, 'SLASH', True, False, True,
3516 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
3517 # NWMergeNodes with Ctrl Shift (MATH)
3518 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', True, True, False,
3519 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
3520 (NWMergeNodes
.bl_idname
, 'EQUAL', True, True, False,
3521 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
3522 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', True, True, False,
3523 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
3524 (NWMergeNodes
.bl_idname
, 'EIGHT', True, True, False,
3525 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
3526 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', True, True, False,
3527 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
3528 (NWMergeNodes
.bl_idname
, 'MINUS', True, True, False,
3529 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
3530 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', True, True, False,
3531 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
3532 (NWMergeNodes
.bl_idname
, 'SLASH', True, True, False,
3533 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
3534 (NWMergeNodes
.bl_idname
, 'COMMA', True, True, False,
3535 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
3536 (NWMergeNodes
.bl_idname
, 'PERIOD', True, True, False,
3537 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
3538 # BATCH CHANGE NODES
3539 # NWBatchChangeNodes with Alt
3540 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', False, False, True,
3541 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
3542 (NWBatchChangeNodes
.bl_idname
, 'ZERO', False, False, True,
3543 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
3544 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', False, False, True,
3545 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
3546 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', False, False, True,
3547 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
3548 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', False, False, True,
3549 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
3550 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', False, False, True,
3551 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
3552 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', False, False, True,
3553 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
3554 (NWBatchChangeNodes
.bl_idname
, 'MINUS', False, False, True,
3555 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
3556 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', False, False, True,
3557 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
3558 (NWBatchChangeNodes
.bl_idname
, 'SLASH', False, False, True,
3559 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
3560 (NWBatchChangeNodes
.bl_idname
, 'COMMA', False, False, True,
3561 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
3562 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', False, False, True,
3563 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
3564 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', False, False, True,
3565 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
3566 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', False, False, True,
3567 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
3568 # LINK ACTIVE TO SELECTED
3569 # Don't use names, don't replace links (K)
3570 (NWLinkActiveToSelected
.bl_idname
, 'K', False, False, False,
3571 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
3572 # Don't use names, replace links (Shift K)
3573 (NWLinkActiveToSelected
.bl_idname
, 'K', False, True, False,
3574 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
3575 # Use node name, don't replace links (')
3576 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', False, False, False,
3577 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
3578 # Use node name, replace links (Shift ')
3579 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', False, True, False,
3580 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
3581 # Don't use names, don't replace links (;)
3582 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', False, False, False,
3583 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
3584 # Don't use names, replace links (')
3585 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', False, True, False,
3586 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
3588 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
3589 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
3590 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
3591 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
3592 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
3593 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
3594 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
3595 (NWChangeMixFactor
.bl_idname
, 'ZERO', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
3596 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
3597 (NWChangeMixFactor
.bl_idname
, 'ONE', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
3598 # CLEAR LABEL (Alt L)
3599 (NWClearLabel
.bl_idname
, 'L', False, False, True, (('option', False),), "Clear node labels"),
3600 # MODIFY LABEL (Alt Shift L)
3601 (NWModifyLabels
.bl_idname
, 'L', False, True, True, None, "Modify node labels"),
3602 # Copy Label from active to selected
3603 (NWCopyLabel
.bl_idname
, 'V', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
3604 # DETACH OUTPUTS (Alt Shift D)
3605 (NWDetachOutputs
.bl_idname
, 'D', False, True, True, None, "Detach outputs"),
3606 # LINK TO OUTPUT NODE (O)
3607 (NWLinkToOutputNode
.bl_idname
, 'O', False, False, False, None, "Link to output node"),
3608 # SELECT PARENT/CHILDREN
3610 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', False, False, False, (('option', 'CHILD'),), "Select children"),
3612 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', False, False, False, (('option', 'PARENT'),), "Select Parent"),
3614 (NWAddTextureSetup
.bl_idname
, 'T', True, False, False, None, "Add texture setup"),
3616 (NWResetBG
.bl_idname
, 'Z', False, False, False, None, "Reset backdrop image zoom"),
3618 (NWDeleteUnused
.bl_idname
, 'X', False, False, True, None, "Delete unused nodes"),
3620 (NWFrameSelected
.bl_idname
, 'P', False, True, False, None, "Frame selected nodes"),
3622 (NWSwapLinks
.bl_idname
, 'S', False, False, True, None, "Swap Outputs"),
3624 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', True, True, False, None, "Connect to Cycles Viewer node"),
3626 (NWReloadImages
.bl_idname
, 'R', False, False, True, None, "Reload images"),
3628 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', False, False, True, None, "Lazy Mix"),
3630 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', True, False, False, None, "Lazy Connect"),
3631 # Lazy Connect with Menu
3632 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
3634 ('wm.call_menu', 'SPACE', True, False, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wranger menu"),
3635 ('wm.call_menu', 'SLASH', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
3636 ('wm.call_menu', 'NUMPAD_SLASH', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
3637 ('wm.call_menu', 'EQUAL', False, True, False, (('name', NWNodeAlignMenu
.bl_idname
),), "Node alignment menu"),
3638 ('wm.call_menu', 'BACK_SLASH', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
3639 ('wm.call_menu', 'C', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
3640 ('wm.call_menu', 'S', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
3646 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
3647 name
="Busy Drawing!",
3649 description
="An internal property used to store only the first mouse position")
3650 bpy
.types
.Scene
.NWLazySource
= StringProperty(
3651 name
="Lazy Source!",
3653 description
="An internal property used to store the first node in a Lazy Connect operation")
3654 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
3655 name
="Lazy Target!",
3657 description
="An internal property used to store the last node in a Lazy Connect operation")
3658 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
3659 name
="Source Socket!",
3661 description
="An internal property used to store the source socket in a Lazy Connect operation")
3663 bpy
.utils
.register_module(__name__
)
3666 km
= bpy
.context
.window_manager
.keyconfigs
.addon
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
3667 for (identifier
, key
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
3668 kmi
= km
.keymap_items
.new(identifier
, key
, 'PRESS', ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
3670 for prop
, value
in props
:
3671 setattr(kmi
.properties
, prop
, value
)
3672 addon_keymaps
.append((km
, kmi
))
3675 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
3676 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
3677 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
3678 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
3679 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
3680 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
3681 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
3682 bpy
.types
.NODE_PT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
3687 del bpy
.types
.Scene
.NWBusyDrawing
3688 del bpy
.types
.Scene
.NWLazySource
3689 del bpy
.types
.Scene
.NWLazyTarget
3690 del bpy
.types
.Scene
.NWSourceSocket
3692 bpy
.utils
.unregister_module(__name__
)
3695 for km
, kmi
in addon_keymaps
:
3696 km
.keymap_items
.remove(kmi
)
3697 addon_keymaps
.clear()
3700 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
3701 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
3702 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
3703 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
3704 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
3705 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
3706 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
3707 bpy
.types
.NODE_PT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
3709 if __name__
== "__main__":