Update 3d-print toolbox to only export selection
[blender-addons.git] / node_wrangler.py
blob3604e80b00b6a4458d45686dba155a34cf950113
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_info = {
20 "name": "Node Wrangler",
21 "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig",
22 "version": (3, 29),
23 "blender": (2, 75, 0),
24 "location": "Node Editor Toolbar or Ctrl-Space",
25 "description": "Various tools to enhance and speed up node-based workflow",
26 "warning": "",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Nodes/Nodes_Efficiency_Tools",
29 "category": "Node",
32 import bpy, blf, bgl
33 from bpy.types import Operator, Panel, Menu
34 from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty, FloatVectorProperty, CollectionProperty
35 from bpy_extras.io_utils import ImportHelper
36 from mathutils import Vector
37 from math import cos, sin, pi, hypot
38 from os import path
39 from glob import glob
40 from copy import copy
42 #################
43 # rl_outputs:
44 # list of outputs of Input Render Layer
45 # with attributes determinig if pass is used,
46 # and MultiLayer EXR outputs names and corresponding render engines
48 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_internal, in_cycles)
49 rl_outputs = (
50 ('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
51 ('use_pass_color', 'Color', 'Color', True, False),
52 ('use_pass_combined', 'Image', 'Combined', True, True),
53 ('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
54 ('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
55 ('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
56 ('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
57 ('use_pass_emit', 'Emit', 'Emit', True, False),
58 ('use_pass_environment', 'Environment', 'Env', True, False),
59 ('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
60 ('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
61 ('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
62 ('use_pass_indirect', 'Indirect', 'Indirect', True, False),
63 ('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
64 ('use_pass_mist', 'Mist', 'Mist', True, False),
65 ('use_pass_normal', 'Normal', 'Normal', True, True),
66 ('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
67 ('use_pass_reflection', 'Reflect', 'Reflect', True, False),
68 ('use_pass_refraction', 'Refract', 'Refract', True, False),
69 ('use_pass_shadow', 'Shadow', 'Shadow', True, True),
70 ('use_pass_specular', 'Specular', 'Spec', True, False),
71 ('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', False, True),
72 ('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', False, True),
73 ('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
74 ('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
75 ('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
76 ('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
77 ('use_pass_uv', 'UV', 'UV', True, True),
78 ('use_pass_vector', 'Speed', 'Vector', True, True),
79 ('use_pass_z', 'Z', 'Depth', True, True),
82 # shader nodes
83 # (rna_type.identifier, type, rna_type.name)
84 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
85 shaders_input_nodes_props = (
86 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
87 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
88 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
89 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
90 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
91 ('ShaderNodeRGB', 'RGB', 'RGB'),
92 ('ShaderNodeValue', 'VALUE', 'Value'),
93 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
94 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
95 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
96 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
97 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
98 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
99 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
100 ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
102 # (rna_type.identifier, type, rna_type.name)
103 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
104 shaders_output_nodes_props = (
105 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
106 ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp Output'),
107 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
109 # (rna_type.identifier, type, rna_type.name)
110 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
111 shaders_shader_nodes_props = (
112 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
113 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
114 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
115 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
116 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
117 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
118 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
119 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
120 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
121 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
122 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
123 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
124 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
125 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
126 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
127 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
128 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
129 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
130 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
132 # (rna_type.identifier, type, rna_type.name)
133 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
134 shaders_texture_nodes_props = (
135 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image'),
136 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment'),
137 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky'),
138 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise'),
139 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave'),
140 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
141 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
142 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient'),
143 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic'),
144 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker'),
145 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick')
147 # (rna_type.identifier, type, rna_type.name)
148 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
149 shaders_color_nodes_props = (
150 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
151 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
152 ('ShaderNodeInvert', 'INVERT', 'Invert'),
153 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
154 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
155 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
156 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
158 # (rna_type.identifier, type, rna_type.name)
159 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
160 shaders_vector_nodes_props = (
161 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
162 ('ShaderNodeBump', 'BUMP', 'Bump'),
163 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
164 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
165 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
166 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
168 # (rna_type.identifier, type, rna_type.name)
169 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
170 shaders_converter_nodes_props = (
171 ('ShaderNodeMath', 'MATH', 'Math'),
172 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
173 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
174 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
175 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
176 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
177 ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
178 ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
179 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
180 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
181 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
182 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
184 # (rna_type.identifier, type, rna_type.name)
185 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
186 shaders_layout_nodes_props = (
187 ('NodeFrame', 'FRAME', 'Frame'),
188 ('NodeReroute', 'REROUTE', 'Reroute'),
191 # compositing nodes
192 # (rna_type.identifier, type, rna_type.name)
193 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
194 compo_input_nodes_props = (
195 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
196 ('CompositorNodeImage', 'IMAGE', 'Image'),
197 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
198 ('CompositorNodeMask', 'MASK', 'Mask'),
199 ('CompositorNodeRGB', 'RGB', 'RGB'),
200 ('CompositorNodeValue', 'VALUE', 'Value'),
201 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
202 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
203 ('CompositorNodeTime', 'TIME', 'Time'),
204 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
206 # (rna_type.identifier, type, rna_type.name)
207 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
208 compo_output_nodes_props = (
209 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
210 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
211 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
212 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
213 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
215 # (rna_type.identifier, type, rna_type.name)
216 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
217 compo_color_nodes_props = (
218 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
219 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
220 ('CompositorNodeInvert', 'INVERT', 'Invert'),
221 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
222 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
223 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
224 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
225 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
226 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
227 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
228 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
229 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
231 # (rna_type.identifier, type, rna_type.name)
232 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
233 compo_converter_nodes_props = (
234 ('CompositorNodeMath', 'MATH', 'Math'),
235 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
236 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
237 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
238 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
239 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
240 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
241 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
242 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
243 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
244 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
245 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
246 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
247 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
249 # (rna_type.identifier, type, rna_type.name)
250 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
251 compo_filter_nodes_props = (
252 ('CompositorNodeBlur', 'BLUR', 'Blur'),
253 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
254 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
255 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
256 ('CompositorNodeFilter', 'FILTER', 'Filter'),
257 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
258 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
259 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
260 ('CompositorNodeGlare', 'GLARE', 'Glare'),
261 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
262 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
263 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
264 ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
266 # (rna_type.identifier, type, rna_type.name)
267 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
268 compo_vector_nodes_props = (
269 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
270 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
271 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
272 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
273 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
275 # (rna_type.identifier, type, rna_type.name)
276 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
277 compo_matte_nodes_props = (
278 ('CompositorNodeKeying', 'KEYING', 'Keying'),
279 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
280 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
281 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
282 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
283 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
284 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
285 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
286 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
287 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
288 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
289 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
291 # (rna_type.identifier, type, rna_type.name)
292 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
293 compo_distort_nodes_props = (
294 ('CompositorNodeScale', 'SCALE', 'Scale'),
295 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
296 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
297 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
298 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
299 ('CompositorNodeFlip', 'FLIP', 'Flip'),
300 ('CompositorNodeCrop', 'CROP', 'Crop'),
301 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
302 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
303 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
304 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
305 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
306 ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
308 # (rna_type.identifier, type, rna_type.name)
309 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
310 compo_layout_nodes_props = (
311 ('NodeFrame', 'FRAME', 'Frame'),
312 ('NodeReroute', 'REROUTE', 'Reroute'),
313 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
315 # Blender Render material nodes
316 # (rna_type.identifier, type, rna_type.name)
317 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
318 blender_mat_input_nodes_props = (
319 ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
320 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
321 ('ShaderNodeLampData', 'LAMP', 'Lamp Data'),
322 ('ShaderNodeValue', 'VALUE', 'Value'),
323 ('ShaderNodeRGB', 'RGB', 'RGB'),
324 ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
325 ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
326 ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
329 # (rna_type.identifier, type, rna_type.name)
330 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
331 blender_mat_output_nodes_props = (
332 ('ShaderNodeOutput', 'OUTPUT', 'Output'),
335 # (rna_type.identifier, type, rna_type.name)
336 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
337 blender_mat_color_nodes_props = (
338 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
339 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
340 ('ShaderNodeInvert', 'INVERT', 'Invert'),
341 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
344 # (rna_type.identifier, type, rna_type.name)
345 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
346 blender_mat_vector_nodes_props = (
347 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
348 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
349 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
352 # (rna_type.identifier, type, rna_type.name)
353 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
354 blender_mat_converter_nodes_props = (
355 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
356 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
357 ('ShaderNodeMath', 'MATH', 'Math'),
358 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
359 ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
360 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
361 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
362 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
363 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
366 # (rna_type.identifier, type, rna_type.name)
367 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
368 blender_mat_layout_nodes_props = (
369 ('NodeReroute', 'REROUTE', 'Reroute'),
372 # Texture Nodes
373 # (rna_type.identifier, type, rna_type.name)
374 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
375 texture_input_nodes_props = (
376 ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
377 ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
378 ('TextureNodeTexture', 'TEXTURE', 'Texture'),
379 ('TextureNodeImage', 'IMAGE', 'Image'),
382 # (rna_type.identifier, type, rna_type.name)
383 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
384 texture_output_nodes_props = (
385 ('TextureNodeOutput', 'OUTPUT', 'Output'),
386 ('TextureNodeViewer', 'VIEWER', 'Viewer'),
389 # (rna_type.identifier, type, rna_type.name)
390 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
391 texture_color_nodes_props = (
392 ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
393 ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
394 ('TextureNodeInvert', 'INVERT', 'Invert'),
395 ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
396 ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
397 ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
400 # (rna_type.identifier, type, rna_type.name)
401 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
402 texture_pattern_nodes_props = (
403 ('TextureNodeChecker', 'CHECKER', 'Checker'),
404 ('TextureNodeBricks', 'BRICKS', 'Bricks'),
407 # (rna_type.identifier, type, rna_type.name)
408 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
409 texture_textures_nodes_props = (
410 ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
411 ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
412 ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
413 ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
414 ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
415 ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
416 ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
417 ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
418 ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
419 ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
422 # (rna_type.identifier, type, rna_type.name)
423 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
424 texture_converter_nodes_props = (
425 ('TextureNodeMath', 'MATH', 'Math'),
426 ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
427 ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
428 ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
429 ('TextureNodeDistance', 'DISTANCE', 'Distance'),
432 # (rna_type.identifier, type, rna_type.name)
433 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
434 texture_distort_nodes_props = (
435 ('TextureNodeScale', 'SCALE', 'Scale'),
436 ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
437 ('TextureNodeRotate', 'ROTATE', 'Rotate'),
438 ('TextureNodeAt', 'AT', 'At'),
441 # (rna_type.identifier, type, rna_type.name)
442 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
443 texture_layout_nodes_props = (
444 ('NodeReroute', 'REROUTE', 'Reroute'),
447 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
448 # used list, not tuple for easy merging with other lists.
449 blend_types = [
450 ('MIX', 'Mix', 'Mix Mode'),
451 ('ADD', 'Add', 'Add Mode'),
452 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
453 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
454 ('SCREEN', 'Screen', 'Screen Mode'),
455 ('DIVIDE', 'Divide', 'Divide Mode'),
456 ('DIFFERENCE', 'Difference', 'Difference Mode'),
457 ('DARKEN', 'Darken', 'Darken Mode'),
458 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
459 ('OVERLAY', 'Overlay', 'Overlay Mode'),
460 ('DODGE', 'Dodge', 'Dodge Mode'),
461 ('BURN', 'Burn', 'Burn Mode'),
462 ('HUE', 'Hue', 'Hue Mode'),
463 ('SATURATION', 'Saturation', 'Saturation Mode'),
464 ('VALUE', 'Value', 'Value Mode'),
465 ('COLOR', 'Color', 'Color Mode'),
466 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
467 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
470 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
471 # used list, not tuple for easy merging with other lists.
472 operations = [
473 ('ADD', 'Add', 'Add Mode'),
474 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
475 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
476 ('DIVIDE', 'Divide', 'Divide Mode'),
477 ('SINE', 'Sine', 'Sine Mode'),
478 ('COSINE', 'Cosine', 'Cosine Mode'),
479 ('TANGENT', 'Tangent', 'Tangent Mode'),
480 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
481 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
482 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
483 ('POWER', 'Power', 'Power Mode'),
484 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
485 ('MINIMUM', 'Minimum', 'Minimum Mode'),
486 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
487 ('ROUND', 'Round', 'Round Mode'),
488 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
489 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
490 ('MODULO', 'Modulo', 'Modulo Mode'),
491 ('ABSOLUTE', 'Absolute', 'Absolute Mode'),
494 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
495 # used list, not tuple for easy merging with other lists.
496 navs = [
497 ('CURRENT', 'Current', 'Leave at current state'),
498 ('NEXT', 'Next', 'Next blend type/operation'),
499 ('PREV', 'Prev', 'Previous blend type/operation'),
502 draw_color_sets = {
503 "red_white": (
504 (1.0, 1.0, 1.0, 0.7),
505 (1.0, 0.0, 0.0, 0.7),
506 (0.8, 0.2, 0.2, 1.0)
508 "green": (
509 (0.0, 0.0, 0.0, 1.0),
510 (0.38, 0.77, 0.38, 1.0),
511 (0.38, 0.77, 0.38, 1.0)
513 "yellow": (
514 (0.0, 0.0, 0.0, 1.0),
515 (0.77, 0.77, 0.16, 1.0),
516 (0.77, 0.77, 0.16, 1.0)
518 "purple": (
519 (0.0, 0.0, 0.0, 1.0),
520 (0.38, 0.38, 0.77, 1.0),
521 (0.38, 0.38, 0.77, 1.0)
523 "grey": (
524 (0.0, 0.0, 0.0, 1.0),
525 (0.63, 0.63, 0.63, 1.0),
526 (0.63, 0.63, 0.63, 1.0)
528 "black": (
529 (1.0, 1.0, 1.0, 0.7),
530 (0.0, 0.0, 0.0, 0.7),
531 (0.2, 0.2, 0.2, 1.0)
536 def nice_hotkey_name(punc):
537 # convert the ugly string name into the actual character
538 pairs = (
539 ('LEFTMOUSE', "LMB"),
540 ('MIDDLEMOUSE', "MMB"),
541 ('RIGHTMOUSE', "RMB"),
542 ('SELECTMOUSE', "Select"),
543 ('WHEELUPMOUSE', "Wheel Up"),
544 ('WHEELDOWNMOUSE', "Wheel Down"),
545 ('WHEELINMOUSE', "Wheel In"),
546 ('WHEELOUTMOUSE', "Wheel Out"),
547 ('ZERO', "0"),
548 ('ONE', "1"),
549 ('TWO', "2"),
550 ('THREE', "3"),
551 ('FOUR', "4"),
552 ('FIVE', "5"),
553 ('SIX', "6"),
554 ('SEVEN', "7"),
555 ('EIGHT', "8"),
556 ('NINE', "9"),
557 ('OSKEY', "Super"),
558 ('RET', "Enter"),
559 ('LINE_FEED', "Enter"),
560 ('SEMI_COLON', ";"),
561 ('PERIOD', "."),
562 ('COMMA', ","),
563 ('QUOTE', '"'),
564 ('MINUS', "-"),
565 ('SLASH', "/"),
566 ('BACK_SLASH', "\\"),
567 ('EQUAL', "="),
568 ('NUMPAD_1', "Numpad 1"),
569 ('NUMPAD_2', "Numpad 2"),
570 ('NUMPAD_3', "Numpad 3"),
571 ('NUMPAD_4', "Numpad 4"),
572 ('NUMPAD_5', "Numpad 5"),
573 ('NUMPAD_6', "Numpad 6"),
574 ('NUMPAD_7', "Numpad 7"),
575 ('NUMPAD_8', "Numpad 8"),
576 ('NUMPAD_9', "Numpad 9"),
577 ('NUMPAD_0', "Numpad 0"),
578 ('NUMPAD_PERIOD', "Numpad ."),
579 ('NUMPAD_SLASH', "Numpad /"),
580 ('NUMPAD_ASTERIX', "Numpad *"),
581 ('NUMPAD_MINUS', "Numpad -"),
582 ('NUMPAD_ENTER', "Numpad Enter"),
583 ('NUMPAD_PLUS', "Numpad +"),
585 nice_punc = False
586 for (ugly, nice) in pairs:
587 if punc == ugly:
588 nice_punc = nice
589 break
590 if not nice_punc:
591 nice_punc = punc.replace("_", " ").title()
592 return nice_punc
595 def hack_force_update(context, nodes):
596 if context.space_data.tree_type == "ShaderNodeTree":
597 node = nodes.new('ShaderNodeMath')
598 node.inputs[0].default_value = 0.0
599 nodes.remove(node)
600 elif context.space_data.tree_type == "CompositorNodeTree":
601 node = nodes.new('CompositorNodeMath')
602 node.inputs[0].default_value = 0.0
603 nodes.remove(node)
604 return False
607 def dpifac():
608 prefs = bpy.context.user_preferences.system
609 if hasattr(prefs, 'pixel_size'): # python access to this was only added recently, assume non-retina display is used if using older blender
610 retinafac = bpy.context.user_preferences.system.pixel_size
611 else:
612 retinafac = 1
613 return bpy.context.user_preferences.system.dpi/(72/retinafac)
616 def is_end_node(node):
617 bool = True
618 for output in node.outputs:
619 if output.links:
620 bool = False
621 break
622 return bool
625 def node_mid_pt(node, axis):
626 if axis == 'x':
627 d = node.location.x + (node.dimensions.x / 2)
628 elif axis == 'y':
629 d = node.location.y - (node.dimensions.y / 2)
630 else:
631 d = 0
632 return d
635 def autolink(node1, node2, links):
636 link_made = False
638 for outp in node1.outputs:
639 for inp in node2.inputs:
640 if not inp.is_linked and inp.name == outp.name:
641 link_made = True
642 links.new(outp, inp)
643 return True
645 for outp in node1.outputs:
646 for inp in node2.inputs:
647 if not inp.is_linked and inp.type == outp.type:
648 link_made = True
649 links.new(outp, inp)
650 return True
652 # force some connection even if the type doesn't match
653 for outp in node1.outputs:
654 for inp in node2.inputs:
655 if not inp.is_linked:
656 link_made = True
657 links.new(outp, inp)
658 return True
660 # even if no sockets are open, force one of matching type
661 for outp in node1.outputs:
662 for inp in node2.inputs:
663 if inp.type == outp.type:
664 link_made = True
665 links.new(outp, inp)
666 return True
668 # do something!
669 for outp in node1.outputs:
670 for inp in node2.inputs:
671 link_made = True
672 links.new(outp, inp)
673 return True
675 print("Could not make a link from " + node1.name + " to " + node2.name)
676 return link_made
679 def node_at_pos(nodes, context, event):
680 nodes_near_mouse = []
681 nodes_under_mouse = []
682 target_node = None
684 store_mouse_cursor(context, event)
685 x, y = context.space_data.cursor_location
686 x = x
687 y = y
689 # Make a list of each corner (and middle of border) for each node.
690 # Will be sorted to find nearest point and thus nearest node
691 node_points_with_dist = []
692 for node in nodes:
693 skipnode = False
694 if node.type != 'FRAME': # no point trying to link to a frame node
695 locx = node.location.x
696 locy = node.location.y
697 dimx = node.dimensions.x/dpifac()
698 dimy = node.dimensions.y/dpifac()
699 if node.parent:
700 locx += node.parent.location.x
701 locy += node.parent.location.y
702 if node.parent.parent:
703 locx += node.parent.parent.location.x
704 locy += node.parent.parent.location.y
705 if node.parent.parent.parent:
706 locx += node.parent.parent.parent.location.x
707 locy += node.parent.parent.parent.location.y
708 if node.parent.parent.parent.parent:
709 # Support three levels or parenting
710 # There's got to be a better way to do this...
711 skipnode = True
712 if not skipnode:
713 node_points_with_dist.append([node, hypot(x - locx, y - locy)]) # Top Left
714 node_points_with_dist.append([node, hypot(x - (locx + dimx), y - locy)]) # Top Right
715 node_points_with_dist.append([node, hypot(x - locx, y - (locy - dimy))]) # Bottom Left
716 node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - dimy))]) # Bottom Right
718 node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - locy)]) # Mid Top
719 node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - (locy - dimy))]) # Mid Bottom
720 node_points_with_dist.append([node, hypot(x - locx, y - (locy - (dimy / 2)))]) # Mid Left
721 node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - (dimy / 2)))]) # Mid Right
723 nearest_node = sorted(node_points_with_dist, key=lambda k: k[1])[0][0]
725 for node in nodes:
726 if node.type != 'FRAME' and skipnode == False:
727 locx = node.location.x
728 locy = node.location.y
729 dimx = node.dimensions.x/dpifac()
730 dimy = node.dimensions.y/dpifac()
731 if node.parent:
732 locx += node.parent.location.x
733 locy += node.parent.location.y
734 if (locx <= x <= locx + dimx) and \
735 (locy - dimy <= y <= locy):
736 nodes_under_mouse.append(node)
738 if len(nodes_under_mouse) == 1:
739 if nodes_under_mouse[0] != nearest_node:
740 target_node = nodes_under_mouse[0] # use the node under the mouse if there is one and only one
741 else:
742 target_node = nearest_node # else use the nearest node
743 else:
744 target_node = nearest_node
745 return target_node
748 def store_mouse_cursor(context, event):
749 space = context.space_data
750 v2d = context.region.view2d
751 tree = space.edit_tree
753 # convert mouse position to the View2D for later node placement
754 if context.region.type == 'WINDOW':
755 space.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
756 else:
757 space.cursor_location = tree.view_center
760 def draw_line(x1, y1, x2, y2, size, colour=[1.0, 1.0, 1.0, 0.7]):
761 bgl.glEnable(bgl.GL_BLEND)
762 bgl.glLineWidth(size * dpifac())
763 bgl.glShadeModel(bgl.GL_SMOOTH)
764 bgl.glEnable(bgl.GL_LINE_SMOOTH)
766 bgl.glBegin(bgl.GL_LINE_STRIP)
767 try:
768 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)
769 bgl.glVertex2f(x1, y1)
770 bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
771 bgl.glVertex2f(x2, y2)
772 except:
773 pass
774 bgl.glEnd()
775 bgl.glShadeModel(bgl.GL_FLAT)
776 bgl.glDisable(bgl.GL_LINE_SMOOTH)
779 def draw_circle(mx, my, radius, colour=[1.0, 1.0, 1.0, 0.7]):
780 bgl.glEnable(bgl.GL_LINE_SMOOTH)
781 bgl.glBegin(bgl.GL_TRIANGLE_FAN)
782 bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
783 radius = radius * dpifac()
784 sides = 12
785 for i in range(sides + 1):
786 cosine = radius * cos(i * 2 * pi / sides) + mx
787 sine = radius * sin(i * 2 * pi / sides) + my
788 bgl.glVertex2f(cosine, sine)
789 bgl.glEnd()
790 bgl.glDisable(bgl.GL_LINE_SMOOTH)
793 def draw_rounded_node_border(node, radius=8, colour=[1.0, 1.0, 1.0, 0.7]):
794 bgl.glEnable(bgl.GL_BLEND)
795 bgl.glEnable(bgl.GL_LINE_SMOOTH)
797 area_width = bpy.context.area.width - (16*dpifac()) - 1
798 bottom_bar = (16*dpifac()) + 1
799 sides = 16
800 radius = radius*dpifac()
801 bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
803 nlocx = (node.location.x+1)*dpifac()
804 nlocy = (node.location.y+1)*dpifac()
805 ndimx = node.dimensions.x
806 ndimy = node.dimensions.y
807 # This is a stupid way to do this... TODO use while loop
808 if node.parent:
809 nlocx += node.parent.location.x
810 nlocy += node.parent.location.y
811 if node.parent.parent:
812 nlocx += node.parent.parent.location.x
813 nlocy += node.parent.parent.location.y
814 if node.parent.parent.parent:
815 nlocx += node.parent.parent.parent.location.x
816 nlocy += node.parent.parent.parent.location.y
818 if node.hide:
819 nlocx += -1
820 nlocy += 5
821 if node.type == 'REROUTE':
822 #nlocx += 1
823 nlocy -= 1
824 ndimx = 0
825 ndimy = 0
826 radius += 6
828 # Top left corner
829 bgl.glBegin(bgl.GL_TRIANGLE_FAN)
830 mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
831 bgl.glVertex2f(mx,my)
832 for i in range(sides+1):
833 if (4<=i<=8):
834 if my > bottom_bar and mx < area_width:
835 cosine = radius * cos(i * 2 * pi / sides) + mx
836 sine = radius * sin(i * 2 * pi / sides) + my
837 bgl.glVertex2f(cosine, sine)
838 bgl.glEnd()
840 # Top right corner
841 bgl.glBegin(bgl.GL_TRIANGLE_FAN)
842 mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
843 bgl.glVertex2f(mx,my)
844 for i in range(sides+1):
845 if (0<=i<=4):
846 if my > bottom_bar and mx < area_width:
847 cosine = radius * cos(i * 2 * pi / sides) + mx
848 sine = radius * sin(i * 2 * pi / sides) + my
849 bgl.glVertex2f(cosine, sine)
850 bgl.glEnd()
852 # Bottom left corner
853 bgl.glBegin(bgl.GL_TRIANGLE_FAN)
854 mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
855 bgl.glVertex2f(mx,my)
856 for i in range(sides+1):
857 if (8<=i<=12):
858 if my > bottom_bar and mx < area_width:
859 cosine = radius * cos(i * 2 * pi / sides) + mx
860 sine = radius * sin(i * 2 * pi / sides) + my
861 bgl.glVertex2f(cosine, sine)
862 bgl.glEnd()
864 # Bottom right corner
865 bgl.glBegin(bgl.GL_TRIANGLE_FAN)
866 mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
867 bgl.glVertex2f(mx,my)
868 for i in range(sides+1):
869 if (12<=i<=16):
870 if my > bottom_bar and mx < area_width:
871 cosine = radius * cos(i * 2 * pi / sides) + mx
872 sine = radius * sin(i * 2 * pi / sides) + my
873 bgl.glVertex2f(cosine, sine)
874 bgl.glEnd()
877 # Left edge
878 bgl.glBegin(bgl.GL_QUADS)
879 m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
880 m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
881 m1y = max(m1y, bottom_bar)
882 m2y = max(m2y, bottom_bar)
883 if m1x < area_width and m2x < area_width:
884 bgl.glVertex2f(m2x-radius,m2y) # draw order is important, start with bottom left and go anti-clockwise
885 bgl.glVertex2f(m2x,m2y)
886 bgl.glVertex2f(m1x,m1y)
887 bgl.glVertex2f(m1x-radius,m1y)
888 bgl.glEnd()
890 # Top edge
891 bgl.glBegin(bgl.GL_QUADS)
892 m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
893 m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
894 m1x = min(m1x, area_width)
895 m2x = min(m2x, area_width)
896 if m1y > bottom_bar and m2y > bottom_bar:
897 bgl.glVertex2f(m1x,m2y) # draw order is important, start with bottom left and go anti-clockwise
898 bgl.glVertex2f(m2x,m2y)
899 bgl.glVertex2f(m2x,m1y+radius)
900 bgl.glVertex2f(m1x,m1y+radius)
901 bgl.glEnd()
903 # Right edge
904 bgl.glBegin(bgl.GL_QUADS)
905 m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
906 m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
907 m1y = max(m1y, bottom_bar)
908 m2y = max(m2y, bottom_bar)
909 if m1x < area_width and m2x < area_width:
910 bgl.glVertex2f(m2x,m2y) # draw order is important, start with bottom left and go anti-clockwise
911 bgl.glVertex2f(m2x+radius,m2y)
912 bgl.glVertex2f(m1x+radius,m1y)
913 bgl.glVertex2f(m1x,m1y)
914 bgl.glEnd()
916 # Bottom edge
917 bgl.glBegin(bgl.GL_QUADS)
918 m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy-ndimy, clip=False)
919 m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy-ndimy, clip=False)
920 m1x = min(m1x, area_width)
921 m2x = min(m2x, area_width)
922 if m1y > bottom_bar and m2y > bottom_bar:
923 bgl.glVertex2f(m1x,m2y) # draw order is important, start with bottom left and go anti-clockwise
924 bgl.glVertex2f(m2x,m2y)
925 bgl.glVertex2f(m2x,m1y-radius)
926 bgl.glVertex2f(m1x,m1y-radius)
927 bgl.glEnd()
930 # Restore defaults
931 bgl.glDisable(bgl.GL_BLEND)
932 bgl.glDisable(bgl.GL_LINE_SMOOTH)
935 def draw_callback_nodeoutline(self, context, mode):
936 if self.mouse_path:
937 nodes, links = get_nodes_links(context)
938 bgl.glEnable(bgl.GL_LINE_SMOOTH)
940 if mode == "LINK":
941 col_outer = [1.0, 0.2, 0.2, 0.4]
942 col_inner = [0.0, 0.0, 0.0, 0.5]
943 col_circle_inner = [0.3, 0.05, 0.05, 1.0]
944 elif mode == "LINKMENU":
945 col_outer = [0.4, 0.6, 1.0, 0.4]
946 col_inner = [0.0, 0.0, 0.0, 0.5]
947 col_circle_inner = [0.08, 0.15, .3, 1.0]
948 elif mode == "MIX":
949 col_outer = [0.2, 1.0, 0.2, 0.4]
950 col_inner = [0.0, 0.0, 0.0, 0.5]
951 col_circle_inner = [0.05, 0.3, 0.05, 1.0]
953 m1x = self.mouse_path[0][0]
954 m1y = self.mouse_path[0][1]
955 m2x = self.mouse_path[-1][0]
956 m2y = self.mouse_path[-1][1]
958 n1 = nodes[context.scene.NWLazySource]
959 n2 = nodes[context.scene.NWLazyTarget]
961 if n1 == n2:
962 col_outer = [0.4, 0.4, 0.4, 0.4]
963 col_inner = [0.0, 0.0, 0.0, 0.5]
964 col_circle_inner = [0.2, 0.2, 0.2, 1.0]
966 draw_rounded_node_border(n1, radius=6, colour=col_outer) # outline
967 draw_rounded_node_border(n1, radius=5, colour=col_inner) # inner
968 draw_rounded_node_border(n2, radius=6, colour=col_outer) # outline
969 draw_rounded_node_border(n2, radius=5, colour=col_inner) # inner
971 draw_line(m1x, m1y, m2x, m2y, 5, col_outer) # line outline
972 draw_line(m1x, m1y, m2x, m2y, 2, col_inner) # line inner
974 # circle outline
975 draw_circle(m1x, m1y, 7, col_outer)
976 draw_circle(m2x, m2y, 7, col_outer)
978 # circle inner
979 draw_circle(m1x, m1y, 5, col_circle_inner)
980 draw_circle(m2x, m2y, 5, col_circle_inner)
982 # restore opengl defaults
983 bgl.glLineWidth(1)
984 bgl.glDisable(bgl.GL_BLEND)
985 bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
987 bgl.glDisable(bgl.GL_LINE_SMOOTH)
990 def get_nodes_links(context):
991 space = context.space_data
992 tree = space.node_tree
993 nodes = tree.nodes
994 links = tree.links
995 active = nodes.active
996 context_active = context.active_node
997 # check if we are working on regular node tree or node group is currently edited.
998 # if group is edited - active node of space_tree is the group
999 # if context.active_node != space active node - it means that the group is being edited.
1000 # in such case we set "nodes" to be nodes of this group, "links" to be links of this group
1001 # if context.active_node == space.active_node it means that we are not currently editing group
1002 is_main_tree = True
1003 if active:
1004 is_main_tree = context_active == active
1005 if not is_main_tree: # if group is currently edited
1006 tree = active.node_tree
1007 nodes = tree.nodes
1008 links = tree.links
1010 return nodes, links
1013 # Addon prefs
1014 class NWNodeWrangler(bpy.types.AddonPreferences):
1015 bl_idname = __name__
1017 merge_hide = EnumProperty(
1018 name="Hide Mix nodes",
1019 items=(
1020 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1021 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1022 ("NEVER", "Never", "Never collapse the new merge nodes")
1024 default='NON_SHADER',
1025 description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy whether to collapse them or show the full node with options expanded")
1026 merge_position = EnumProperty(
1027 name="Mix Node Position",
1028 items=(
1029 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1030 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1032 default='CENTER',
1033 description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy the position of the new nodes")
1035 show_hotkey_list = BoolProperty(
1036 name="Show Hotkey List",
1037 default=False,
1038 description="Expand this box into a list of all the hotkeys for functions in this addon"
1040 hotkey_list_filter = StringProperty(
1041 name=" Filter by Name",
1042 default="",
1043 description="Show only hotkeys that have this text in their name"
1046 def draw(self, context):
1047 layout = self.layout
1048 col = layout.column()
1049 col.prop(self, "merge_position")
1050 col.prop(self, "merge_hide")
1052 box = col.box()
1053 col = box.column(align=True)
1055 hotkey_button_name = "Show Hotkey List"
1056 if self.show_hotkey_list:
1057 hotkey_button_name = "Hide Hotkey List"
1058 col.prop(self, "show_hotkey_list", text=hotkey_button_name, toggle=True)
1059 if self.show_hotkey_list:
1060 col.prop(self, "hotkey_list_filter", icon="VIEWZOOM")
1061 col.separator()
1062 for hotkey in kmi_defs:
1063 if hotkey[7]:
1064 hotkey_name = hotkey[7]
1066 if self.hotkey_list_filter.lower() in hotkey_name.lower():
1067 row = col.row(align=True)
1068 row.label(hotkey_name)
1069 keystr = nice_hotkey_name(hotkey[1])
1070 if hotkey[4]:
1071 keystr = "Shift " + keystr
1072 if hotkey[5]:
1073 keystr = "Alt " + keystr
1074 if hotkey[3]:
1075 keystr = "Ctrl " + keystr
1076 row.label(keystr)
1078 def nw_check(context):
1079 space = context.space_data
1080 valid = False
1081 if space.type == 'NODE_EDITOR' and space.node_tree is not None:
1082 valid = True
1084 return valid
1086 class NWBase:
1087 @classmethod
1088 def poll(cls, context):
1089 return nw_check(context)
1092 # OPERATORS
1093 class NWLazyMix(Operator, NWBase):
1094 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1095 bl_idname = "node.nw_lazy_mix"
1096 bl_label = "Mix Nodes"
1097 bl_options = {'REGISTER', 'UNDO'}
1099 def modal(self, context, event):
1100 context.area.tag_redraw()
1101 nodes, links = get_nodes_links(context)
1102 cont = True
1104 start_pos = [event.mouse_region_x, event.mouse_region_y]
1106 node1 = None
1107 if not context.scene.NWBusyDrawing:
1108 node1 = node_at_pos(nodes, context, event)
1109 if node1:
1110 context.scene.NWBusyDrawing = node1.name
1111 else:
1112 if context.scene.NWBusyDrawing != 'STOP':
1113 node1 = nodes[context.scene.NWBusyDrawing]
1115 context.scene.NWLazySource = node1.name
1116 context.scene.NWLazyTarget = node_at_pos(nodes, context, event).name
1118 if event.type == 'MOUSEMOVE':
1119 self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
1121 elif event.type == 'RIGHTMOUSE':
1122 end_pos = [event.mouse_region_x, event.mouse_region_y]
1123 bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
1125 node2 = None
1126 node2 = node_at_pos(nodes, context, event)
1127 if node2:
1128 context.scene.NWBusyDrawing = node2.name
1130 if node1 == node2:
1131 cont = False
1133 if cont:
1134 if node1 and node2:
1135 for node in nodes:
1136 node.select = False
1137 node1.select = True
1138 node2.select = True
1140 bpy.ops.node.nw_merge_nodes(mode="MIX", merge_type="AUTO")
1142 context.scene.NWBusyDrawing = ""
1143 return {'FINISHED'}
1145 elif event.type == 'ESC':
1146 print('cancelled')
1147 bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
1148 return {'CANCELLED'}
1150 return {'RUNNING_MODAL'}
1152 def invoke(self, context, event):
1153 if context.area.type == 'NODE_EDITOR':
1154 # the arguments we pass the the callback
1155 args = (self, context, 'MIX')
1156 # Add the region OpenGL drawing callback
1157 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1158 self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
1160 self.mouse_path = []
1162 context.window_manager.modal_handler_add(self)
1163 return {'RUNNING_MODAL'}
1164 else:
1165 self.report({'WARNING'}, "View3D not found, cannot run operator")
1166 return {'CANCELLED'}
1169 class NWLazyConnect(Operator, NWBase):
1170 """Connect two nodes without clicking a specific socket (automatically determined"""
1171 bl_idname = "node.nw_lazy_connect"
1172 bl_label = "Lazy Connect"
1173 bl_options = {'REGISTER', 'UNDO'}
1174 with_menu = BoolProperty()
1176 def modal(self, context, event):
1177 context.area.tag_redraw()
1178 nodes, links = get_nodes_links(context)
1179 cont = True
1181 start_pos = [event.mouse_region_x, event.mouse_region_y]
1183 node1 = None
1184 if not context.scene.NWBusyDrawing:
1185 node1 = node_at_pos(nodes, context, event)
1186 if node1:
1187 context.scene.NWBusyDrawing = node1.name
1188 else:
1189 if context.scene.NWBusyDrawing != 'STOP':
1190 node1 = nodes[context.scene.NWBusyDrawing]
1192 context.scene.NWLazySource = node1.name
1193 context.scene.NWLazyTarget = node_at_pos(nodes, context, event).name
1195 if event.type == 'MOUSEMOVE':
1196 self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
1198 elif event.type == 'RIGHTMOUSE':
1199 end_pos = [event.mouse_region_x, event.mouse_region_y]
1200 bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
1202 node2 = None
1203 node2 = node_at_pos(nodes, context, event)
1204 if node2:
1205 context.scene.NWBusyDrawing = node2.name
1207 if node1 == node2:
1208 cont = False
1210 link_success = False
1211 if cont:
1212 if node1 and node2:
1213 original_sel = []
1214 original_unsel = []
1215 for node in nodes:
1216 if node.select == True:
1217 node.select = False
1218 original_sel.append(node)
1219 else:
1220 original_unsel.append(node)
1221 node1.select = True
1222 node2.select = True
1224 #link_success = autolink(node1, node2, links)
1225 if self.with_menu:
1226 if len(node1.outputs) > 1 and node2.inputs:
1227 bpy.ops.wm.call_menu("INVOKE_DEFAULT", name=NWConnectionListOutputs.bl_idname)
1228 elif len(node1.outputs) == 1:
1229 bpy.ops.node.nw_call_inputs_menu(from_socket=0)
1230 else:
1231 link_success = autolink(node1, node2, links)
1233 for node in original_sel:
1234 node.select = True
1235 for node in original_unsel:
1236 node.select = False
1238 if link_success:
1239 hack_force_update(context, nodes)
1240 context.scene.NWBusyDrawing = ""
1241 return {'FINISHED'}
1243 elif event.type == 'ESC':
1244 bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
1245 return {'CANCELLED'}
1247 return {'RUNNING_MODAL'}
1249 def invoke(self, context, event):
1250 if context.area.type == 'NODE_EDITOR':
1251 nodes, links = get_nodes_links(context)
1252 node = node_at_pos(nodes, context, event)
1253 if node:
1254 context.scene.NWBusyDrawing = node.name
1256 # the arguments we pass the the callback
1257 mode = "LINK"
1258 if self.with_menu:
1259 mode = "LINKMENU"
1260 args = (self, context, mode)
1261 # Add the region OpenGL drawing callback
1262 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1263 self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_nodeoutline, args, 'WINDOW', 'POST_PIXEL')
1265 self.mouse_path = []
1267 context.window_manager.modal_handler_add(self)
1268 return {'RUNNING_MODAL'}
1269 else:
1270 self.report({'WARNING'}, "View3D not found, cannot run operator")
1271 return {'CANCELLED'}
1274 class NWDeleteUnused(Operator, NWBase):
1275 """Delete all nodes whose output is not used"""
1276 bl_idname = 'node.nw_del_unused'
1277 bl_label = 'Delete Unused Nodes'
1278 bl_options = {'REGISTER', 'UNDO'}
1280 @classmethod
1281 def poll(cls, context):
1282 valid = False
1283 if nw_check(context):
1284 if context.space_data.node_tree.nodes:
1285 valid = True
1286 return valid
1288 def execute(self, context):
1289 nodes, links = get_nodes_links(context)
1290 end_types = 'OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1291 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LAMP', \
1292 'OUTPUT_WORLD', 'GROUP', 'GROUP_INPUT', 'GROUP_OUTPUT'
1294 # Store selection
1295 selection = []
1296 for node in nodes:
1297 if node.select == True:
1298 selection.append(node.name)
1300 deleted_nodes = []
1301 temp_deleted_nodes = []
1302 del_unused_iterations = len(nodes)
1303 for it in range(0, del_unused_iterations):
1304 temp_deleted_nodes = list(deleted_nodes) # keep record of last iteration
1305 for node in nodes:
1306 node.select = False
1307 for node in nodes:
1308 if is_end_node(node) and not node.type in end_types and node.type != 'FRAME':
1309 node.select = True
1310 deleted_nodes.append(node.name)
1311 bpy.ops.node.delete()
1313 if temp_deleted_nodes == deleted_nodes: # stop iterations when there are no more nodes to be deleted
1314 break
1315 # get unique list of deleted nodes (iterations would count the same node more than once)
1316 deleted_nodes = list(set(deleted_nodes))
1317 for n in deleted_nodes:
1318 self.report({'INFO'}, "Node " + n + " deleted")
1319 num_deleted = len(deleted_nodes)
1320 n = ' node'
1321 if num_deleted > 1:
1322 n += 's'
1323 if num_deleted:
1324 self.report({'INFO'}, "Deleted " + str(num_deleted) + n)
1325 else:
1326 self.report({'INFO'}, "Nothing deleted")
1328 # Restore selection
1329 nodes, links = get_nodes_links(context)
1330 for node in nodes:
1331 if node.name in selection:
1332 node.select = True
1333 return {'FINISHED'}
1335 def invoke(self, context, event):
1336 return context.window_manager.invoke_confirm(self, event)
1339 class NWSwapLinks(Operator, NWBase):
1340 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1341 bl_idname = 'node.nw_swap_links'
1342 bl_label = 'Swap Links'
1343 bl_options = {'REGISTER', 'UNDO'}
1345 @classmethod
1346 def poll(cls, context):
1347 valid = False
1348 if nw_check(context):
1349 if context.selected_nodes:
1350 valid = len(context.selected_nodes) <= 2
1351 return valid
1353 def execute(self, context):
1354 nodes, links = get_nodes_links(context)
1355 selected_nodes = context.selected_nodes
1356 n1 = selected_nodes[0]
1358 # Swap outputs
1359 if len(selected_nodes) == 2:
1360 n2 = selected_nodes[1]
1361 if n1.outputs and n2.outputs:
1362 n1_outputs = []
1363 n2_outputs = []
1365 out_index = 0
1366 for output in n1.outputs:
1367 if output.links:
1368 for link in output.links:
1369 n1_outputs.append([out_index, link.to_socket])
1370 links.remove(link)
1371 out_index += 1
1373 out_index = 0
1374 for output in n2.outputs:
1375 if output.links:
1376 for link in output.links:
1377 n2_outputs.append([out_index, link.to_socket])
1378 links.remove(link)
1379 out_index += 1
1381 for connection in n1_outputs:
1382 try:
1383 links.new(n2.outputs[connection[0]], connection[1])
1384 except:
1385 self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1386 for connection in n2_outputs:
1387 try:
1388 links.new(n1.outputs[connection[0]], connection[1])
1389 except:
1390 self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1391 else:
1392 if n1.outputs or n2.outputs:
1393 self.report({'WARNING'}, "One of the nodes has no outputs!")
1394 else:
1395 self.report({'WARNING'}, "Neither of the nodes have outputs!")
1397 # Swap Inputs
1398 elif len(selected_nodes) == 1:
1399 if n1.inputs:
1400 types = []
1402 for i1 in n1.inputs:
1403 if i1.is_linked:
1404 similar_types = 0
1405 for i2 in n1.inputs:
1406 if i1.type == i2.type and i2.is_linked:
1407 similar_types += 1
1408 types.append ([i1, similar_types, i])
1409 i += 1
1410 types.sort(key=lambda k: k[1], reverse=True)
1412 if types:
1413 t = types[0]
1414 if t[1] == 2:
1415 for i2 in n1.inputs:
1416 if t[0].type == i2.type == t[0].type and t[0] != i2 and i2.is_linked:
1417 pair = [t[0], i2]
1418 i1f = pair[0].links[0].from_socket
1419 i1t = pair[0].links[0].to_socket
1420 i2f = pair[1].links[0].from_socket
1421 i2t = pair[1].links[0].to_socket
1422 links.new(i1f, i2t)
1423 links.new(i2f, i1t)
1424 if t[1] == 1:
1425 if len(types) == 1:
1426 fs = t[0].links[0].from_socket
1427 i = t[2]
1428 links.remove(t[0].links[0])
1429 if i+1 == len(n1.inputs):
1430 i = -1
1431 i += 1
1432 while n1.inputs[i].is_linked:
1433 i += 1
1434 links.new(fs, n1.inputs[i])
1435 elif len(types) == 2:
1436 i1f = types[0][0].links[0].from_socket
1437 i1t = types[0][0].links[0].to_socket
1438 i2f = types[1][0].links[0].from_socket
1439 i2t = types[1][0].links[0].to_socket
1440 links.new(i1f, i2t)
1441 links.new(i2f, i1t)
1443 else:
1444 self.report({'WARNING'}, "This node has no input connections to swap!")
1445 else:
1446 self.report({'WARNING'}, "This node has no inputs to swap!")
1448 hack_force_update(context, nodes)
1449 return {'FINISHED'}
1452 class NWResetBG(Operator, NWBase):
1453 """Reset the zoom and position of the background image"""
1454 bl_idname = 'node.nw_bg_reset'
1455 bl_label = 'Reset Backdrop'
1456 bl_options = {'REGISTER', 'UNDO'}
1458 @classmethod
1459 def poll(cls, context):
1460 valid = False
1461 if nw_check(context):
1462 snode = context.space_data
1463 valid = snode.tree_type == 'CompositorNodeTree'
1464 return valid
1466 def execute(self, context):
1467 context.space_data.backdrop_zoom = 1
1468 context.space_data.backdrop_x = 0
1469 context.space_data.backdrop_y = 0
1470 return {'FINISHED'}
1473 class NWAddAttrNode(Operator, NWBase):
1474 """Add an Attribute node with this name"""
1475 bl_idname = 'node.nw_add_attr_node'
1476 bl_label = 'Add UV map'
1477 attr_name = StringProperty()
1478 bl_options = {'REGISTER', 'UNDO'}
1480 def execute(self, context):
1481 bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type="ShaderNodeAttribute")
1482 nodes, links = get_nodes_links(context)
1483 nodes.active.attribute_name = self.attr_name
1484 return {'FINISHED'}
1487 class NWEmissionViewer(Operator, NWBase):
1488 bl_idname = "node.nw_emission_viewer"
1489 bl_label = "Emission Viewer"
1490 bl_description = "Connect active node to Emission Shader for shadeless previews"
1491 bl_options = {'REGISTER', 'UNDO'}
1493 @classmethod
1494 def poll(cls, context):
1495 is_cycles = context.scene.render.engine == 'CYCLES'
1496 if nw_check(context):
1497 space = context.space_data
1498 if space.tree_type == 'ShaderNodeTree' and is_cycles:
1499 if context.active_node:
1500 if context.active_node.type != "OUTPUT_MATERIAL" or context.active_node.type != "OUTPUT_WORLD":
1501 return True
1502 else:
1503 return True
1504 return False
1506 def invoke(self, context, event):
1507 space = context.space_data
1508 shader_type = space.shader_type
1509 if shader_type == 'OBJECT':
1510 if space.id not in [lamp for lamp in bpy.data.lamps]: # cannot use bpy.data.lamps directly as iterable
1511 shader_output_type = "OUTPUT_MATERIAL"
1512 shader_output_ident = "ShaderNodeOutputMaterial"
1513 shader_viewer_ident = "ShaderNodeEmission"
1514 else:
1515 shader_output_type = "OUTPUT_LAMP"
1516 shader_output_ident = "ShaderNodeOutputLamp"
1517 shader_viewer_ident = "ShaderNodeEmission"
1519 elif shader_type == 'WORLD':
1520 shader_output_type = "OUTPUT_WORLD"
1521 shader_output_ident = "ShaderNodeOutputWorld"
1522 shader_viewer_ident = "ShaderNodeBackground"
1523 shader_types = [x[1] for x in shaders_shader_nodes_props]
1524 mlocx = event.mouse_region_x
1525 mlocy = event.mouse_region_y
1526 select_node = bpy.ops.node.select(mouse_x=mlocx, mouse_y=mlocy, extend=False)
1527 if 'FINISHED' in select_node: # only run if mouse click is on a node
1528 nodes, links = get_nodes_links(context)
1529 in_group = context.active_node != space.node_tree.nodes.active
1530 active = nodes.active
1531 output_types = [x[1] for x in shaders_output_nodes_props]
1532 valid = False
1533 if active:
1534 if (active.name != "Emission Viewer") and (active.type not in output_types) and not in_group:
1535 for out in active.outputs:
1536 if not out.hide:
1537 valid = True
1538 break
1539 if valid:
1540 # get material_output node, store selection, deselect all
1541 materialout = None # placeholder node
1542 selection = []
1543 for node in nodes:
1544 if node.type == shader_output_type:
1545 materialout = node
1546 if node.select:
1547 selection.append(node.name)
1548 node.select = False
1549 if not materialout:
1550 # get right-most location
1551 sorted_by_xloc = (sorted(nodes, key=lambda x: x.location.x))
1552 max_xloc_node = sorted_by_xloc[-1]
1553 if max_xloc_node.name == 'Emission Viewer':
1554 max_xloc_node = sorted_by_xloc[-2]
1556 # get average y location
1557 sum_yloc = 0
1558 for node in nodes:
1559 sum_yloc += node.location.y
1561 new_locx = max_xloc_node.location.x + max_xloc_node.dimensions.x + 80
1562 new_locy = sum_yloc / len(nodes)
1564 materialout = nodes.new(shader_output_ident)
1565 materialout.location.x = new_locx
1566 materialout.location.y = new_locy
1567 materialout.select = False
1568 # Analyze outputs, add "Emission Viewer" if needed, make links
1569 out_i = None
1570 valid_outputs = []
1571 for i, out in enumerate(active.outputs):
1572 if not out.hide:
1573 valid_outputs.append(i)
1574 if valid_outputs:
1575 out_i = valid_outputs[0] # Start index of node's outputs
1576 for i, valid_i in enumerate(valid_outputs):
1577 for out_link in active.outputs[valid_i].links:
1578 linked_to_out = False
1579 if "Emission Viewer" in out_link.to_node.name or out_link.to_node == materialout:
1580 linked_to_out = True
1581 if linked_to_out:
1582 if i < len(valid_outputs) - 1:
1583 out_i = valid_outputs[i + 1]
1584 else:
1585 out_i = valid_outputs[0]
1586 make_links = [] # store sockets for new links
1587 if active.outputs:
1588 # If output type not 'SHADER' - "Emission Viewer" needed
1589 if active.outputs[out_i].type != 'SHADER':
1590 # get Emission Viewer node
1591 emission_exists = False
1592 emission_placeholder = nodes[0]
1593 for node in nodes:
1594 if "Emission Viewer" in node.name:
1595 emission_exists = True
1596 emission_placeholder = node
1597 if not emission_exists:
1598 emission = nodes.new(shader_viewer_ident)
1599 emission.hide = True
1600 emission.location = [materialout.location.x, (materialout.location.y + 40)]
1601 emission.label = "Viewer"
1602 emission.name = "Emission Viewer"
1603 emission.use_custom_color = True
1604 emission.color = (0.6, 0.5, 0.4)
1605 emission.select = False
1606 else:
1607 emission = emission_placeholder
1608 make_links.append((active.outputs[out_i], emission.inputs[0]))
1609 make_links.append((emission.outputs[0], materialout.inputs[0]))
1610 else:
1611 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1612 make_links.append((active.outputs[out_i], materialout.inputs[1 if active.outputs[out_i].name == "Volume" else 0]))
1613 for node in nodes:
1614 if node.name == 'Emission Viewer':
1615 node.select = True
1616 bpy.ops.node.delete()
1617 for li_from, li_to in make_links:
1618 links.new(li_from, li_to)
1619 # Restore selection
1620 nodes.active = active
1621 for node in nodes:
1622 if node.name in selection:
1623 node.select = True
1624 hack_force_update(context, nodes)
1625 return {'FINISHED'}
1626 else:
1627 return {'CANCELLED'}
1630 class NWFrameSelected(Operator, NWBase):
1631 bl_idname = "node.nw_frame_selected"
1632 bl_label = "Frame Selected"
1633 bl_description = "Add a frame node and parent the selected nodes to it"
1634 bl_options = {'REGISTER', 'UNDO'}
1635 label_prop = StringProperty(name='Label', default=' ', description='The visual name of the frame node')
1636 color_prop = FloatVectorProperty(name="Color", description="The color of the frame node", default=(0.6, 0.6, 0.6),
1637 min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3)
1639 def execute(self, context):
1640 nodes, links = get_nodes_links(context)
1641 selected = []
1642 for node in nodes:
1643 if node.select == True:
1644 selected.append(node)
1646 bpy.ops.node.add_node(type='NodeFrame')
1647 frm = nodes.active
1648 frm.label = self.label_prop
1649 frm.use_custom_color = True
1650 frm.color = self.color_prop
1652 for node in selected:
1653 node.parent = frm
1655 return {'FINISHED'}
1658 class NWReloadImages(Operator, NWBase):
1659 bl_idname = "node.nw_reload_images"
1660 bl_label = "Reload Images"
1661 bl_description = "Update all the image nodes to match their files on disk"
1663 def execute(self, context):
1664 nodes, links = get_nodes_links(context)
1665 image_types = ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1666 num_reloaded = 0
1667 for node in nodes:
1668 if node.type in image_types:
1669 if node.type == "TEXTURE":
1670 if node.texture: # node has texture assigned
1671 if node.texture.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1672 if node.texture.image: # texture has image assigned
1673 node.texture.image.reload()
1674 num_reloaded += 1
1675 else:
1676 if node.image:
1677 node.image.reload()
1678 num_reloaded += 1
1680 if num_reloaded:
1681 self.report({'INFO'}, "Reloaded images")
1682 print("Reloaded " + str(num_reloaded) + " images")
1683 hack_force_update(context, nodes)
1684 return {'FINISHED'}
1685 else:
1686 self.report({'WARNING'}, "No images found to reload in this node tree")
1687 return {'CANCELLED'}
1690 class NWSwitchNodeType(Operator, NWBase):
1691 """Switch type of selected nodes """
1692 bl_idname = "node.nw_swtch_node_type"
1693 bl_label = "Switch Node Type"
1694 bl_options = {'REGISTER', 'UNDO'}
1696 to_type = EnumProperty(
1697 name="Switch to type",
1698 items=list(shaders_input_nodes_props) +
1699 list(shaders_output_nodes_props) +
1700 list(shaders_shader_nodes_props) +
1701 list(shaders_texture_nodes_props) +
1702 list(shaders_color_nodes_props) +
1703 list(shaders_vector_nodes_props) +
1704 list(shaders_converter_nodes_props) +
1705 list(shaders_layout_nodes_props) +
1706 list(compo_input_nodes_props) +
1707 list(compo_output_nodes_props) +
1708 list(compo_color_nodes_props) +
1709 list(compo_converter_nodes_props) +
1710 list(compo_filter_nodes_props) +
1711 list(compo_vector_nodes_props) +
1712 list(compo_matte_nodes_props) +
1713 list(compo_distort_nodes_props) +
1714 list(compo_layout_nodes_props) +
1715 list(blender_mat_input_nodes_props) +
1716 list(blender_mat_output_nodes_props) +
1717 list(blender_mat_color_nodes_props) +
1718 list(blender_mat_vector_nodes_props) +
1719 list(blender_mat_converter_nodes_props) +
1720 list(blender_mat_layout_nodes_props) +
1721 list(texture_input_nodes_props) +
1722 list(texture_output_nodes_props) +
1723 list(texture_color_nodes_props) +
1724 list(texture_pattern_nodes_props) +
1725 list(texture_textures_nodes_props) +
1726 list(texture_converter_nodes_props) +
1727 list(texture_distort_nodes_props) +
1728 list(texture_layout_nodes_props)
1731 def execute(self, context):
1732 nodes, links = get_nodes_links(context)
1733 to_type = self.to_type
1734 # Those types of nodes will not swap.
1735 src_excludes = ('NodeFrame')
1736 # Those attributes of nodes will be copied if possible
1737 attrs_to_pass = ('color', 'hide', 'label', 'mute', 'parent',
1738 'show_options', 'show_preview', 'show_texture',
1739 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1741 selected = [n for n in nodes if n.select]
1742 reselect = []
1743 for node in [n for n in selected if
1744 n.rna_type.identifier not in src_excludes and
1745 n.rna_type.identifier != to_type]:
1746 new_node = nodes.new(to_type)
1747 for attr in attrs_to_pass:
1748 if hasattr(node, attr) and hasattr(new_node, attr):
1749 setattr(new_node, attr, getattr(node, attr))
1750 # set image datablock of dst to image of src
1751 if hasattr(node, 'image') and hasattr(new_node, 'image'):
1752 if node.image:
1753 new_node.image = node.image
1754 # Special cases
1755 if new_node.type == 'SWITCH':
1756 new_node.hide = True
1757 # Dictionaries: src_sockets and dst_sockets:
1758 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1759 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1760 # in 'INPUTS' and 'OUTPUTS':
1761 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1762 # socket entry:
1763 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1764 src_sockets = {
1765 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1766 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1768 dst_sockets = {
1769 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1770 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1772 types_order_one = 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1773 types_order_two = 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1774 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1775 for sockets, nd in ((src_sockets, node), (dst_sockets, new_node)):
1776 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1777 for in_out, in_out_name in ((nd.inputs, 'INPUTS'), (nd.outputs, 'OUTPUTS')):
1778 # enumerate in inputs, then in outputs
1779 # find name, default value and links of socket
1780 for i, socket in enumerate(in_out):
1781 the_name = socket.name
1782 dval = None
1783 # Not every socket, especially in outputs has "default_value"
1784 if hasattr(socket, 'default_value'):
1785 dval = socket.default_value
1786 socket_links = []
1787 for lnk in socket.links:
1788 socket_links.append(lnk)
1789 # check type of socket to fill proper keys.
1790 for the_type in types_order_one:
1791 if socket.type == the_type:
1792 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1793 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1794 sockets[in_out_name][the_type].append((len(sockets[in_out_name][the_type]), i, the_name, dval, socket_links))
1795 # Check which of the types in inputs/outputs is considered to be "main".
1796 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1797 for type_check in types_order_one:
1798 if sockets[in_out_name][type_check]:
1799 sockets[in_out_name]['MAIN'] = type_check
1800 break
1802 matches = {
1803 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1804 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1807 for inout, soctype in (
1808 ('INPUTS', 'MAIN',),
1809 ('INPUTS', 'SHADER',),
1810 ('INPUTS', 'RGBA',),
1811 ('INPUTS', 'VECTOR',),
1812 ('INPUTS', 'VALUE',),
1813 ('OUTPUTS', 'MAIN',),
1814 ('OUTPUTS', 'SHADER',),
1815 ('OUTPUTS', 'RGBA',),
1816 ('OUTPUTS', 'VECTOR',),
1817 ('OUTPUTS', 'VALUE',),
1819 if src_sockets[inout][soctype] and dst_sockets[inout][soctype]:
1820 if soctype == 'MAIN':
1821 sc = src_sockets[inout][src_sockets[inout]['MAIN']]
1822 dt = dst_sockets[inout][dst_sockets[inout]['MAIN']]
1823 else:
1824 sc = src_sockets[inout][soctype]
1825 dt = dst_sockets[inout][soctype]
1826 # start with 'dt' to determine number of possibilities.
1827 for i, soc in enumerate(dt):
1828 # if src main has enough entries - match them with dst main sockets by indexes.
1829 if len(sc) > i:
1830 matches[inout][soctype].append(((sc[i][1], sc[i][3]), (soc[1], soc[3])))
1831 # add 'VALUE_NAME' criterion to inputs.
1832 if inout == 'INPUTS' and soctype == 'VALUE':
1833 for s in sc:
1834 if s[2] == soc[2]: # if names match
1835 # append src (index, dval), dst (index, dval)
1836 matches['INPUTS']['VALUE_NAME'].append(((s[1], s[3]), (soc[1], soc[3])))
1838 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1839 # This creates better links when relinking textures.
1840 if src_sockets['INPUTS']['MAIN'] == 'VECTOR' and matches['INPUTS']['VECTOR']:
1841 matches['INPUTS']['MAIN'] = matches['INPUTS']['VECTOR']
1843 # Pass default values and RELINK:
1844 for tp in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1845 # INPUTS: Base on matches in proper order.
1846 for (src_i, src_dval), (dst_i, dst_dval) in matches['INPUTS'][tp]:
1847 # pass dvals
1848 if src_dval and dst_dval and tp in {'RGBA', 'VALUE_NAME'}:
1849 new_node.inputs[dst_i].default_value = src_dval
1850 # Special case: switch to math
1851 if node.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1852 new_node.type == 'MATH' and\
1853 tp == 'MAIN':
1854 new_dst_dval = max(src_dval[0], src_dval[1], src_dval[2])
1855 new_node.inputs[dst_i].default_value = new_dst_dval
1856 if node.type == 'MIX_RGB':
1857 if node.blend_type in [o[0] for o in operations]:
1858 new_node.operation = node.blend_type
1859 # Special case: switch from math to some types
1860 if node.type == 'MATH' and\
1861 new_node.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1862 tp == 'MAIN':
1863 for i in range(3):
1864 new_node.inputs[dst_i].default_value[i] = src_dval
1865 if new_node.type == 'MIX_RGB':
1866 if node.operation in [t[0] for t in blend_types]:
1867 new_node.blend_type = node.operation
1868 # Set Fac of MIX_RGB to 1.0
1869 new_node.inputs[0].default_value = 1.0
1870 # make link only when dst matching input is not linked already.
1871 if node.inputs[src_i].links and not new_node.inputs[dst_i].links:
1872 in_src_link = node.inputs[src_i].links[0]
1873 in_dst_socket = new_node.inputs[dst_i]
1874 links.new(in_src_link.from_socket, in_dst_socket)
1875 links.remove(in_src_link)
1876 # OUTPUTS: Base on matches in proper order.
1877 for (src_i, src_dval), (dst_i, dst_dval) in matches['OUTPUTS'][tp]:
1878 for out_src_link in node.outputs[src_i].links:
1879 out_dst_socket = new_node.outputs[dst_i]
1880 links.new(out_dst_socket, out_src_link.to_socket)
1881 # relink rest inputs if possible, no criteria
1882 for src_inp in node.inputs:
1883 for dst_inp in new_node.inputs:
1884 if src_inp.links and not dst_inp.links:
1885 src_link = src_inp.links[0]
1886 links.new(src_link.from_socket, dst_inp)
1887 links.remove(src_link)
1888 # relink rest outputs if possible, base on node kind if any left.
1889 for src_o in node.outputs:
1890 for out_src_link in src_o.links:
1891 for dst_o in new_node.outputs:
1892 if src_o.type == dst_o.type:
1893 links.new(dst_o, out_src_link.to_socket)
1894 # relink rest outputs no criteria if any left. Link all from first output.
1895 for src_o in node.outputs:
1896 for out_src_link in src_o.links:
1897 if new_node.outputs:
1898 links.new(new_node.outputs[0], out_src_link.to_socket)
1899 nodes.remove(node)
1900 return {'FINISHED'}
1903 class NWMergeNodes(Operator, NWBase):
1904 bl_idname = "node.nw_merge_nodes"
1905 bl_label = "Merge Nodes"
1906 bl_description = "Merge Selected Nodes"
1907 bl_options = {'REGISTER', 'UNDO'}
1909 mode = EnumProperty(
1910 name="mode",
1911 description="All possible blend types and math operations",
1912 items=blend_types + [op for op in operations if op not in blend_types],
1914 merge_type = EnumProperty(
1915 name="merge type",
1916 description="Type of Merge to be used",
1917 items=(
1918 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
1919 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
1920 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
1921 ('MATH', 'Math Node', 'Merge using Math Nodes'),
1922 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
1923 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
1927 def execute(self, context):
1928 settings = context.user_preferences.addons[__name__].preferences
1929 merge_hide = settings.merge_hide
1930 merge_position = settings.merge_position # 'center' or 'bottom'
1932 do_hide = False
1933 do_hide_shader = False
1934 if merge_hide == 'ALWAYS':
1935 do_hide = True
1936 do_hide_shader = True
1937 elif merge_hide == 'NON_SHADER':
1938 do_hide = True
1940 tree_type = context.space_data.node_tree.type
1941 if tree_type == 'COMPOSITING':
1942 node_type = 'CompositorNode'
1943 elif tree_type == 'SHADER':
1944 node_type = 'ShaderNode'
1945 elif tree_type == 'TEXTURE':
1946 node_type = 'TextureNode'
1947 nodes, links = get_nodes_links(context)
1948 mode = self.mode
1949 merge_type = self.merge_type
1950 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
1951 # 'ZCOMBINE' works only if mode == 'MIX'
1952 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
1953 if (merge_type == 'ZCOMBINE' or merge_type == 'ALPHAOVER') and tree_type != 'COMPOSITING':
1954 merge_type = 'MIX'
1955 mode = 'MIX'
1956 selected_mix = [] # entry = [index, loc]
1957 selected_shader = [] # entry = [index, loc]
1958 selected_math = [] # entry = [index, loc]
1959 selected_z = [] # entry = [index, loc]
1960 selected_alphaover = [] # entry = [index, loc]
1962 for i, node in enumerate(nodes):
1963 if node.select and node.outputs:
1964 if merge_type == 'AUTO':
1965 for (type, types_list, dst) in (
1966 ('SHADER', ('MIX', 'ADD'), selected_shader),
1967 ('RGBA', [t[0] for t in blend_types], selected_mix),
1968 ('VALUE', [t[0] for t in operations], selected_math),
1970 output_type = node.outputs[0].type
1971 valid_mode = mode in types_list
1972 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
1973 # Cheat that output type is 'RGBA',
1974 # and that 'MIX' exists in math operations list.
1975 # This way when selected_mix list is analyzed:
1976 # Node data will be appended even though it doesn't meet requirements.
1977 if output_type != 'SHADER' and mode == 'MIX':
1978 output_type = 'RGBA'
1979 valid_mode = True
1980 if output_type == type and valid_mode:
1981 dst.append([i, node.location.x, node.location.y, node.dimensions.x, node.hide])
1982 else:
1983 for (type, types_list, dst) in (
1984 ('SHADER', ('MIX', 'ADD'), selected_shader),
1985 ('MIX', [t[0] for t in blend_types], selected_mix),
1986 ('MATH', [t[0] for t in operations], selected_math),
1987 ('ZCOMBINE', ('MIX', ), selected_z),
1988 ('ALPHAOVER', ('MIX', ), selected_alphaover),
1990 if merge_type == type and mode in types_list:
1991 dst.append([i, node.location.x, node.location.y, node.dimensions.x, node.hide])
1992 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
1993 # use only 'Mix' nodes for merging.
1994 # For that we add selected_math list to selected_mix list and clear selected_math.
1995 if selected_mix and selected_math and merge_type == 'AUTO':
1996 selected_mix += selected_math
1997 selected_math = []
1999 for nodes_list in [selected_mix, selected_shader, selected_math, selected_z, selected_alphaover]:
2000 if nodes_list:
2001 count_before = len(nodes)
2002 # sort list by loc_x - reversed
2003 nodes_list.sort(key=lambda k: k[1], reverse=True)
2004 # get maximum loc_x
2005 loc_x = nodes_list[0][1] + nodes_list[0][3] + 70
2006 nodes_list.sort(key=lambda k: k[2], reverse=True)
2007 if merge_position == 'CENTER':
2008 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)
2009 if nodes_list[len(nodes_list) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2010 if do_hide:
2011 loc_y += 40
2012 else:
2013 loc_y += 80
2014 else:
2015 loc_y = nodes_list[len(nodes_list) - 1][2]
2016 offset_y = 100
2017 if not do_hide:
2018 offset_y = 200
2019 if nodes_list == selected_shader and not do_hide_shader:
2020 offset_y = 150.0
2021 the_range = len(nodes_list) - 1
2022 if len(nodes_list) == 1:
2023 the_range = 1
2024 for i in range(the_range):
2025 if nodes_list == selected_mix:
2026 add_type = node_type + 'MixRGB'
2027 add = nodes.new(add_type)
2028 add.blend_type = mode
2029 if mode != 'MIX':
2030 add.inputs[0].default_value = 1.0
2031 add.show_preview = False
2032 add.hide = do_hide
2033 if do_hide:
2034 loc_y = loc_y - 50
2035 first = 1
2036 second = 2
2037 add.width_hidden = 100.0
2038 elif nodes_list == selected_math:
2039 add_type = node_type + 'Math'
2040 add = nodes.new(add_type)
2041 add.operation = mode
2042 add.hide = do_hide
2043 if do_hide:
2044 loc_y = loc_y - 50
2045 first = 0
2046 second = 1
2047 add.width_hidden = 100.0
2048 elif nodes_list == selected_shader:
2049 if mode == 'MIX':
2050 add_type = node_type + 'MixShader'
2051 add = nodes.new(add_type)
2052 add.hide = do_hide_shader
2053 if do_hide_shader:
2054 loc_y = loc_y - 50
2055 first = 1
2056 second = 2
2057 add.width_hidden = 100.0
2058 elif mode == 'ADD':
2059 add_type = node_type + 'AddShader'
2060 add = nodes.new(add_type)
2061 add.hide = do_hide_shader
2062 if do_hide_shader:
2063 loc_y = loc_y - 50
2064 first = 0
2065 second = 1
2066 add.width_hidden = 100.0
2067 elif nodes_list == selected_z:
2068 add = nodes.new('CompositorNodeZcombine')
2069 add.show_preview = False
2070 add.hide = do_hide
2071 if do_hide:
2072 loc_y = loc_y - 50
2073 first = 0
2074 second = 2
2075 add.width_hidden = 100.0
2076 elif nodes_list == selected_alphaover:
2077 add = nodes.new('CompositorNodeAlphaOver')
2078 add.show_preview = False
2079 add.hide = do_hide
2080 if do_hide:
2081 loc_y = loc_y - 50
2082 first = 1
2083 second = 2
2084 add.width_hidden = 100.0
2085 add.location = loc_x, loc_y
2086 loc_y += offset_y
2087 add.select = True
2088 count_adds = i + 1
2089 count_after = len(nodes)
2090 index = count_after - 1
2091 first_selected = nodes[nodes_list[0][0]]
2092 # "last" node has been added as first, so its index is count_before.
2093 last_add = nodes[count_before]
2094 # Special case:
2095 # Two nodes were selected and first selected has no output links, second selected has output links.
2096 # Then add links from last add to all links 'to_socket' of out links of second selected.
2097 if len(nodes_list) == 2:
2098 if not first_selected.outputs[0].links:
2099 second_selected = nodes[nodes_list[1][0]]
2100 for ss_link in second_selected.outputs[0].links:
2101 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2102 # Create list of invalid indexes.
2103 invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader + selected_z)]
2104 # Link only if "to_node" index not in invalid indexes list.
2105 if ss_link.to_node not in [nodes[i] for i in invalid_i]:
2106 links.new(last_add.outputs[0], ss_link.to_socket)
2107 # add links from last_add to all links 'to_socket' of out links of first selected.
2108 for fs_link in first_selected.outputs[0].links:
2109 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2110 # Create list of invalid indexes.
2111 invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader + selected_z)]
2112 # Link only if "to_node" index not in invalid indexes list.
2113 if fs_link.to_node not in [nodes[i] for i in invalid_i]:
2114 links.new(last_add.outputs[0], fs_link.to_socket)
2115 # add link from "first" selected and "first" add node
2116 node_to = nodes[count_after - 1]
2117 links.new(first_selected.outputs[0], node_to.inputs[first])
2118 if node_to.type == 'ZCOMBINE':
2119 for fs_out in first_selected.outputs:
2120 if fs_out != first_selected.outputs[0] and fs_out.name in ('Z', 'Depth'):
2121 links.new(fs_out, node_to.inputs[1])
2122 break
2123 # add links between added ADD nodes and between selected and ADD nodes
2124 for i in range(count_adds):
2125 if i < count_adds - 1:
2126 node_from = nodes[index]
2127 node_to = nodes[index - 1]
2128 node_to_input_i = first
2129 node_to_z_i = 1 # if z combine - link z to first z input
2130 links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
2131 if node_to.type == 'ZCOMBINE':
2132 for from_out in node_from.outputs:
2133 if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
2134 links.new(from_out, node_to.inputs[node_to_z_i])
2135 if len(nodes_list) > 1:
2136 node_from = nodes[nodes_list[i + 1][0]]
2137 node_to = nodes[index]
2138 node_to_input_i = second
2139 node_to_z_i = 3 # if z combine - link z to second z input
2140 links.new(node_from.outputs[0], node_to.inputs[node_to_input_i])
2141 if node_to.type == 'ZCOMBINE':
2142 for from_out in node_from.outputs:
2143 if from_out != node_from.outputs[0] and from_out.name in ('Z', 'Depth'):
2144 links.new(from_out, node_to.inputs[node_to_z_i])
2145 index -= 1
2146 # set "last" of added nodes as active
2147 nodes.active = last_add
2148 for i, x, y, dx, h in nodes_list:
2149 nodes[i].select = False
2151 return {'FINISHED'}
2154 class NWBatchChangeNodes(Operator, NWBase):
2155 bl_idname = "node.nw_batch_change"
2156 bl_label = "Batch Change"
2157 bl_description = "Batch Change Blend Type and Math Operation"
2158 bl_options = {'REGISTER', 'UNDO'}
2160 blend_type = EnumProperty(
2161 name="Blend Type",
2162 items=blend_types + navs,
2164 operation = EnumProperty(
2165 name="Operation",
2166 items=operations + navs,
2169 def execute(self, context):
2171 nodes, links = get_nodes_links(context)
2172 blend_type = self.blend_type
2173 operation = self.operation
2174 for node in context.selected_nodes:
2175 if node.type == 'MIX_RGB':
2176 if not blend_type in [nav[0] for nav in navs]:
2177 node.blend_type = blend_type
2178 else:
2179 if blend_type == 'NEXT':
2180 index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
2181 #index = blend_types.index(node.blend_type)
2182 if index == len(blend_types) - 1:
2183 node.blend_type = blend_types[0][0]
2184 else:
2185 node.blend_type = blend_types[index + 1][0]
2187 if blend_type == 'PREV':
2188 index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
2189 if index == 0:
2190 node.blend_type = blend_types[len(blend_types) - 1][0]
2191 else:
2192 node.blend_type = blend_types[index - 1][0]
2194 if node.type == 'MATH':
2195 if not operation in [nav[0] for nav in navs]:
2196 node.operation = operation
2197 else:
2198 if operation == 'NEXT':
2199 index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
2200 #index = operations.index(node.operation)
2201 if index == len(operations) - 1:
2202 node.operation = operations[0][0]
2203 else:
2204 node.operation = operations[index + 1][0]
2206 if operation == 'PREV':
2207 index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
2208 #index = operations.index(node.operation)
2209 if index == 0:
2210 node.operation = operations[len(operations) - 1][0]
2211 else:
2212 node.operation = operations[index - 1][0]
2214 return {'FINISHED'}
2217 class NWChangeMixFactor(Operator, NWBase):
2218 bl_idname = "node.nw_factor"
2219 bl_label = "Change Factor"
2220 bl_description = "Change Factors of Mix Nodes and Mix Shader Nodes"
2221 bl_options = {'REGISTER', 'UNDO'}
2223 # option: Change factor.
2224 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2225 # Else - change factor by option value.
2226 option = FloatProperty()
2228 def execute(self, context):
2229 nodes, links = get_nodes_links(context)
2230 option = self.option
2231 selected = [] # entry = index
2232 for si, node in enumerate(nodes):
2233 if node.select:
2234 if node.type in {'MIX_RGB', 'MIX_SHADER'}:
2235 selected.append(si)
2237 for si in selected:
2238 fac = nodes[si].inputs[0]
2239 nodes[si].hide = False
2240 if option in {0.0, 1.0}:
2241 fac.default_value = option
2242 else:
2243 fac.default_value += option
2245 return {'FINISHED'}
2248 class NWCopySettings(Operator, NWBase):
2249 bl_idname = "node.nw_copy_settings"
2250 bl_label = "Copy Settings"
2251 bl_description = "Copy Settings of Active Node to Selected Nodes"
2252 bl_options = {'REGISTER', 'UNDO'}
2254 @classmethod
2255 def poll(cls, context):
2256 valid = False
2257 if nw_check(context):
2258 if context.active_node is not None and context.active_node.type is not 'FRAME':
2259 valid = True
2260 return valid
2262 def execute(self, context):
2263 nodes, links = get_nodes_links(context)
2264 selected = [n for n in nodes if n.select]
2265 reselect = [] # duplicated nodes will be selected after execution
2266 active = nodes.active
2267 if active.select:
2268 reselect.append(active)
2270 for node in selected:
2271 if node.type == active.type and node != active:
2272 # duplicate active, relink links as in 'node', append copy to 'reselect', delete node
2273 bpy.ops.node.select_all(action='DESELECT')
2274 nodes.active = active
2275 active.select = True
2276 bpy.ops.node.duplicate()
2277 copied = nodes.active
2278 # Copied active should however inherit some properties from 'node'
2279 attributes = (
2280 'hide', 'show_preview', 'mute', 'label',
2281 'use_custom_color', 'color', 'width', 'width_hidden',
2283 for attr in attributes:
2284 setattr(copied, attr, getattr(node, attr))
2285 # Handle scenario when 'node' is in frame. 'copied' is in same frame then.
2286 if copied.parent:
2287 bpy.ops.node.parent_clear()
2288 locx = node.location.x
2289 locy = node.location.y
2290 # get absolute node location
2291 parent = node.parent
2292 while parent:
2293 locx += parent.location.x
2294 locy += parent.location.y
2295 parent = parent.parent
2296 copied.location = [locx, locy]
2297 # reconnect links from node to copied
2298 for i, input in enumerate(node.inputs):
2299 if input.links:
2300 link = input.links[0]
2301 links.new(link.from_socket, copied.inputs[i])
2302 for out, output in enumerate(node.outputs):
2303 if output.links:
2304 out_links = output.links
2305 for link in out_links:
2306 links.new(copied.outputs[out], link.to_socket)
2307 bpy.ops.node.select_all(action='DESELECT')
2308 node.select = True
2309 bpy.ops.node.delete()
2310 reselect.append(copied)
2311 else: # If selected wasn't copied, need to reselect it afterwards.
2312 reselect.append(node)
2313 # clean up
2314 bpy.ops.node.select_all(action='DESELECT')
2315 for node in reselect:
2316 node.select = True
2317 nodes.active = active
2319 return {'FINISHED'}
2322 class NWCopyLabel(Operator, NWBase):
2323 bl_idname = "node.nw_copy_label"
2324 bl_label = "Copy Label"
2325 bl_options = {'REGISTER', 'UNDO'}
2327 option = EnumProperty(
2328 name="option",
2329 description="Source of name of label",
2330 items=(
2331 ('FROM_ACTIVE', 'from active', 'from active node',),
2332 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2333 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2337 def execute(self, context):
2338 nodes, links = get_nodes_links(context)
2339 option = self.option
2340 active = nodes.active
2341 if option == 'FROM_ACTIVE':
2342 if active:
2343 src_label = active.label
2344 for node in [n for n in nodes if n.select and nodes.active != n]:
2345 node.label = src_label
2346 elif option == 'FROM_NODE':
2347 selected = [n for n in nodes if n.select]
2348 for node in selected:
2349 for input in node.inputs:
2350 if input.links:
2351 src = input.links[0].from_node
2352 node.label = src.label
2353 break
2354 elif option == 'FROM_SOCKET':
2355 selected = [n for n in nodes if n.select]
2356 for node in selected:
2357 for input in node.inputs:
2358 if input.links:
2359 src = input.links[0].from_socket
2360 node.label = src.name
2361 break
2363 return {'FINISHED'}
2366 class NWClearLabel(Operator, NWBase):
2367 bl_idname = "node.nw_clear_label"
2368 bl_label = "Clear Label"
2369 bl_options = {'REGISTER', 'UNDO'}
2371 option = BoolProperty()
2373 def execute(self, context):
2374 nodes, links = get_nodes_links(context)
2375 for node in [n for n in nodes if n.select]:
2376 node.label = ''
2378 return {'FINISHED'}
2380 def invoke(self, context, event):
2381 if self.option:
2382 return self.execute(context)
2383 else:
2384 return context.window_manager.invoke_confirm(self, event)
2387 class NWModifyLabels(Operator, NWBase):
2388 """Modify Labels of all selected nodes"""
2389 bl_idname = "node.nw_modify_labels"
2390 bl_label = "Modify Labels"
2391 bl_options = {'REGISTER', 'UNDO'}
2393 prepend = StringProperty(
2394 name="Add to Beginning"
2396 append = StringProperty(
2397 name="Add to End"
2399 replace_from = StringProperty(
2400 name="Text to Replace"
2402 replace_to = StringProperty(
2403 name="Replace with"
2406 def execute(self, context):
2407 nodes, links = get_nodes_links(context)
2408 for node in [n for n in nodes if n.select]:
2409 node.label = self.prepend + node.label.replace(self.replace_from, self.replace_to) + self.append
2411 return {'FINISHED'}
2413 def invoke(self, context, event):
2414 self.prepend = ""
2415 self.append = ""
2416 self.remove = ""
2417 return context.window_manager.invoke_props_dialog(self)
2420 class NWAddTextureSetup(Operator, NWBase):
2421 bl_idname = "node.nw_add_texture"
2422 bl_label = "Texture Setup"
2423 bl_description = "Add Texture Node Setup to Selected Shaders"
2424 bl_options = {'REGISTER', 'UNDO'}
2426 @classmethod
2427 def poll(cls, context):
2428 valid = False
2429 if nw_check(context):
2430 space = context.space_data
2431 if space.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
2432 valid = True
2433 return valid
2435 def execute(self, context):
2436 nodes, links = get_nodes_links(context)
2437 active = nodes.active
2438 shader_types = [x[1] for x in shaders_shader_nodes_props if x[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2439 texture_types = [x[1] for x in shaders_texture_nodes_props]
2440 valid = False
2441 if active:
2442 if active.select:
2443 if active.type in shader_types or active.type in texture_types:
2444 if not active.inputs[0].is_linked:
2445 valid = True
2446 if valid:
2447 locx = active.location.x
2448 locy = active.location.y
2450 xoffset = [500.0, 700.0]
2451 isshader = True
2452 if active.type not in shader_types:
2453 xoffset = [290.0, 500.0]
2454 isshader = False
2456 coordout = 2
2457 image_type = 'ShaderNodeTexImage'
2459 if (active.type in texture_types and active.type != 'TEX_IMAGE') or (active.type == 'BACKGROUND'):
2460 coordout = 0 # image texture uses UVs, procedural textures and Background shader use Generated
2461 if active.type == 'BACKGROUND':
2462 image_type = 'ShaderNodeTexEnvironment'
2464 if isshader:
2465 tex = nodes.new(image_type)
2466 tex.location = [locx - 200.0, locy + 28.0]
2468 map = nodes.new('ShaderNodeMapping')
2469 map.location = [locx - xoffset[0], locy + 80.0]
2470 map.width = 240
2471 coord = nodes.new('ShaderNodeTexCoord')
2472 coord.location = [locx - xoffset[1], locy + 40.0]
2473 active.select = False
2475 if isshader:
2476 nodes.active = tex
2477 links.new(tex.outputs[0], active.inputs[0])
2478 links.new(map.outputs[0], tex.inputs[0])
2479 links.new(coord.outputs[coordout], map.inputs[0])
2481 else:
2482 nodes.active = map
2483 links.new(map.outputs[0], active.inputs[0])
2484 links.new(coord.outputs[coordout], map.inputs[0])
2486 return {'FINISHED'}
2489 class NWAddReroutes(Operator, NWBase):
2490 """Add Reroute Nodes and link them to outputs of selected nodes"""
2491 bl_idname = "node.nw_add_reroutes"
2492 bl_label = "Add Reroutes"
2493 bl_description = "Add Reroutes to Outputs"
2494 bl_options = {'REGISTER', 'UNDO'}
2496 option = EnumProperty(
2497 name="option",
2498 items=[
2499 ('ALL', 'to all', 'Add to all outputs'),
2500 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2501 ('LINKED', 'to linked', 'Add only to linked outputs'),
2505 def execute(self, context):
2506 tree_type = context.space_data.node_tree.type
2507 option = self.option
2508 nodes, links = get_nodes_links(context)
2509 # output valid when option is 'all' or when 'loose' output has no links
2510 valid = False
2511 post_select = [] # nodes to be selected after execution
2512 # create reroutes and recreate links
2513 for node in [n for n in nodes if n.select]:
2514 if node.outputs:
2515 x = node.location.x
2516 y = node.location.y
2517 width = node.width
2518 # unhide 'REROUTE' nodes to avoid issues with location.y
2519 if node.type == 'REROUTE':
2520 node.hide = False
2521 # When node is hidden - width_hidden not usable.
2522 # Hack needed to calculate real width
2523 if node.hide:
2524 bpy.ops.node.select_all(action='DESELECT')
2525 helper = nodes.new('NodeReroute')
2526 helper.select = True
2527 node.select = True
2528 # resize node and helper to zero. Then check locations to calculate width
2529 bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
2530 width = 2.0 * (helper.location.x - node.location.x)
2531 # restore node location
2532 node.location = x, y
2533 # delete helper
2534 node.select = False
2535 # only helper is selected now
2536 bpy.ops.node.delete()
2537 x = node.location.x + width + 20.0
2538 if node.type != 'REROUTE':
2539 y -= 35.0
2540 y_offset = -22.0
2541 loc = x, y
2542 reroutes_count = 0 # will be used when aligning reroutes added to hidden nodes
2543 for out_i, output in enumerate(node.outputs):
2544 pass_used = False # initial value to be analyzed if 'R_LAYERS'
2545 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2546 if node.type != 'R_LAYERS':
2547 pass_used = True
2548 else: # if 'R_LAYERS' check if output represent used render pass
2549 node_scene = node.scene
2550 node_layer = node.layer
2551 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2552 if output.name == 'Alpha':
2553 pass_used = True
2554 else:
2555 # check entries in global 'rl_outputs' variable
2556 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
2557 if output.name == out_name:
2558 pass_used = getattr(node_scene.render.layers[node_layer], render_pass)
2559 break
2560 if pass_used:
2561 valid = ((option == 'ALL') or
2562 (option == 'LOOSE' and not output.links) or
2563 (option == 'LINKED' and output.links))
2564 # Add reroutes only if valid, but offset location in all cases.
2565 if valid:
2566 n = nodes.new('NodeReroute')
2567 nodes.active = n
2568 for link in output.links:
2569 links.new(n.outputs[0], link.to_socket)
2570 links.new(output, n.inputs[0])
2571 n.location = loc
2572 post_select.append(n)
2573 reroutes_count += 1
2574 y += y_offset
2575 loc = x, y
2576 # disselect the node so that after execution of script only newly created nodes are selected
2577 node.select = False
2578 # nicer reroutes distribution along y when node.hide
2579 if node.hide:
2580 y_translate = reroutes_count * y_offset / 2.0 - y_offset - 35.0
2581 for reroute in [r for r in nodes if r.select]:
2582 reroute.location.y -= y_translate
2583 for node in post_select:
2584 node.select = True
2586 return {'FINISHED'}
2589 class NWLinkActiveToSelected(Operator, NWBase):
2590 """Link active node to selected nodes basing on various criteria"""
2591 bl_idname = "node.nw_link_active_to_selected"
2592 bl_label = "Link Active Node to Selected"
2593 bl_options = {'REGISTER', 'UNDO'}
2595 replace = BoolProperty()
2596 use_node_name = BoolProperty()
2597 use_outputs_names = BoolProperty()
2599 @classmethod
2600 def poll(cls, context):
2601 valid = False
2602 if nw_check(context):
2603 if context.active_node is not None:
2604 if context.active_node.select:
2605 valid = True
2606 return valid
2608 def execute(self, context):
2609 nodes, links = get_nodes_links(context)
2610 replace = self.replace
2611 use_node_name = self.use_node_name
2612 use_outputs_names = self.use_outputs_names
2613 active = nodes.active
2614 selected = [node for node in nodes if node.select and node != active]
2615 outputs = [] # Only usable outputs of active nodes will be stored here.
2616 for out in active.outputs:
2617 if active.type != 'R_LAYERS':
2618 outputs.append(out)
2619 else:
2620 # 'R_LAYERS' node type needs special handling.
2621 # outputs of 'R_LAYERS' are callable even if not seen in UI.
2622 # Only outputs that represent used passes should be taken into account
2623 # Check if pass represented by output is used.
2624 # global 'rl_outputs' list will be used for that
2625 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
2626 pass_used = False # initial value. Will be set to True if pass is used
2627 if out.name == 'Alpha':
2628 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
2629 pass_used = True
2630 elif out.name == out_name:
2631 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
2632 pass_used = getattr(active.scene.render.layers[active.layer], render_pass)
2633 break
2634 if pass_used:
2635 outputs.append(out)
2636 doit = True # Will be changed to False when links successfully added to previous output.
2637 for out in outputs:
2638 if doit:
2639 for node in selected:
2640 dst_name = node.name # Will be compared with src_name if needed.
2641 # When node has label - use it as dst_name
2642 if node.label:
2643 dst_name = node.label
2644 valid = True # Initial value. Will be changed to False if names don't match.
2645 src_name = dst_name # If names not used - this asignment will keep valid = True.
2646 if use_node_name:
2647 # Set src_name to source node name or label
2648 src_name = active.name
2649 if active.label:
2650 src_name = active.label
2651 elif use_outputs_names:
2652 src_name = (out.name, )
2653 for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
2654 if out.name in {out_name, exr_name}:
2655 src_name = (out_name, exr_name)
2656 if dst_name not in src_name:
2657 valid = False
2658 if valid:
2659 for input in node.inputs:
2660 if input.type == out.type or node.type == 'REROUTE':
2661 if replace or not input.is_linked:
2662 links.new(out, input)
2663 if not use_node_name and not use_outputs_names:
2664 doit = False
2665 break
2667 return {'FINISHED'}
2670 class NWAlignNodes(Operator, NWBase):
2671 '''Align the selected nodes neatly in a row/column'''
2672 bl_idname = "node.nw_align_nodes"
2673 bl_label = "Align Nodes"
2674 bl_options = {'REGISTER', 'UNDO'}
2675 margin = IntProperty(name='Margin', default=50, description='The amount of space between nodes')
2677 def execute(self, context):
2678 nodes, links = get_nodes_links(context)
2679 margin = self.margin
2681 selection = []
2682 for node in nodes:
2683 if node.select and node.type != 'FRAME':
2684 selection.append(node)
2686 # If no nodes are selected, align all nodes
2687 active_loc = None
2688 if not selection:
2689 selection = nodes
2690 elif nodes.active in selection:
2691 active_loc = copy(nodes.active.location) # make a copy, not a reference
2693 # Check if nodes should be layed out horizontally or vertically
2694 x_locs = [n.location.x + (n.dimensions.x / 2) for n in selection] # use dimension to get center of node, not corner
2695 y_locs = [n.location.y - (n.dimensions.y / 2) for n in selection]
2696 x_range = max(x_locs) - min(x_locs)
2697 y_range = max(y_locs) - min(y_locs)
2698 mid_x = (max(x_locs) + min(x_locs)) / 2
2699 mid_y = (max(y_locs) + min(y_locs)) / 2
2700 horizontal = x_range > y_range
2702 # Sort selection by location of node mid-point
2703 if horizontal:
2704 selection = sorted(selection, key=lambda n: n.location.x + (n.dimensions.x / 2))
2705 else:
2706 selection = sorted(selection, key=lambda n: n.location.y - (n.dimensions.y / 2), reverse=True)
2708 # Alignment
2709 current_pos = 0
2710 for node in selection:
2711 current_margin = margin
2712 current_margin = current_margin * 0.5 if node.hide else current_margin # use a smaller margin for hidden nodes
2714 if horizontal:
2715 node.location.x = current_pos
2716 current_pos += current_margin + node.dimensions.x
2717 node.location.y = mid_y + (node.dimensions.y / 2)
2718 else:
2719 node.location.y = current_pos
2720 current_pos -= (current_margin * 0.3) + node.dimensions.y # use half-margin for vertical alignment
2721 node.location.x = mid_x - (node.dimensions.x / 2)
2723 # If active node is selected, center nodes around it
2724 if active_loc is not None:
2725 active_loc_diff = active_loc - nodes.active.location
2726 for node in selection:
2727 node.location += active_loc_diff
2728 else: # Position nodes centered around where they used to be
2729 locs = ([n.location.x + (n.dimensions.x / 2) for n in selection]) if horizontal else ([n.location.y - (n.dimensions.y / 2) for n in selection])
2730 new_mid = (max(locs) + min(locs)) / 2
2731 for node in selection:
2732 if horizontal:
2733 node.location.x += (mid_x - new_mid)
2734 else:
2735 node.location.y += (mid_y - new_mid)
2737 return {'FINISHED'}
2740 class NWSelectParentChildren(Operator, NWBase):
2741 bl_idname = "node.nw_select_parent_child"
2742 bl_label = "Select Parent or Children"
2743 bl_options = {'REGISTER', 'UNDO'}
2745 option = EnumProperty(
2746 name="option",
2747 items=(
2748 ('PARENT', 'Select Parent', 'Select Parent Frame'),
2749 ('CHILD', 'Select Children', 'Select members of selected frame'),
2753 def execute(self, context):
2754 nodes, links = get_nodes_links(context)
2755 option = self.option
2756 selected = [node for node in nodes if node.select]
2757 if option == 'PARENT':
2758 for sel in selected:
2759 parent = sel.parent
2760 if parent:
2761 parent.select = True
2762 else: # option == 'CHILD'
2763 for sel in selected:
2764 children = [node for node in nodes if node.parent == sel]
2765 for kid in children:
2766 kid.select = True
2768 return {'FINISHED'}
2771 class NWDetachOutputs(Operator, NWBase):
2772 """Detach outputs of selected node leaving inluts liked"""
2773 bl_idname = "node.nw_detach_outputs"
2774 bl_label = "Detach Outputs"
2775 bl_options = {'REGISTER', 'UNDO'}
2777 def execute(self, context):
2778 nodes, links = get_nodes_links(context)
2779 selected = context.selected_nodes
2780 bpy.ops.node.duplicate_move_keep_inputs()
2781 new_nodes = context.selected_nodes
2782 bpy.ops.node.select_all(action="DESELECT")
2783 for node in selected:
2784 node.select = True
2785 bpy.ops.node.delete_reconnect()
2786 for new_node in new_nodes:
2787 new_node.select = True
2788 bpy.ops.transform.translate('INVOKE_DEFAULT')
2790 return {'FINISHED'}
2793 class NWLinkToOutputNode(Operator, NWBase):
2794 """Link to Composite node or Material Output node"""
2795 bl_idname = "node.nw_link_out"
2796 bl_label = "Connect to Output"
2797 bl_options = {'REGISTER', 'UNDO'}
2799 @classmethod
2800 def poll(cls, context):
2801 valid = False
2802 if nw_check(context):
2803 if context.active_node is not None:
2804 for out in context.active_node.outputs:
2805 if not out.hide:
2806 valid = True
2807 break
2808 return valid
2810 def execute(self, context):
2811 nodes, links = get_nodes_links(context)
2812 active = nodes.active
2813 output_node = None
2814 output_index = None
2815 tree_type = context.space_data.tree_type
2816 output_types_shaders = [x[1] for x in shaders_output_nodes_props]
2817 output_types_compo = ['COMPOSITE']
2818 output_types_blender_mat = ['OUTPUT']
2819 output_types_textures = ['OUTPUT']
2820 output_types = output_types_shaders + output_types_compo + output_types_blender_mat
2821 for node in nodes:
2822 if node.type in output_types:
2823 output_node = node
2824 break
2825 if not output_node:
2826 bpy.ops.node.select_all(action="DESELECT")
2827 if tree_type == 'ShaderNodeTree':
2828 if context.scene.render.engine == 'CYCLES':
2829 output_node = nodes.new('ShaderNodeOutputMaterial')
2830 else:
2831 output_node = nodes.new('ShaderNodeOutput')
2832 elif tree_type == 'CompositorNodeTree':
2833 output_node = nodes.new('CompositorNodeComposite')
2834 elif tree_type == 'TextureNodeTree':
2835 output_node = nodes.new('TextureNodeOutput')
2836 output_node.location.x = active.location.x + active.dimensions.x + 80
2837 output_node.location.y = active.location.y
2838 if (output_node and active.outputs):
2839 for i, output in enumerate(active.outputs):
2840 if not output.hide:
2841 output_index = i
2842 break
2843 for i, output in enumerate(active.outputs):
2844 if output.type == output_node.inputs[0].type and not output.hide:
2845 output_index = i
2846 break
2848 out_input_index = 0
2849 if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
2850 if active.outputs[output_index].name == 'Volume':
2851 out_input_index = 1
2852 elif active.outputs[output_index].type != 'SHADER': # connect to displacement if not a shader
2853 out_input_index = 2
2854 links.new(active.outputs[output_index], output_node.inputs[out_input_index])
2856 hack_force_update(context, nodes) # viewport render does not update
2858 return {'FINISHED'}
2861 class NWMakeLink(Operator, NWBase):
2862 """Make a link from one socket to another"""
2863 bl_idname = 'node.nw_make_link'
2864 bl_label = 'Make Link'
2865 bl_options = {'REGISTER', 'UNDO'}
2866 from_socket = IntProperty()
2867 to_socket = IntProperty()
2869 def execute(self, context):
2870 nodes, links = get_nodes_links(context)
2872 n1 = nodes[context.scene.NWLazySource]
2873 n2 = nodes[context.scene.NWLazyTarget]
2875 links.new(n1.outputs[self.from_socket], n2.inputs[self.to_socket])
2877 hack_force_update(context, nodes)
2879 return {'FINISHED'}
2882 class NWCallInputsMenu(Operator, NWBase):
2883 """Link from this output"""
2884 bl_idname = 'node.nw_call_inputs_menu'
2885 bl_label = 'Make Link'
2886 bl_options = {'REGISTER', 'UNDO'}
2887 from_socket = IntProperty()
2889 def execute(self, context):
2890 nodes, links = get_nodes_links(context)
2892 context.scene.NWSourceSocket = self.from_socket
2894 n1 = nodes[context.scene.NWLazySource]
2895 n2 = nodes[context.scene.NWLazyTarget]
2896 if len(n2.inputs) > 1:
2897 bpy.ops.wm.call_menu("INVOKE_DEFAULT", name=NWConnectionListInputs.bl_idname)
2898 elif len(n2.inputs) == 1:
2899 links.new(n1.outputs[self.from_socket], n2.inputs[0])
2900 return {'FINISHED'}
2903 class NWAddSequence(Operator, ImportHelper):
2904 """Add an Image Sequence"""
2905 bl_idname = 'node.nw_add_sequence'
2906 bl_label = 'Import Image Sequence'
2907 bl_options = {'REGISTER', 'UNDO'}
2908 directory = StringProperty(subtype="DIR_PATH")
2909 filename = StringProperty(subtype="FILE_NAME")
2910 files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
2912 def execute(self, context):
2913 nodes, links = get_nodes_links(context)
2914 directory = self.directory
2915 filename = self.filename
2916 files = self.files
2917 tree = context.space_data.node_tree
2919 # DEBUG
2920 # print ("\nDIR:", directory)
2921 # print ("FN:", filename)
2922 # print ("Fs:", list(f.name for f in files), '\n')
2924 if tree.type == 'SHADER':
2925 node_type = "ShaderNodeTexImage"
2926 elif tree.type == 'COMPOSITING':
2927 node_type = "CompositorNodeImage"
2928 else:
2929 self.report({'ERROR'}, "Unsupported Node Tree type!")
2930 return {'CANCELLED'}
2932 if not files[0].name and not filename:
2933 self.report({'ERROR'}, "No file chosen")
2934 return {'CANCELLED'}
2935 elif files[0].name and (not filename or not path.exists(directory+filename)):
2936 # User has selected multiple files without an active one, or the active one is non-existant
2937 filename = files[0].name
2939 if not path.exists(directory+filename):
2940 self.report({'ERROR'}, filename+" does not exist!")
2941 return {'CANCELLED'}
2943 without_ext = '.'.join(filename.split('.')[:-1])
2945 # if last digit isn't a number, it's not a sequence
2946 if not without_ext[-1].isdigit():
2947 self.report({'ERROR'}, filename+" does not seem to be part of a sequence")
2948 return {'CANCELLED'}
2951 extension = filename.split('.')[-1]
2952 reverse = without_ext[::-1] # reverse string
2954 count_numbers = 0
2955 for char in reverse:
2956 if char.isdigit():
2957 count_numbers += 1
2958 else:
2959 break
2961 without_num = without_ext[:count_numbers*-1]
2963 files = sorted(glob(directory + without_num + "[0-9]"*count_numbers + "." + extension))
2965 num_frames = len(files)
2967 nodes_list = [node for node in nodes]
2968 if nodes_list:
2969 nodes_list.sort(key=lambda k: k.location.x)
2970 xloc = nodes_list[0].location.x - 220 # place new nodes at far left
2971 yloc = 0
2972 for node in nodes:
2973 node.select = False
2974 yloc += node_mid_pt(node, 'y')
2975 yloc = yloc/len(nodes)
2976 else:
2977 xloc = 0
2978 yloc = 0
2980 name_with_hashes = without_num + "#"*count_numbers + '.' + extension
2982 bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type=node_type)
2983 node = context.space_data.node_tree.nodes.active
2984 node.label = name_with_hashes
2986 img = bpy.data.images.load(directory+(without_ext+'.'+extension))
2987 img.source = 'SEQUENCE'
2988 img.name = name_with_hashes
2989 node.image = img
2990 image_user = node.image_user if tree.type == 'SHADER' else node
2991 image_user.frame_offset = int(files[0][len(without_num)+len(directory):-1*(len(extension)+1)]) - 1 # separate the number from the file name of the first file
2992 image_user.frame_duration = num_frames
2994 return {'FINISHED'}
2997 class NWAddMultipleImages(Operator, ImportHelper):
2998 """Add multiple images at once"""
2999 bl_idname = 'node.nw_add_multiple_images'
3000 bl_label = 'Open Selected Images'
3001 bl_options = {'REGISTER', 'UNDO'}
3002 directory = StringProperty(subtype="DIR_PATH")
3003 files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
3005 def execute(self, context):
3006 nodes, links = get_nodes_links(context)
3007 nodes_list = [node for node in nodes]
3008 if nodes_list:
3009 nodes_list.sort(key=lambda k: k.location.x)
3010 xloc = nodes_list[0].location.x - 220 # place new nodes at far left
3011 yloc = 0
3012 for node in nodes:
3013 node.select = False
3014 yloc += node_mid_pt(node, 'y')
3015 yloc = yloc/len(nodes)
3016 else:
3017 xloc = 0
3018 yloc = 0
3020 if context.space_data.node_tree.type == 'SHADER':
3021 node_type = "ShaderNodeTexImage"
3022 elif context.space_data.node_tree.type == 'COMPOSITING':
3023 node_type = "CompositorNodeImage"
3024 else:
3025 self.report({'ERROR'}, "Unsupported Node Tree type!")
3026 return {'CANCELLED'}
3028 new_nodes = []
3029 for f in self.files:
3030 fname = f.name
3032 node = nodes.new(node_type)
3033 new_nodes.append(node)
3034 node.label = fname
3035 node.hide = True
3036 node.width_hidden = 100
3037 node.location.x = xloc
3038 node.location.y = yloc
3039 yloc -= 40
3041 img = bpy.data.images.load(self.directory+fname)
3042 node.image = img
3044 # shift new nodes up to center of tree
3045 list_size = new_nodes[0].location.y - new_nodes[-1].location.y
3046 for node in new_nodes:
3047 node.select = True
3048 node.location.y += (list_size/2)
3049 return {'FINISHED'}
3052 class NWViewerFocus(bpy.types.Operator):
3053 """Set the viewer tile center to the mouse position"""
3054 bl_idname = "node.nw_viewer_focus"
3055 bl_label = "Viewer Focus"
3057 x = bpy.props.IntProperty()
3058 y = bpy.props.IntProperty()
3060 @classmethod
3061 def poll(cls, context):
3062 return nw_check(context) and context.space_data.tree_type == 'CompositorNodeTree'
3064 def execute(self, context):
3065 return {'FINISHED'}
3067 def invoke(self, context, event):
3068 render = context.scene.render
3069 space = context.space_data
3070 percent = render.resolution_percentage*0.01
3072 nodes, links = get_nodes_links(context)
3073 viewers = [n for n in nodes if n.type == 'VIEWER']
3075 if viewers:
3076 mlocx = event.mouse_region_x
3077 mlocy = event.mouse_region_y
3078 select_node = bpy.ops.node.select(mouse_x=mlocx, mouse_y=mlocy, extend=False)
3080 if not 'FINISHED' in select_node: # only run if we're not clicking on a node
3081 region_x = context.region.width
3082 region_y = context.region.height
3084 region_center_x = context.region.width / 2
3085 region_center_y = context.region.height / 2
3087 bd_x = render.resolution_x * percent * space.backdrop_zoom
3088 bd_y = render.resolution_y * percent * space.backdrop_zoom
3090 backdrop_center_x = (bd_x / 2) - space.backdrop_x
3091 backdrop_center_y = (bd_y / 2) - space.backdrop_y
3093 margin_x = region_center_x - backdrop_center_x
3094 margin_y = region_center_y - backdrop_center_y
3096 abs_mouse_x = (mlocx - margin_x) / bd_x
3097 abs_mouse_y = (mlocy - margin_y) / bd_y
3099 for node in viewers:
3100 node.center_x = abs_mouse_x
3101 node.center_y = abs_mouse_y
3102 else:
3103 return {'PASS_THROUGH'}
3105 return self.execute(context)
3109 # P A N E L
3112 def drawlayout(context, layout, mode='non-panel'):
3113 tree_type = context.space_data.tree_type
3115 col = layout.column(align=True)
3116 col.menu(NWMergeNodesMenu.bl_idname)
3117 col.separator()
3119 col = layout.column(align=True)
3120 col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type")
3121 col.separator()
3123 if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
3124 col = layout.column(align=True)
3125 col.operator(NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
3126 col.separator()
3128 col = layout.column(align=True)
3129 col.operator(NWDetachOutputs.bl_idname, icon='UNLINKED')
3130 col.operator(NWSwapLinks.bl_idname)
3131 col.menu(NWAddReroutesMenu.bl_idname, text="Add Reroutes", icon='LAYER_USED')
3132 col.separator()
3134 col = layout.column(align=True)
3135 col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
3136 col.operator(NWLinkToOutputNode.bl_idname, icon='DRIVER')
3137 col.separator()
3139 col = layout.column(align=True)
3140 if mode == 'panel':
3141 row = col.row(align=True)
3142 row.operator(NWClearLabel.bl_idname).option = True
3143 row.operator(NWModifyLabels.bl_idname)
3144 else:
3145 col.operator(NWClearLabel.bl_idname).option = True
3146 col.operator(NWModifyLabels.bl_idname)
3147 col.menu(NWBatchChangeNodesMenu.bl_idname, text="Batch Change")
3148 col.separator()
3149 col.menu(NWCopyToSelectedMenu.bl_idname, text="Copy to Selected")
3150 col.separator()
3152 col = layout.column(align=True)
3153 if tree_type == 'CompositorNodeTree':
3154 col.operator(NWResetBG.bl_idname, icon='ZOOM_PREVIOUS')
3155 col.operator(NWReloadImages.bl_idname, icon='FILE_REFRESH')
3156 col.separator()
3158 col = layout.column(align=True)
3159 col.operator(NWFrameSelected.bl_idname, icon='STICKY_UVS_LOC')
3160 col.separator()
3162 col = layout.column(align=True)
3163 col.operator(NWAlignNodes.bl_idname, icon='ALIGN')
3164 col.separator()
3166 col = layout.column(align=True)
3167 col.operator(NWDeleteUnused.bl_idname, icon='CANCEL')
3168 col.separator()
3171 class NodeWranglerPanel(Panel, NWBase):
3172 bl_idname = "NODE_PT_nw_node_wrangler"
3173 bl_space_type = 'NODE_EDITOR'
3174 bl_label = "Node Wrangler"
3175 bl_region_type = "TOOLS"
3176 bl_category = "Node Wrangler"
3178 prepend = StringProperty(
3179 name='prepend',
3181 append = StringProperty()
3182 remove = StringProperty()
3184 def draw(self, context):
3185 self.layout.label(text="(Quick access: Ctrl+Space)")
3186 drawlayout(context, self.layout, mode='panel')
3190 # M E N U S
3192 class NodeWranglerMenu(Menu, NWBase):
3193 bl_idname = "NODE_MT_nw_node_wrangler_menu"
3194 bl_label = "Node Wrangler"
3196 def draw(self, context):
3197 drawlayout(context, self.layout)
3200 class NWMergeNodesMenu(Menu, NWBase):
3201 bl_idname = "NODE_MT_nw_merge_nodes_menu"
3202 bl_label = "Merge Selected Nodes"
3204 def draw(self, context):
3205 type = context.space_data.tree_type
3206 layout = self.layout
3207 if type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
3208 layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
3209 layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
3210 layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
3211 props = layout.operator(NWMergeNodes.bl_idname, text="Use Z-Combine Nodes")
3212 props.mode = 'MIX'
3213 props.merge_type = 'ZCOMBINE'
3214 props = layout.operator(NWMergeNodes.bl_idname, text="Use Alpha Over Nodes")
3215 props.mode = 'MIX'
3216 props.merge_type = 'ALPHAOVER'
3219 class NWMergeShadersMenu(Menu, NWBase):
3220 bl_idname = "NODE_MT_nw_merge_shaders_menu"
3221 bl_label = "Merge Selected Nodes using Shaders"
3223 def draw(self, context):
3224 layout = self.layout
3225 for type in ('MIX', 'ADD'):
3226 props = layout.operator(NWMergeNodes.bl_idname, text=type)
3227 props.mode = type
3228 props.merge_type = 'SHADER'
3231 class NWMergeMixMenu(Menu, NWBase):
3232 bl_idname = "NODE_MT_nw_merge_mix_menu"
3233 bl_label = "Merge Selected Nodes using Mix"
3235 def draw(self, context):
3236 layout = self.layout
3237 for type, name, description in blend_types:
3238 props = layout.operator(NWMergeNodes.bl_idname, text=name)
3239 props.mode = type
3240 props.merge_type = 'MIX'
3243 class NWConnectionListOutputs(Menu, NWBase):
3244 bl_idname = "NODE_MT_nw_connection_list_out"
3245 bl_label = "From:"
3247 def draw(self, context):
3248 layout = self.layout
3249 nodes, links = get_nodes_links(context)
3251 n1 = nodes[context.scene.NWLazySource]
3253 if n1.type == "R_LAYERS":
3254 index=0
3255 for o in n1.outputs:
3256 if o.enabled: # Check which passes the render layer has enabled
3257 layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
3258 index+=1
3259 else:
3260 index=0
3261 for o in n1.outputs:
3262 layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
3263 index+=1
3266 class NWConnectionListInputs(Menu, NWBase):
3267 bl_idname = "NODE_MT_nw_connection_list_in"
3268 bl_label = "To:"
3270 def draw(self, context):
3271 layout = self.layout
3272 nodes, links = get_nodes_links(context)
3274 n2 = nodes[context.scene.NWLazyTarget]
3276 index = 0
3277 for i in n2.inputs:
3278 op = layout.operator(NWMakeLink.bl_idname, text=i.name, icon="FORWARD")
3279 op.from_socket = context.scene.NWSourceSocket
3280 op.to_socket = index
3281 index+=1
3284 class NWMergeMathMenu(Menu, NWBase):
3285 bl_idname = "NODE_MT_nw_merge_math_menu"
3286 bl_label = "Merge Selected Nodes using Math"
3288 def draw(self, context):
3289 layout = self.layout
3290 for type, name, description in operations:
3291 props = layout.operator(NWMergeNodes.bl_idname, text=name)
3292 props.mode = type
3293 props.merge_type = 'MATH'
3296 class NWBatchChangeNodesMenu(Menu, NWBase):
3297 bl_idname = "NODE_MT_nw_batch_change_nodes_menu"
3298 bl_label = "Batch Change Selected Nodes"
3300 def draw(self, context):
3301 layout = self.layout
3302 layout.menu(NWBatchChangeBlendTypeMenu.bl_idname)
3303 layout.menu(NWBatchChangeOperationMenu.bl_idname)
3306 class NWBatchChangeBlendTypeMenu(Menu, NWBase):
3307 bl_idname = "NODE_MT_nw_batch_change_blend_type_menu"
3308 bl_label = "Batch Change Blend Type"
3310 def draw(self, context):
3311 layout = self.layout
3312 for type, name, description in blend_types:
3313 props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
3314 props.blend_type = type
3315 props.operation = 'CURRENT'
3318 class NWBatchChangeOperationMenu(Menu, NWBase):
3319 bl_idname = "NODE_MT_nw_batch_change_operation_menu"
3320 bl_label = "Batch Change Math Operation"
3322 def draw(self, context):
3323 layout = self.layout
3324 for type, name, description in operations:
3325 props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
3326 props.blend_type = 'CURRENT'
3327 props.operation = type
3330 class NWCopyToSelectedMenu(Menu, NWBase):
3331 bl_idname = "NODE_MT_nw_copy_node_properties_menu"
3332 bl_label = "Copy to Selected"
3334 def draw(self, context):
3335 layout = self.layout
3336 layout.operator(NWCopySettings.bl_idname, text="Settings from Active")
3337 layout.menu(NWCopyLabelMenu.bl_idname)
3340 class NWCopyLabelMenu(Menu, NWBase):
3341 bl_idname = "NODE_MT_nw_copy_label_menu"
3342 bl_label = "Copy Label"
3344 def draw(self, context):
3345 layout = self.layout
3346 layout.operator(NWCopyLabel.bl_idname, text="from Active Node's Label").option = 'FROM_ACTIVE'
3347 layout.operator(NWCopyLabel.bl_idname, text="from Linked Node's Label").option = 'FROM_NODE'
3348 layout.operator(NWCopyLabel.bl_idname, text="from Linked Output's Name").option = 'FROM_SOCKET'
3351 class NWAddReroutesMenu(Menu, NWBase):
3352 bl_idname = "NODE_MT_nw_add_reroutes_menu"
3353 bl_label = "Add Reroutes"
3354 bl_description = "Add Reroute Nodes to Selected Nodes' Outputs"
3356 def draw(self, context):
3357 layout = self.layout
3358 layout.operator(NWAddReroutes.bl_idname, text="to All Outputs").option = 'ALL'
3359 layout.operator(NWAddReroutes.bl_idname, text="to Loose Outputs").option = 'LOOSE'
3360 layout.operator(NWAddReroutes.bl_idname, text="to Linked Outputs").option = 'LINKED'
3363 class NWLinkActiveToSelectedMenu(Menu, NWBase):
3364 bl_idname = "NODE_MT_nw_link_active_to_selected_menu"
3365 bl_label = "Link Active to Selected"
3367 def draw(self, context):
3368 layout = self.layout
3369 layout.menu(NWLinkStandardMenu.bl_idname)
3370 layout.menu(NWLinkUseNodeNameMenu.bl_idname)
3371 layout.menu(NWLinkUseOutputsNamesMenu.bl_idname)
3374 class NWLinkStandardMenu(Menu, NWBase):
3375 bl_idname = "NODE_MT_nw_link_standard_menu"
3376 bl_label = "To All Selected"
3378 def draw(self, context):
3379 layout = self.layout
3380 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
3381 props.replace = False
3382 props.use_node_name = False
3383 props.use_outputs_names = False
3384 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
3385 props.replace = True
3386 props.use_node_name = False
3387 props.use_outputs_names = False
3390 class NWLinkUseNodeNameMenu(Menu, NWBase):
3391 bl_idname = "NODE_MT_nw_link_use_node_name_menu"
3392 bl_label = "Use Node Name/Label"
3394 def draw(self, context):
3395 layout = self.layout
3396 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
3397 props.replace = False
3398 props.use_node_name = True
3399 props.use_outputs_names = False
3400 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
3401 props.replace = True
3402 props.use_node_name = True
3403 props.use_outputs_names = False
3406 class NWLinkUseOutputsNamesMenu(Menu, NWBase):
3407 bl_idname = "NODE_MT_nw_link_use_outputs_names_menu"
3408 bl_label = "Use Outputs Names"
3410 def draw(self, context):
3411 layout = self.layout
3412 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
3413 props.replace = False
3414 props.use_node_name = False
3415 props.use_outputs_names = True
3416 props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
3417 props.replace = True
3418 props.use_node_name = False
3419 props.use_outputs_names = True
3422 class NWVertColMenu(bpy.types.Menu):
3423 bl_idname = "NODE_MT_nw_node_vertex_color_menu"
3424 bl_label = "Vertex Colors"
3426 @classmethod
3427 def poll(cls, context):
3428 valid = False
3429 if nw_check(context):
3430 snode = context.space_data
3431 valid = snode.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES'
3432 return valid
3434 def draw(self, context):
3435 l = self.layout
3436 nodes, links = get_nodes_links(context)
3437 mat = context.object.active_material
3439 objs = []
3440 for obj in bpy.data.objects:
3441 for slot in obj.material_slots:
3442 if slot.material == mat:
3443 objs.append(obj)
3444 vcols = []
3445 for obj in objs:
3446 if obj.data.vertex_colors:
3447 for vcol in obj.data.vertex_colors:
3448 vcols.append(vcol.name)
3449 vcols = list(set(vcols)) # get a unique list
3451 if vcols:
3452 for vcol in vcols:
3453 l.operator(NWAddAttrNode.bl_idname, text=vcol).attr_name = vcol
3454 else:
3455 l.label("No Vertex Color layers on objects with this material")
3458 class NWSwitchNodeTypeMenu(Menu, NWBase):
3459 bl_idname = "NODE_MT_nw_switch_node_type_menu"
3460 bl_label = "Switch Type to..."
3462 def draw(self, context):
3463 layout = self.layout
3464 tree = context.space_data.node_tree
3465 if tree.type == 'SHADER':
3466 if context.scene.render.engine == 'CYCLES':
3467 layout.menu(NWSwitchShadersInputSubmenu.bl_idname)
3468 layout.menu(NWSwitchShadersOutputSubmenu.bl_idname)
3469 layout.menu(NWSwitchShadersShaderSubmenu.bl_idname)
3470 layout.menu(NWSwitchShadersTextureSubmenu.bl_idname)
3471 layout.menu(NWSwitchShadersColorSubmenu.bl_idname)
3472 layout.menu(NWSwitchShadersVectorSubmenu.bl_idname)
3473 layout.menu(NWSwitchShadersConverterSubmenu.bl_idname)
3474 layout.menu(NWSwitchShadersLayoutSubmenu.bl_idname)
3475 if context.scene.render.engine != 'CYCLES':
3476 layout.menu(NWSwitchMatInputSubmenu.bl_idname)
3477 layout.menu(NWSwitchMatOutputSubmenu.bl_idname)
3478 layout.menu(NWSwitchMatColorSubmenu.bl_idname)
3479 layout.menu(NWSwitchMatVectorSubmenu.bl_idname)
3480 layout.menu(NWSwitchMatConverterSubmenu.bl_idname)
3481 layout.menu(NWSwitchMatLayoutSubmenu.bl_idname)
3482 if tree.type == 'COMPOSITING':
3483 layout.menu(NWSwitchCompoInputSubmenu.bl_idname)
3484 layout.menu(NWSwitchCompoOutputSubmenu.bl_idname)
3485 layout.menu(NWSwitchCompoColorSubmenu.bl_idname)
3486 layout.menu(NWSwitchCompoConverterSubmenu.bl_idname)
3487 layout.menu(NWSwitchCompoFilterSubmenu.bl_idname)
3488 layout.menu(NWSwitchCompoVectorSubmenu.bl_idname)
3489 layout.menu(NWSwitchCompoMatteSubmenu.bl_idname)
3490 layout.menu(NWSwitchCompoDistortSubmenu.bl_idname)
3491 layout.menu(NWSwitchCompoLayoutSubmenu.bl_idname)
3492 if tree.type == 'TEXTURE':
3493 layout.menu(NWSwitchTexInputSubmenu.bl_idname)
3494 layout.menu(NWSwitchTexOutputSubmenu.bl_idname)
3495 layout.menu(NWSwitchTexColorSubmenu.bl_idname)
3496 layout.menu(NWSwitchTexPatternSubmenu.bl_idname)
3497 layout.menu(NWSwitchTexTexturesSubmenu.bl_idname)
3498 layout.menu(NWSwitchTexConverterSubmenu.bl_idname)
3499 layout.menu(NWSwitchTexDistortSubmenu.bl_idname)
3500 layout.menu(NWSwitchTexLayoutSubmenu.bl_idname)
3503 class NWSwitchShadersInputSubmenu(Menu, NWBase):
3504 bl_idname = "NODE_MT_nw_switch_shaders_input_submenu"
3505 bl_label = "Input"
3507 def draw(self, context):
3508 layout = self.layout
3509 for ident, type, rna_name in shaders_input_nodes_props:
3510 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3511 props.to_type = ident
3514 class NWSwitchShadersOutputSubmenu(Menu, NWBase):
3515 bl_idname = "NODE_MT_nw_switch_shaders_output_submenu"
3516 bl_label = "Output"
3518 def draw(self, context):
3519 layout = self.layout
3520 for ident, type, rna_name in shaders_output_nodes_props:
3521 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3522 props.to_type = ident
3525 class NWSwitchShadersShaderSubmenu(Menu, NWBase):
3526 bl_idname = "NODE_MT_nw_switch_shaders_shader_submenu"
3527 bl_label = "Shader"
3529 def draw(self, context):
3530 layout = self.layout
3531 for ident, type, rna_name in shaders_shader_nodes_props:
3532 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3533 props.to_type = ident
3536 class NWSwitchShadersTextureSubmenu(Menu, NWBase):
3537 bl_idname = "NODE_MT_nw_switch_shaders_texture_submenu"
3538 bl_label = "Texture"
3540 def draw(self, context):
3541 layout = self.layout
3542 for ident, type, rna_name in shaders_texture_nodes_props:
3543 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3544 props.to_type = ident
3547 class NWSwitchShadersColorSubmenu(Menu, NWBase):
3548 bl_idname = "NODE_MT_nw_switch_shaders_color_submenu"
3549 bl_label = "Color"
3551 def draw(self, context):
3552 layout = self.layout
3553 for ident, type, rna_name in shaders_color_nodes_props:
3554 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3555 props.to_type = ident
3558 class NWSwitchShadersVectorSubmenu(Menu, NWBase):
3559 bl_idname = "NODE_MT_nw_switch_shaders_vector_submenu"
3560 bl_label = "Vector"
3562 def draw(self, context):
3563 layout = self.layout
3564 for ident, type, rna_name in shaders_vector_nodes_props:
3565 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3566 props.to_type = ident
3569 class NWSwitchShadersConverterSubmenu(Menu, NWBase):
3570 bl_idname = "NODE_MT_nw_switch_shaders_converter_submenu"
3571 bl_label = "Converter"
3573 def draw(self, context):
3574 layout = self.layout
3575 for ident, type, rna_name in shaders_converter_nodes_props:
3576 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3577 props.to_type = ident
3580 class NWSwitchShadersLayoutSubmenu(Menu, NWBase):
3581 bl_idname = "NODE_MT_nw_switch_shaders_layout_submenu"
3582 bl_label = "Layout"
3584 def draw(self, context):
3585 layout = self.layout
3586 for ident, type, rna_name in shaders_layout_nodes_props:
3587 if type != 'FRAME':
3588 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3589 props.to_type = ident
3592 class NWSwitchCompoInputSubmenu(Menu, NWBase):
3593 bl_idname = "NODE_MT_nw_switch_compo_input_submenu"
3594 bl_label = "Input"
3596 def draw(self, context):
3597 layout = self.layout
3598 for ident, type, rna_name in compo_input_nodes_props:
3599 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3600 props.to_type = ident
3603 class NWSwitchCompoOutputSubmenu(Menu, NWBase):
3604 bl_idname = "NODE_MT_nw_switch_compo_output_submenu"
3605 bl_label = "Output"
3607 def draw(self, context):
3608 layout = self.layout
3609 for ident, type, rna_name in compo_output_nodes_props:
3610 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3611 props.to_type = ident
3614 class NWSwitchCompoColorSubmenu(Menu, NWBase):
3615 bl_idname = "NODE_MT_nw_switch_compo_color_submenu"
3616 bl_label = "Color"
3618 def draw(self, context):
3619 layout = self.layout
3620 for ident, type, rna_name in compo_color_nodes_props:
3621 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3622 props.to_type = ident
3625 class NWSwitchCompoConverterSubmenu(Menu, NWBase):
3626 bl_idname = "NODE_MT_nw_switch_compo_converter_submenu"
3627 bl_label = "Converter"
3629 def draw(self, context):
3630 layout = self.layout
3631 for ident, type, rna_name in compo_converter_nodes_props:
3632 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3633 props.to_type = ident
3636 class NWSwitchCompoFilterSubmenu(Menu, NWBase):
3637 bl_idname = "NODE_MT_nw_switch_compo_filter_submenu"
3638 bl_label = "Filter"
3640 def draw(self, context):
3641 layout = self.layout
3642 for ident, type, rna_name in compo_filter_nodes_props:
3643 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3644 props.to_type = ident
3647 class NWSwitchCompoVectorSubmenu(Menu, NWBase):
3648 bl_idname = "NODE_MT_nw_switch_compo_vector_submenu"
3649 bl_label = "Vector"
3651 def draw(self, context):
3652 layout = self.layout
3653 for ident, type, rna_name in compo_vector_nodes_props:
3654 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3655 props.to_type = ident
3658 class NWSwitchCompoMatteSubmenu(Menu, NWBase):
3659 bl_idname = "NODE_MT_nw_switch_compo_matte_submenu"
3660 bl_label = "Matte"
3662 def draw(self, context):
3663 layout = self.layout
3664 for ident, type, rna_name in compo_matte_nodes_props:
3665 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3666 props.to_type = ident
3669 class NWSwitchCompoDistortSubmenu(Menu, NWBase):
3670 bl_idname = "NODE_MT_nw_switch_compo_distort_submenu"
3671 bl_label = "Distort"
3673 def draw(self, context):
3674 layout = self.layout
3675 for ident, type, rna_name in compo_distort_nodes_props:
3676 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3677 props.to_type = ident
3680 class NWSwitchCompoLayoutSubmenu(Menu, NWBase):
3681 bl_idname = "NODE_MT_nw_switch_compo_layout_submenu"
3682 bl_label = "Layout"
3684 def draw(self, context):
3685 layout = self.layout
3686 for ident, type, rna_name in compo_layout_nodes_props:
3687 if type != 'FRAME':
3688 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3689 props.to_type = ident
3692 class NWSwitchMatInputSubmenu(Menu, NWBase):
3693 bl_idname = "NODE_MT_nw_switch_mat_input_submenu"
3694 bl_label = "Input"
3696 def draw(self, context):
3697 layout = self.layout
3698 for ident, type, rna_name in blender_mat_input_nodes_props:
3699 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3700 props.to_type = ident
3703 class NWSwitchMatOutputSubmenu(Menu, NWBase):
3704 bl_idname = "NODE_MT_nw_switch_mat_output_submenu"
3705 bl_label = "Output"
3707 def draw(self, context):
3708 layout = self.layout
3709 for ident, type, rna_name in blender_mat_output_nodes_props:
3710 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3711 props.to_type = ident
3714 class NWSwitchMatColorSubmenu(Menu, NWBase):
3715 bl_idname = "NODE_MT_nw_switch_mat_color_submenu"
3716 bl_label = "Color"
3718 def draw(self, context):
3719 layout = self.layout
3720 for ident, type, rna_name in blender_mat_color_nodes_props:
3721 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3722 props.to_type = ident
3725 class NWSwitchMatVectorSubmenu(Menu, NWBase):
3726 bl_idname = "NODE_MT_nw_switch_mat_vector_submenu"
3727 bl_label = "Vector"
3729 def draw(self, context):
3730 layout = self.layout
3731 for ident, type, rna_name in blender_mat_vector_nodes_props:
3732 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3733 props.to_type = ident
3736 class NWSwitchMatConverterSubmenu(Menu, NWBase):
3737 bl_idname = "NODE_MT_nw_switch_mat_converter_submenu"
3738 bl_label = "Converter"
3740 def draw(self, context):
3741 layout = self.layout
3742 for ident, type, rna_name in blender_mat_converter_nodes_props:
3743 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3744 props.to_type = ident
3747 class NWSwitchMatLayoutSubmenu(Menu, NWBase):
3748 bl_idname = "NODE_MT_nw_switch_mat_layout_submenu"
3749 bl_label = "Layout"
3751 def draw(self, context):
3752 layout = self.layout
3753 for ident, type, rna_name in blender_mat_layout_nodes_props:
3754 if type != 'FRAME':
3755 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3756 props.to_type = ident
3759 class NWSwitchTexInputSubmenu(Menu, NWBase):
3760 bl_idname = "NODE_MT_nw_switch_tex_input_submenu"
3761 bl_label = "Input"
3763 def draw(self, context):
3764 layout = self.layout
3765 for ident, type, rna_name in texture_input_nodes_props:
3766 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3767 props.to_type = ident
3770 class NWSwitchTexOutputSubmenu(Menu, NWBase):
3771 bl_idname = "NODE_MT_nw_switch_tex_output_submenu"
3772 bl_label = "Output"
3774 def draw(self, context):
3775 layout = self.layout
3776 for ident, type, rna_name in texture_output_nodes_props:
3777 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3778 props.to_type = ident
3781 class NWSwitchTexColorSubmenu(Menu, NWBase):
3782 bl_idname = "NODE_MT_nw_switch_tex_color_submenu"
3783 bl_label = "Color"
3785 def draw(self, context):
3786 layout = self.layout
3787 for ident, type, rna_name in texture_color_nodes_props:
3788 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3789 props.to_type = ident
3792 class NWSwitchTexPatternSubmenu(Menu, NWBase):
3793 bl_idname = "NODE_MT_nw_switch_tex_pattern_submenu"
3794 bl_label = "Pattern"
3796 def draw(self, context):
3797 layout = self.layout
3798 for ident, type, rna_name in texture_pattern_nodes_props:
3799 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3800 props.to_type = ident
3803 class NWSwitchTexTexturesSubmenu(Menu, NWBase):
3804 bl_idname = "NODE_MT_nw_switch_tex_textures_submenu"
3805 bl_label = "Textures"
3807 def draw(self, context):
3808 layout = self.layout
3809 for ident, type, rna_name in texture_textures_nodes_props:
3810 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3811 props.to_type = ident
3814 class NWSwitchTexConverterSubmenu(Menu, NWBase):
3815 bl_idname = "NODE_MT_nw_switch_tex_converter_submenu"
3816 bl_label = "Converter"
3818 def draw(self, context):
3819 layout = self.layout
3820 for ident, type, rna_name in texture_converter_nodes_props:
3821 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3822 props.to_type = ident
3825 class NWSwitchTexDistortSubmenu(Menu, NWBase):
3826 bl_idname = "NODE_MT_nw_switch_tex_distort_submenu"
3827 bl_label = "Distort"
3829 def draw(self, context):
3830 layout = self.layout
3831 for ident, type, rna_name in texture_distort_nodes_props:
3832 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3833 props.to_type = ident
3836 class NWSwitchTexLayoutSubmenu(Menu, NWBase):
3837 bl_idname = "NODE_MT_nw_switch_tex_layout_submenu"
3838 bl_label = "Layout"
3840 def draw(self, context):
3841 layout = self.layout
3842 for ident, type, rna_name in texture_layout_nodes_props:
3843 if type != 'FRAME':
3844 props = layout.operator(NWSwitchNodeType.bl_idname, text=rna_name)
3845 props.to_type = ident
3849 # APPENDAGES TO EXISTING UI
3853 def select_parent_children_buttons(self, context):
3854 layout = self.layout
3855 layout.operator(NWSelectParentChildren.bl_idname, text="Select frame's members (children)").option = 'CHILD'
3856 layout.operator(NWSelectParentChildren.bl_idname, text="Select parent frame").option = 'PARENT'
3859 def attr_nodes_menu_func(self, context):
3860 col = self.layout.column(align=True)
3861 col.menu("NODE_MT_nw_node_vertex_color_menu")
3862 col.separator()
3865 def multipleimages_menu_func(self, context):
3866 col = self.layout.column(align=True)
3867 col.operator(NWAddMultipleImages.bl_idname, text="Multiple Images")
3868 col.operator(NWAddSequence.bl_idname, text="Image Sequence")
3869 col.separator()
3872 def bgreset_menu_func(self, context):
3873 self.layout.operator(NWResetBG.bl_idname)
3877 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
3880 addon_keymaps = []
3881 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
3882 # props entry: (property name, property value)
3883 kmi_defs = (
3884 # MERGE NODES
3885 # NWMergeNodes with Ctrl (AUTO).
3886 (NWMergeNodes.bl_idname, 'NUMPAD_0', 'PRESS', True, False, False,
3887 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
3888 (NWMergeNodes.bl_idname, 'ZERO', 'PRESS', True, False, False,
3889 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
3890 (NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, False, False,
3891 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
3892 (NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, False, False,
3893 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
3894 (NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
3895 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
3896 (NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, False, False,
3897 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
3898 (NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, False, False,
3899 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
3900 (NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, False, False,
3901 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
3902 (NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, False, False,
3903 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
3904 (NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, False, False,
3905 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
3906 (NWMergeNodes.bl_idname, 'COMMA', 'PRESS', True, False, False,
3907 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
3908 (NWMergeNodes.bl_idname, 'PERIOD', 'PRESS', True, False, False,
3909 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
3910 (NWMergeNodes.bl_idname, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
3911 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
3912 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
3913 (NWMergeNodes.bl_idname, 'NUMPAD_0', 'PRESS', True, False, True,
3914 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
3915 (NWMergeNodes.bl_idname, 'ZERO', 'PRESS', True, False, True,
3916 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
3917 (NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, False, True,
3918 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
3919 (NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, False, True,
3920 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
3921 (NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
3922 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
3923 (NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, False, True,
3924 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
3925 (NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, False, True,
3926 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
3927 (NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, False, True,
3928 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
3929 (NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, False, True,
3930 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
3931 (NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, False, True,
3932 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
3933 # NWMergeNodes with Ctrl Shift (MATH)
3934 (NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, True, False,
3935 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
3936 (NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, True, False,
3937 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
3938 (NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
3939 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
3940 (NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, True, False,
3941 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
3942 (NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, True, False,
3943 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
3944 (NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, True, False,
3945 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
3946 (NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, True, False,
3947 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
3948 (NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, True, False,
3949 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
3950 (NWMergeNodes.bl_idname, 'COMMA', 'PRESS', True, True, False,
3951 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
3952 (NWMergeNodes.bl_idname, 'PERIOD', 'PRESS', True, True, False,
3953 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
3954 # BATCH CHANGE NODES
3955 # NWBatchChangeNodes with Alt
3956 (NWBatchChangeNodes.bl_idname, 'NUMPAD_0', 'PRESS', False, False, True,
3957 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
3958 (NWBatchChangeNodes.bl_idname, 'ZERO', 'PRESS', False, False, True,
3959 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
3960 (NWBatchChangeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', False, False, True,
3961 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
3962 (NWBatchChangeNodes.bl_idname, 'EQUAL', 'PRESS', False, False, True,
3963 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
3964 (NWBatchChangeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
3965 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
3966 (NWBatchChangeNodes.bl_idname, 'EIGHT', 'PRESS', False, False, True,
3967 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
3968 (NWBatchChangeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', False, False, True,
3969 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
3970 (NWBatchChangeNodes.bl_idname, 'MINUS', 'PRESS', False, False, True,
3971 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
3972 (NWBatchChangeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', False, False, True,
3973 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
3974 (NWBatchChangeNodes.bl_idname, 'SLASH', 'PRESS', False, False, True,
3975 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
3976 (NWBatchChangeNodes.bl_idname, 'COMMA', 'PRESS', False, False, True,
3977 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
3978 (NWBatchChangeNodes.bl_idname, 'PERIOD', 'PRESS', False, False, True,
3979 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
3980 (NWBatchChangeNodes.bl_idname, 'DOWN_ARROW', 'PRESS', False, False, True,
3981 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
3982 (NWBatchChangeNodes.bl_idname, 'UP_ARROW', 'PRESS', False, False, True,
3983 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
3984 # LINK ACTIVE TO SELECTED
3985 # Don't use names, don't replace links (K)
3986 (NWLinkActiveToSelected.bl_idname, 'K', 'PRESS', False, False, False,
3987 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
3988 # Don't use names, replace links (Shift K)
3989 (NWLinkActiveToSelected.bl_idname, 'K', 'PRESS', False, True, False,
3990 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
3991 # Use node name, don't replace links (')
3992 (NWLinkActiveToSelected.bl_idname, 'QUOTE', 'PRESS', False, False, False,
3993 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
3994 # Use node name, replace links (Shift ')
3995 (NWLinkActiveToSelected.bl_idname, 'QUOTE', 'PRESS', False, True, False,
3996 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
3997 # Don't use names, don't replace links (;)
3998 (NWLinkActiveToSelected.bl_idname, 'SEMI_COLON', 'PRESS', False, False, False,
3999 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4000 # Don't use names, replace links (')
4001 (NWLinkActiveToSelected.bl_idname, 'SEMI_COLON', 'PRESS', False, True, False,
4002 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4003 # CHANGE MIX FACTOR
4004 (NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4005 (NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4006 (NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4007 (NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4008 (NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4009 (NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4010 (NWChangeMixFactor.bl_idname, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4011 (NWChangeMixFactor.bl_idname, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4012 (NWChangeMixFactor.bl_idname, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4013 (NWChangeMixFactor.bl_idname, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4014 # CLEAR LABEL (Alt L)
4015 (NWClearLabel.bl_idname, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4016 # MODIFY LABEL (Alt Shift L)
4017 (NWModifyLabels.bl_idname, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4018 # Copy Label from active to selected
4019 (NWCopyLabel.bl_idname, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4020 # DETACH OUTPUTS (Alt Shift D)
4021 (NWDetachOutputs.bl_idname, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4022 # LINK TO OUTPUT NODE (O)
4023 (NWLinkToOutputNode.bl_idname, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4024 # SELECT PARENT/CHILDREN
4025 # Select Children
4026 (NWSelectParentChildren.bl_idname, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4027 # Select Parent
4028 (NWSelectParentChildren.bl_idname, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4029 # Add Texture Setup
4030 (NWAddTextureSetup.bl_idname, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4031 # Reset backdrop
4032 (NWResetBG.bl_idname, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4033 # Delete unused
4034 (NWDeleteUnused.bl_idname, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4035 # Frame Seleted
4036 (NWFrameSelected.bl_idname, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4037 # Swap Outputs
4038 (NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4039 # Emission Viewer
4040 (NWEmissionViewer.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4041 # Reload Images
4042 (NWReloadImages.bl_idname, 'R', 'PRESS', False, False, True, None, "Reload images"),
4043 # Lazy Mix
4044 (NWLazyMix.bl_idname, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
4045 # Lazy Connect
4046 (NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', True, False, False, None, "Lazy Connect"),
4047 # Lazy Connect with Menu
4048 (NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4049 # Viewer Tile Center
4050 (NWViewerFocus.bl_idname, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4051 # Align Nodes
4052 (NWAlignNodes.bl_idname, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4053 # MENUS
4054 ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"),
4055 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
4056 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
4057 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
4058 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"),
4059 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu.bl_idname),), "Switch node type menu"),
4063 def register():
4064 # props
4065 bpy.types.Scene.NWBusyDrawing = StringProperty(
4066 name="Busy Drawing!",
4067 default="",
4068 description="An internal property used to store only the first mouse position")
4069 bpy.types.Scene.NWLazySource = StringProperty(
4070 name="Lazy Source!",
4071 default="x",
4072 description="An internal property used to store the first node in a Lazy Connect operation")
4073 bpy.types.Scene.NWLazyTarget = StringProperty(
4074 name="Lazy Target!",
4075 default="x",
4076 description="An internal property used to store the last node in a Lazy Connect operation")
4077 bpy.types.Scene.NWSourceSocket = IntProperty(
4078 name="Source Socket!",
4079 default=0,
4080 description="An internal property used to store the source socket in a Lazy Connect operation")
4082 bpy.utils.register_module(__name__)
4084 # keymaps
4085 addon_keymaps.clear()
4086 kc = bpy.context.window_manager.keyconfigs.addon
4087 if kc:
4088 km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
4089 for (identifier, key, action, CTRL, SHIFT, ALT, props, nicename) in kmi_defs:
4090 kmi = km.keymap_items.new(identifier, key, action, ctrl=CTRL, shift=SHIFT, alt=ALT)
4091 if props:
4092 for prop, value in props:
4093 setattr(kmi.properties, prop, value)
4094 addon_keymaps.append((km, kmi))
4096 # menu items
4097 bpy.types.NODE_MT_select.append(select_parent_children_buttons)
4098 bpy.types.NODE_MT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func)
4099 bpy.types.NODE_PT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func)
4100 bpy.types.NODE_PT_backdrop.append(bgreset_menu_func)
4101 bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
4102 bpy.types.NODE_PT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
4103 bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func)
4104 bpy.types.NODE_PT_category_CMP_INPUT.prepend(multipleimages_menu_func)
4107 def unregister():
4108 # props
4109 del bpy.types.Scene.NWBusyDrawing
4110 del bpy.types.Scene.NWLazySource
4111 del bpy.types.Scene.NWLazyTarget
4112 del bpy.types.Scene.NWSourceSocket
4114 # keymaps
4115 for km, kmi in addon_keymaps:
4116 km.keymap_items.remove(kmi)
4117 addon_keymaps.clear()
4119 # menuitems
4120 bpy.types.NODE_MT_select.remove(select_parent_children_buttons)
4121 bpy.types.NODE_MT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func)
4122 bpy.types.NODE_PT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func)
4123 bpy.types.NODE_PT_backdrop.remove(bgreset_menu_func)
4124 bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
4125 bpy.types.NODE_PT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
4126 bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func)
4127 bpy.types.NODE_PT_category_CMP_INPUT.remove(multipleimages_menu_func)
4129 bpy.utils.unregister_module(__name__)
4131 if __name__ == "__main__":
4132 register()