1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 "name": "Node Wrangler",
21 "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig",
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",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Nodes/Nodes_Efficiency_Tools",
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
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)
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),
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'),
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'),
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.
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.
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.
497 ('CURRENT', 'Current', 'Leave at current state'),
498 ('NEXT', 'Next', 'Next blend type/operation'),
499 ('PREV', 'Prev', 'Previous blend type/operation'),
504 (1.0, 1.0, 1.0, 0.7),
505 (1.0, 0.0, 0.0, 0.7),
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)
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)
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)
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)
529 (1.0, 1.0, 1.0, 0.7),
530 (0.0, 0.0, 0.0, 0.7),
536 def nice_hotkey_name(punc
):
537 # convert the ugly string name into the actual character
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"),
559 ('LINE_FEED', "Enter"),
566 ('BACK_SLASH', "\\"),
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 +"),
586 for (ugly
, nice
) in pairs
:
591 nice_punc
= punc
.replace("_", " ").title()
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
600 elif context
.space_data
.tree_type
== "CompositorNodeTree":
601 node
= nodes
.new('CompositorNodeMath')
602 node
.inputs
[0].default_value
= 0.0
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
613 return bpy
.context
.user_preferences
.system
.dpi
/(72/retinafac
)
616 def is_end_node(node
):
618 for output
in node
.outputs
:
625 def node_mid_pt(node
, axis
):
627 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
629 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
635 def autolink(node1
, node2
, links
):
638 for outp
in node1
.outputs
:
639 for inp
in node2
.inputs
:
640 if not inp
.is_linked
and inp
.name
== outp
.name
:
645 for outp
in node1
.outputs
:
646 for inp
in node2
.inputs
:
647 if not inp
.is_linked
and inp
.type == outp
.type:
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
:
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:
669 for outp
in node1
.outputs
:
670 for inp
in node2
.inputs
:
675 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
679 def node_at_pos(nodes
, context
, event
):
680 nodes_near_mouse
= []
681 nodes_under_mouse
= []
684 store_mouse_cursor(context
, event
)
685 x
, y
= context
.space_data
.cursor_location
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
= []
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()
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...
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]
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()
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
742 target_node
= nearest_node
# else use the nearest node
744 target_node
= nearest_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
)
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
)
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
)
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()
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
)
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
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
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
821 if node
.type == 'REROUTE':
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):
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
)
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):
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
)
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):
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
)
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):
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
)
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
)
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
)
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
)
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
)
931 bgl
.glDisable(bgl
.GL_BLEND
)
932 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
935 def draw_callback_nodeoutline(self
, context
, mode
):
937 nodes
, links
= get_nodes_links(context
)
938 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
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]
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
]
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
975 draw_circle(m1x
, m1y
, 7, col_outer
)
976 draw_circle(m2x
, m2y
, 7, col_outer
)
979 draw_circle(m1x
, m1y
, 5, col_circle_inner
)
980 draw_circle(m2x
, m2y
, 5, col_circle_inner
)
982 # restore opengl defaults
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
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
1004 is_main_tree
= context_active
== active
1005 if not is_main_tree
: # if group is currently edited
1006 tree
= active
.node_tree
1014 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1015 bl_idname
= __name__
1017 merge_hide
= EnumProperty(
1018 name
="Hide Mix nodes",
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",
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")
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",
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",
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")
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")
1062 for hotkey
in kmi_defs
:
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])
1071 keystr
= "Shift " + keystr
1073 keystr
= "Alt " + keystr
1075 keystr
= "Ctrl " + keystr
1078 def nw_check(context
):
1079 space
= context
.space_data
1081 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None:
1088 def poll(cls
, context
):
1089 return nw_check(context
)
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
)
1104 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1107 if not context
.scene
.NWBusyDrawing
:
1108 node1
= node_at_pos(nodes
, context
, event
)
1110 context
.scene
.NWBusyDrawing
= node1
.name
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')
1126 node2
= node_at_pos(nodes
, context
, event
)
1128 context
.scene
.NWBusyDrawing
= node2
.name
1140 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1142 context
.scene
.NWBusyDrawing
= ""
1145 elif event
.type == 'ESC':
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'}
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
)
1181 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1184 if not context
.scene
.NWBusyDrawing
:
1185 node1
= node_at_pos(nodes
, context
, event
)
1187 context
.scene
.NWBusyDrawing
= node1
.name
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')
1203 node2
= node_at_pos(nodes
, context
, event
)
1205 context
.scene
.NWBusyDrawing
= node2
.name
1210 link_success
= False
1216 if node
.select
== True:
1218 original_sel
.append(node
)
1220 original_unsel
.append(node
)
1224 #link_success = autolink(node1, node2, links)
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)
1231 link_success
= autolink(node1
, node2
, links
)
1233 for node
in original_sel
:
1235 for node
in original_unsel
:
1239 hack_force_update(context
, nodes
)
1240 context
.scene
.NWBusyDrawing
= ""
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
)
1254 context
.scene
.NWBusyDrawing
= node
.name
1256 # the arguments we pass the the callback
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'}
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'}
1281 def poll(cls
, context
):
1283 if nw_check(context
):
1284 if context
.space_data
.node_tree
.nodes
:
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'
1297 if node
.select
== True:
1298 selection
.append(node
.name
)
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
1308 if is_end_node(node
) and not node
.type in end_types
and node
.type != 'FRAME':
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
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
)
1324 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1326 self
.report({'INFO'}, "Nothing deleted")
1329 nodes
, links
= get_nodes_links(context
)
1331 if node
.name
in selection
:
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'}
1346 def poll(cls
, context
):
1348 if nw_check(context
):
1349 if context
.selected_nodes
:
1350 valid
= len(context
.selected_nodes
) <= 2
1353 def execute(self
, context
):
1354 nodes
, links
= get_nodes_links(context
)
1355 selected_nodes
= context
.selected_nodes
1356 n1
= selected_nodes
[0]
1359 if len(selected_nodes
) == 2:
1360 n2
= selected_nodes
[1]
1361 if n1
.outputs
and n2
.outputs
:
1366 for output
in n1
.outputs
:
1368 for link
in output
.links
:
1369 n1_outputs
.append([out_index
, link
.to_socket
])
1374 for output
in n2
.outputs
:
1376 for link
in output
.links
:
1377 n2_outputs
.append([out_index
, link
.to_socket
])
1381 for connection
in n1_outputs
:
1383 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1385 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1386 for connection
in n2_outputs
:
1388 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1390 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1392 if n1
.outputs
or n2
.outputs
:
1393 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1395 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1398 elif len(selected_nodes
) == 1:
1402 for i1
in n1
.inputs
:
1405 for i2
in n1
.inputs
:
1406 if i1
.type == i2
.type and i2
.is_linked
:
1408 types
.append ([i1
, similar_types
, i
])
1410 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1415 for i2
in n1
.inputs
:
1416 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
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
1426 fs
= t
[0].links
[0].from_socket
1428 links
.remove(t
[0].links
[0])
1429 if i
+1 == len(n1
.inputs
):
1432 while n1
.inputs
[i
].is_linked
:
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
1444 self
.report({'WARNING'}, "This node has no input connections to swap!")
1446 self
.report({'WARNING'}, "This node has no inputs to swap!")
1448 hack_force_update(context
, nodes
)
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'}
1459 def poll(cls
, context
):
1461 if nw_check(context
):
1462 snode
= context
.space_data
1463 valid
= snode
.tree_type
== 'CompositorNodeTree'
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
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
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'}
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":
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"
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
]
1534 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1535 for out
in active
.outputs
:
1540 # get material_output node, store selection, deselect all
1541 materialout
= None # placeholder node
1544 if node
.type == shader_output_type
:
1547 selection
.append(node
.name
)
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
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
1571 for i
, out
in enumerate(active
.outputs
):
1573 valid_outputs
.append(i
)
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
1582 if i
< len(valid_outputs
) - 1:
1583 out_i
= valid_outputs
[i
+ 1]
1585 out_i
= valid_outputs
[0]
1586 make_links
= [] # store sockets for new links
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]
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
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]))
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]))
1614 if node
.name
== 'Emission Viewer':
1616 bpy
.ops
.node
.delete()
1617 for li_from
, li_to
in make_links
:
1618 links
.new(li_from
, li_to
)
1620 nodes
.active
= active
1622 if node
.name
in selection
:
1624 hack_force_update(context
, nodes
)
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
)
1643 if node
.select
== True:
1644 selected
.append(node
)
1646 bpy
.ops
.node
.add_node(type='NodeFrame')
1648 frm
.label
= self
.label_prop
1649 frm
.use_custom_color
= True
1650 frm
.color
= self
.color_prop
1652 for node
in selected
:
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"]
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()
1681 self
.report({'INFO'}, "Reloaded images")
1682 print("Reloaded " + str(num_reloaded
) + " images")
1683 hack_force_update(context
, nodes
)
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
]
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'):
1753 new_node
.image
= node
.image
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.
1763 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1765 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1766 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
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
1783 # Not every socket, especially in outputs has "default_value"
1784 if hasattr(socket
, 'default_value'):
1785 dval
= socket
.default_value
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
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']]
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.
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':
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
]:
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\
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\
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
)
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(
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(
1916 description
="Type of Merge to be used",
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'
1933 do_hide_shader
= False
1934 if merge_hide
== 'ALWAYS':
1936 do_hide_shader
= True
1937 elif merge_hide
== 'NON_SHADER':
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
)
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':
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'
1980 if output_type
== type and valid_mode
:
1981 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
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
1999 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2001 count_before
= len(nodes
)
2002 # sort list by loc_x - reversed
2003 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
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
2015 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2019 if nodes_list
== selected_shader
and not do_hide_shader
:
2021 the_range
= len(nodes_list
) - 1
2022 if len(nodes_list
) == 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
2030 add
.inputs
[0].default_value
= 1.0
2031 add
.show_preview
= False
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
2047 add
.width_hidden
= 100.0
2048 elif nodes_list
== selected_shader
:
2050 add_type
= node_type
+ 'MixShader'
2051 add
= nodes
.new(add_type
)
2052 add
.hide
= do_hide_shader
2057 add
.width_hidden
= 100.0
2059 add_type
= node_type
+ 'AddShader'
2060 add
= nodes
.new(add_type
)
2061 add
.hide
= do_hide_shader
2066 add
.width_hidden
= 100.0
2067 elif nodes_list
== selected_z
:
2068 add
= nodes
.new('CompositorNodeZcombine')
2069 add
.show_preview
= False
2075 add
.width_hidden
= 100.0
2076 elif nodes_list
== selected_alphaover
:
2077 add
= nodes
.new('CompositorNodeAlphaOver')
2078 add
.show_preview
= False
2084 add
.width_hidden
= 100.0
2085 add
.location
= loc_x
, loc_y
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
]
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])
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
])
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
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(
2162 items
=blend_types
+ navs
,
2164 operation
= EnumProperty(
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
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]
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]
2190 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
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
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]
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)
2210 node
.operation
= operations
[len(operations
) - 1][0]
2212 node
.operation
= operations
[index
- 1][0]
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
):
2234 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2238 fac
= nodes
[si
].inputs
[0]
2239 nodes
[si
].hide
= False
2240 if option
in {0.0, 1.0}:
2241 fac
.default_value
= option
2243 fac
.default_value
+= option
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'}
2255 def poll(cls
, context
):
2257 if nw_check(context
):
2258 if context
.active_node
is not None and context
.active_node
.type is not 'FRAME':
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
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'
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.
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
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
):
2300 link
= input.links
[0]
2301 links
.new(link
.from_socket
, copied
.inputs
[i
])
2302 for out
, output
in enumerate(node
.outputs
):
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')
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
)
2314 bpy
.ops
.node
.select_all(action
='DESELECT')
2315 for node
in reselect
:
2317 nodes
.active
= active
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(
2329 description
="Source of name of label",
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':
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
:
2351 src
= input.links
[0].from_node
2352 node
.label
= src
.label
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
:
2359 src
= input.links
[0].from_socket
2360 node
.label
= src
.name
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
]:
2380 def invoke(self
, context
, event
):
2382 return self
.execute(context
)
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(
2399 replace_from
= StringProperty(
2400 name
="Text to Replace"
2402 replace_to
= StringProperty(
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
2413 def invoke(self
, context
, event
):
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'}
2427 def poll(cls
, context
):
2429 if nw_check(context
):
2430 space
= context
.space_data
2431 if space
.tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
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
]
2443 if active
.type in shader_types
or active
.type in texture_types
:
2444 if not active
.inputs
[0].is_linked
:
2447 locx
= active
.location
.x
2448 locy
= active
.location
.y
2450 xoffset
= [500.0, 700.0]
2452 if active
.type not in shader_types
:
2453 xoffset
= [290.0, 500.0]
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'
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]
2471 coord
= nodes
.new('ShaderNodeTexCoord')
2472 coord
.location
= [locx
- xoffset
[1], locy
+ 40.0]
2473 active
.select
= False
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])
2483 links
.new(map.outputs
[0], active
.inputs
[0])
2484 links
.new(coord
.outputs
[coordout
], map.inputs
[0])
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(
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
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
]:
2518 # unhide 'REROUTE' nodes to avoid issues with location.y
2519 if node
.type == 'REROUTE':
2521 # When node is hidden - width_hidden not usable.
2522 # Hack needed to calculate real width
2524 bpy
.ops
.node
.select_all(action
='DESELECT')
2525 helper
= nodes
.new('NodeReroute')
2526 helper
.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
2535 # only helper is selected now
2536 bpy
.ops
.node
.delete()
2537 x
= node
.location
.x
+ width
+ 20.0
2538 if node
.type != 'REROUTE':
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':
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':
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
)
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.
2566 n
= nodes
.new('NodeReroute')
2568 for link
in output
.links
:
2569 links
.new(n
.outputs
[0], link
.to_socket
)
2570 links
.new(output
, n
.inputs
[0])
2572 post_select
.append(n
)
2576 # disselect the node so that after execution of script only newly created nodes are selected
2578 # nicer reroutes distribution along y when 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
:
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()
2600 def poll(cls
, context
):
2602 if nw_check(context
):
2603 if context
.active_node
is not None:
2604 if context
.active_node
.select
:
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':
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.
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
)
2636 doit
= True # Will be changed to False when links successfully added to previous output.
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
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.
2647 # Set src_name to source node name or label
2648 src_name
= active
.name
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
:
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
:
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
2683 if node
.select
and node
.type != 'FRAME':
2684 selection
.append(node
)
2686 # If no nodes are selected, align all 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
2704 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
2706 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
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
2715 node
.location
.x
= current_pos
2716 current_pos
+= current_margin
+ node
.dimensions
.x
2717 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
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
:
2733 node
.location
.x
+= (mid_x
- new_mid
)
2735 node
.location
.y
+= (mid_y
- new_mid
)
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(
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
:
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
:
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
:
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')
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'}
2800 def poll(cls
, context
):
2802 if nw_check(context
):
2803 if context
.active_node
is not None:
2804 for out
in context
.active_node
.outputs
:
2810 def execute(self
, context
):
2811 nodes
, links
= get_nodes_links(context
)
2812 active
= nodes
.active
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
2822 if node
.type in output_types
:
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')
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
):
2843 for i
, output
in enumerate(active
.outputs
):
2844 if output
.type == output_node
.inputs
[0].type and not output
.hide
:
2849 if tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
2850 if active
.outputs
[output_index
].name
== 'Volume':
2852 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
2854 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
2856 hack_force_update(context
, nodes
) # viewport render does not update
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
)
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])
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
2917 tree
= context
.space_data
.node_tree
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"
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
2955 for char
in reverse
:
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
]
2969 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
2970 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
2974 yloc
+= node_mid_pt(node
, 'y')
2975 yloc
= yloc
/len(nodes
)
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
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
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
]
3009 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3010 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3014 yloc
+= node_mid_pt(node
, 'y')
3015 yloc
= yloc
/len(nodes
)
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"
3025 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3026 return {'CANCELLED'}
3029 for f
in self
.files
:
3032 node
= nodes
.new(node_type
)
3033 new_nodes
.append(node
)
3036 node
.width_hidden
= 100
3037 node
.location
.x
= xloc
3038 node
.location
.y
= yloc
3041 img
= bpy
.data
.images
.load(self
.directory
+fname
)
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
:
3048 node
.location
.y
+= (list_size
/2)
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()
3061 def poll(cls
, context
):
3062 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3064 def execute(self
, context
):
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']
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
3103 return {'PASS_THROUGH'}
3105 return self
.execute(context
)
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
)
3119 col
= layout
.column(align
=True)
3120 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
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')
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')
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')
3139 col
= layout
.column(align
=True)
3141 row
= col
.row(align
=True)
3142 row
.operator(NWClearLabel
.bl_idname
).option
= True
3143 row
.operator(NWModifyLabels
.bl_idname
)
3145 col
.operator(NWClearLabel
.bl_idname
).option
= True
3146 col
.operator(NWModifyLabels
.bl_idname
)
3147 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3149 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
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')
3158 col
= layout
.column(align
=True)
3159 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3162 col
= layout
.column(align
=True)
3163 col
.operator(NWAlignNodes
.bl_idname
, icon
='ALIGN')
3166 col
= layout
.column(align
=True)
3167 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
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(
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')
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")
3213 props
.merge_type
= 'ZCOMBINE'
3214 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
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)
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
)
3240 props
.merge_type
= 'MIX'
3243 class NWConnectionListOutputs(Menu
, NWBase
):
3244 bl_idname
= "NODE_MT_nw_connection_list_out"
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":
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
3261 for o
in n1
.outputs
:
3262 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3266 class NWConnectionListInputs(Menu
, NWBase
):
3267 bl_idname
= "NODE_MT_nw_connection_list_in"
3270 def draw(self
, context
):
3271 layout
= self
.layout
3272 nodes
, links
= get_nodes_links(context
)
3274 n2
= nodes
[context
.scene
.NWLazyTarget
]
3278 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3279 op
.from_socket
= context
.scene
.NWSourceSocket
3280 op
.to_socket
= index
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
)
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"
3427 def poll(cls
, context
):
3429 if nw_check(context
):
3430 snode
= context
.space_data
3431 valid
= snode
.tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES'
3434 def draw(self
, context
):
3436 nodes
, links
= get_nodes_links(context
)
3437 mat
= context
.object.active_material
3440 for obj
in bpy
.data
.objects
:
3441 for slot
in obj
.material_slots
:
3442 if slot
.material
== mat
:
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
3453 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
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"
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"
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"
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"
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"
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"
3584 def draw(self
, context
):
3585 layout
= self
.layout
3586 for ident
, type, rna_name
in shaders_layout_nodes_props
:
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"
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"
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"
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"
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"
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"
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"
3684 def draw(self
, context
):
3685 layout
= self
.layout
3686 for ident
, type, rna_name
in compo_layout_nodes_props
:
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"
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"
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"
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"
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"
3751 def draw(self
, context
):
3752 layout
= self
.layout
3753 for ident
, type, rna_name
in blender_mat_layout_nodes_props
:
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"
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"
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"
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"
3840 def draw(self
, context
):
3841 layout
= self
.layout
3842 for ident
, type, rna_name
in texture_layout_nodes_props
:
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")
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")
3872 def bgreset_menu_func(self
, context
):
3873 self
.layout
.operator(NWResetBG
.bl_idname
)
3877 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
3881 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
3882 # props entry: (property name, property value)
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)"),
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
4026 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4028 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4030 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4032 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4034 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4036 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4038 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4040 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4042 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4044 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
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"),
4052 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
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"),
4065 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4066 name
="Busy Drawing!",
4068 description
="An internal property used to store only the first mouse position")
4069 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4070 name
="Lazy Source!",
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!",
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!",
4080 description
="An internal property used to store the source socket in a Lazy Connect operation")
4082 bpy
.utils
.register_module(__name__
)
4085 addon_keymaps
.clear()
4086 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
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
)
4092 for prop
, value
in props
:
4093 setattr(kmi
.properties
, prop
, value
)
4094 addon_keymaps
.append((km
, kmi
))
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
)
4109 del bpy
.types
.Scene
.NWBusyDrawing
4110 del bpy
.types
.Scene
.NWLazySource
4111 del bpy
.types
.Scene
.NWLazyTarget
4112 del bpy
.types
.Scene
.NWSourceSocket
4115 for km
, kmi
in addon_keymaps
:
4116 km
.keymap_items
.remove(kmi
)
4117 addon_keymaps
.clear()
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__":