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, Christian Brinkmann, Florian Meyer",
23 "blender": (2, 80, 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 (
43 from bpy_extras
.io_utils
import ImportHelper
, ExportHelper
44 from mathutils
import Vector
45 from math
import cos
, sin
, pi
, hypot
49 from itertools
import chain
51 from collections
import namedtuple
55 # list of outputs of Input Render Layer
56 # with attributes determinig if pass is used,
57 # and MultiLayer EXR outputs names and corresponding render engines
59 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_internal, in_cycles)
60 RL_entry
= namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_internal', 'in_cycles'])
62 RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
63 RL_entry('use_pass_color', 'Color', 'Color', True, False),
64 RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
65 RL_entry('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
66 RL_entry('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
67 RL_entry('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
68 RL_entry('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
69 RL_entry('use_pass_emit', 'Emit', 'Emit', True, False),
70 RL_entry('use_pass_environment', 'Environment', 'Env', True, False),
71 RL_entry('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
72 RL_entry('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
73 RL_entry('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
74 RL_entry('use_pass_indirect', 'Indirect', 'Indirect', True, False),
75 RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
76 RL_entry('use_pass_mist', 'Mist', 'Mist', True, False),
77 RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
78 RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
79 RL_entry('use_pass_reflection', 'Reflect', 'Reflect', True, False),
80 RL_entry('use_pass_refraction', 'Refract', 'Refract', True, False),
81 RL_entry('use_pass_shadow', 'Shadow', 'Shadow', True, True),
82 RL_entry('use_pass_specular', 'Specular', 'Spec', True, False),
83 RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', False, True),
84 RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', False, True),
85 RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
86 RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
87 RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
88 RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
89 RL_entry('use_pass_uv', 'UV', 'UV', True, True),
90 RL_entry('use_pass_vector', 'Speed', 'Vector', True, True),
91 RL_entry('use_pass_z', 'Z', 'Depth', True, True),
95 # (rna_type.identifier, type, rna_type.name)
96 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
97 shaders_input_nodes_props
= (
98 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
99 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
100 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
101 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
102 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
103 ('ShaderNodeRGB', 'RGB', 'RGB'),
104 ('ShaderNodeValue', 'VALUE', 'Value'),
105 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
106 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
107 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
108 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
109 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
110 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
111 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
112 ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
114 # (rna_type.identifier, type, rna_type.name)
115 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
116 shaders_output_nodes_props
= (
117 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
118 ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
119 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
121 # (rna_type.identifier, type, rna_type.name)
122 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
123 shaders_shader_nodes_props
= (
124 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
125 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
126 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
127 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
128 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
129 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
130 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
131 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
132 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
133 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
134 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
135 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
136 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
137 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
138 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
139 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
140 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
141 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
142 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
143 ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
145 # (rna_type.identifier, type, rna_type.name)
146 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
147 shaders_texture_nodes_props
= (
148 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
149 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
150 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
151 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
152 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
153 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
154 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
155 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
156 ('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
157 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
158 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
159 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
161 # (rna_type.identifier, type, rna_type.name)
162 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
163 shaders_color_nodes_props
= (
164 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
165 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
166 ('ShaderNodeInvert', 'INVERT', 'Invert'),
167 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
168 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
169 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
170 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
172 # (rna_type.identifier, type, rna_type.name)
173 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
174 shaders_vector_nodes_props
= (
175 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
176 ('ShaderNodeBump', 'BUMP', 'Bump'),
177 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
178 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
179 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
180 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
182 # (rna_type.identifier, type, rna_type.name)
183 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
184 shaders_converter_nodes_props
= (
185 ('ShaderNodeMath', 'MATH', 'Math'),
186 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
187 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
188 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
189 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
190 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
191 ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
192 ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
193 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
194 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
195 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
196 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
198 # (rna_type.identifier, type, rna_type.name)
199 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
200 shaders_layout_nodes_props
= (
201 ('NodeFrame', 'FRAME', 'Frame'),
202 ('NodeReroute', 'REROUTE', 'Reroute'),
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_input_nodes_props
= (
209 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
210 ('CompositorNodeImage', 'IMAGE', 'Image'),
211 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
212 ('CompositorNodeMask', 'MASK', 'Mask'),
213 ('CompositorNodeRGB', 'RGB', 'RGB'),
214 ('CompositorNodeValue', 'VALUE', 'Value'),
215 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
216 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
217 ('CompositorNodeTime', 'TIME', 'Time'),
218 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
220 # (rna_type.identifier, type, rna_type.name)
221 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
222 compo_output_nodes_props
= (
223 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
224 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
225 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
226 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
227 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
229 # (rna_type.identifier, type, rna_type.name)
230 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
231 compo_color_nodes_props
= (
232 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
233 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
234 ('CompositorNodeInvert', 'INVERT', 'Invert'),
235 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
236 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
237 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
238 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
239 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
240 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
241 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
242 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
243 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
245 # (rna_type.identifier, type, rna_type.name)
246 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
247 compo_converter_nodes_props
= (
248 ('CompositorNodeMath', 'MATH', 'Math'),
249 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
250 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
251 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
252 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
253 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
254 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
255 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
256 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
257 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
258 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
259 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
260 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
261 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
263 # (rna_type.identifier, type, rna_type.name)
264 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
265 compo_filter_nodes_props
= (
266 ('CompositorNodeBlur', 'BLUR', 'Blur'),
267 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
268 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
269 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
270 ('CompositorNodeFilter', 'FILTER', 'Filter'),
271 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
272 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
273 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
274 ('CompositorNodeGlare', 'GLARE', 'Glare'),
275 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
276 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
277 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
278 ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
280 # (rna_type.identifier, type, rna_type.name)
281 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
282 compo_vector_nodes_props
= (
283 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
284 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
285 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
286 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
287 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
289 # (rna_type.identifier, type, rna_type.name)
290 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
291 compo_matte_nodes_props
= (
292 ('CompositorNodeKeying', 'KEYING', 'Keying'),
293 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
294 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
295 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
296 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
297 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
298 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
299 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
300 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
301 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
302 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
303 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
305 # (rna_type.identifier, type, rna_type.name)
306 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
307 compo_distort_nodes_props
= (
308 ('CompositorNodeScale', 'SCALE', 'Scale'),
309 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
310 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
311 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
312 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
313 ('CompositorNodeFlip', 'FLIP', 'Flip'),
314 ('CompositorNodeCrop', 'CROP', 'Crop'),
315 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
316 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
317 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
318 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
319 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
320 ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
322 # (rna_type.identifier, type, rna_type.name)
323 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
324 compo_layout_nodes_props
= (
325 ('NodeFrame', 'FRAME', 'Frame'),
326 ('NodeReroute', 'REROUTE', 'Reroute'),
327 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
329 # Blender Render material nodes
330 # (rna_type.identifier, type, rna_type.name)
331 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
332 blender_mat_input_nodes_props
= (
333 ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
334 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
335 ('ShaderNodeLightData', 'LIGHT', 'Light Data'),
336 ('ShaderNodeValue', 'VALUE', 'Value'),
337 ('ShaderNodeRGB', 'RGB', 'RGB'),
338 ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
339 ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
340 ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
343 # (rna_type.identifier, type, rna_type.name)
344 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
345 blender_mat_output_nodes_props
= (
346 ('ShaderNodeOutput', 'OUTPUT', 'Output'),
349 # (rna_type.identifier, type, rna_type.name)
350 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
351 blender_mat_color_nodes_props
= (
352 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
353 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
354 ('ShaderNodeInvert', 'INVERT', 'Invert'),
355 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
358 # (rna_type.identifier, type, rna_type.name)
359 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
360 blender_mat_vector_nodes_props
= (
361 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
362 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
363 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
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_converter_nodes_props
= (
369 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
370 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
371 ('ShaderNodeMath', 'MATH', 'Math'),
372 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
373 ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
374 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
375 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
376 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
377 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
380 # (rna_type.identifier, type, rna_type.name)
381 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
382 blender_mat_layout_nodes_props
= (
383 ('NodeReroute', 'REROUTE', 'Reroute'),
387 # (rna_type.identifier, type, rna_type.name)
388 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
389 texture_input_nodes_props
= (
390 ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
391 ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
392 ('TextureNodeTexture', 'TEXTURE', 'Texture'),
393 ('TextureNodeImage', 'IMAGE', 'Image'),
396 # (rna_type.identifier, type, rna_type.name)
397 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
398 texture_output_nodes_props
= (
399 ('TextureNodeOutput', 'OUTPUT', 'Output'),
400 ('TextureNodeViewer', 'VIEWER', 'Viewer'),
403 # (rna_type.identifier, type, rna_type.name)
404 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
405 texture_color_nodes_props
= (
406 ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
407 ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
408 ('TextureNodeInvert', 'INVERT', 'Invert'),
409 ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
410 ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
411 ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
414 # (rna_type.identifier, type, rna_type.name)
415 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
416 texture_pattern_nodes_props
= (
417 ('TextureNodeChecker', 'CHECKER', 'Checker'),
418 ('TextureNodeBricks', 'BRICKS', 'Bricks'),
421 # (rna_type.identifier, type, rna_type.name)
422 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
423 texture_textures_nodes_props
= (
424 ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
425 ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
426 ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
427 ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
428 ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
429 ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
430 ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
431 ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
432 ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
433 ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
436 # (rna_type.identifier, type, rna_type.name)
437 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
438 texture_converter_nodes_props
= (
439 ('TextureNodeMath', 'MATH', 'Math'),
440 ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
441 ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
442 ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
443 ('TextureNodeDistance', 'DISTANCE', 'Distance'),
446 # (rna_type.identifier, type, rna_type.name)
447 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
448 texture_distort_nodes_props
= (
449 ('TextureNodeScale', 'SCALE', 'Scale'),
450 ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
451 ('TextureNodeRotate', 'ROTATE', 'Rotate'),
452 ('TextureNodeAt', 'AT', 'At'),
455 # (rna_type.identifier, type, rna_type.name)
456 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
457 texture_layout_nodes_props
= (
458 ('NodeReroute', 'REROUTE', 'Reroute'),
461 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
462 # used list, not tuple for easy merging with other lists.
464 ('MIX', 'Mix', 'Mix Mode'),
465 ('ADD', 'Add', 'Add Mode'),
466 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
467 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
468 ('SCREEN', 'Screen', 'Screen Mode'),
469 ('DIVIDE', 'Divide', 'Divide Mode'),
470 ('DIFFERENCE', 'Difference', 'Difference Mode'),
471 ('DARKEN', 'Darken', 'Darken Mode'),
472 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
473 ('OVERLAY', 'Overlay', 'Overlay Mode'),
474 ('DODGE', 'Dodge', 'Dodge Mode'),
475 ('BURN', 'Burn', 'Burn Mode'),
476 ('HUE', 'Hue', 'Hue Mode'),
477 ('SATURATION', 'Saturation', 'Saturation Mode'),
478 ('VALUE', 'Value', 'Value Mode'),
479 ('COLOR', 'Color', 'Color Mode'),
480 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
481 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
484 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
485 # used list, not tuple for easy merging with other lists.
487 ('ADD', 'Add', 'Add Mode'),
488 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
489 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
490 ('DIVIDE', 'Divide', 'Divide Mode'),
491 ('SINE', 'Sine', 'Sine Mode'),
492 ('COSINE', 'Cosine', 'Cosine Mode'),
493 ('TANGENT', 'Tangent', 'Tangent Mode'),
494 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
495 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
496 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
497 ('POWER', 'Power', 'Power Mode'),
498 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
499 ('MINIMUM', 'Minimum', 'Minimum Mode'),
500 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
501 ('ROUND', 'Round', 'Round Mode'),
502 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
503 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
504 ('MODULO', 'Modulo', 'Modulo Mode'),
505 ('ABSOLUTE', 'Absolute', 'Absolute Mode'),
508 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
509 # used list, not tuple for easy merging with other lists.
511 ('CURRENT', 'Current', 'Leave at current state'),
512 ('NEXT', 'Next', 'Next blend type/operation'),
513 ('PREV', 'Prev', 'Previous blend type/operation'),
518 (1.0, 1.0, 1.0, 0.7),
519 (1.0, 0.0, 0.0, 0.7),
523 (0.0, 0.0, 0.0, 1.0),
524 (0.38, 0.77, 0.38, 1.0),
525 (0.38, 0.77, 0.38, 1.0)
528 (0.0, 0.0, 0.0, 1.0),
529 (0.77, 0.77, 0.16, 1.0),
530 (0.77, 0.77, 0.16, 1.0)
533 (0.0, 0.0, 0.0, 1.0),
534 (0.38, 0.38, 0.77, 1.0),
535 (0.38, 0.38, 0.77, 1.0)
538 (0.0, 0.0, 0.0, 1.0),
539 (0.63, 0.63, 0.63, 1.0),
540 (0.63, 0.63, 0.63, 1.0)
543 (1.0, 1.0, 1.0, 0.7),
544 (0.0, 0.0, 0.0, 0.7),
550 def is_cycles_or_eevee(context
):
551 return context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}
554 def nice_hotkey_name(punc
):
555 # convert the ugly string name into the actual character
557 ('LEFTMOUSE', "LMB"),
558 ('MIDDLEMOUSE', "MMB"),
559 ('RIGHTMOUSE', "RMB"),
560 ('SELECTMOUSE', "Select"),
561 ('WHEELUPMOUSE', "Wheel Up"),
562 ('WHEELDOWNMOUSE', "Wheel Down"),
563 ('WHEELINMOUSE', "Wheel In"),
564 ('WHEELOUTMOUSE', "Wheel Out"),
577 ('LINE_FEED', "Enter"),
584 ('BACK_SLASH', "\\"),
586 ('NUMPAD_1', "Numpad 1"),
587 ('NUMPAD_2', "Numpad 2"),
588 ('NUMPAD_3', "Numpad 3"),
589 ('NUMPAD_4', "Numpad 4"),
590 ('NUMPAD_5', "Numpad 5"),
591 ('NUMPAD_6', "Numpad 6"),
592 ('NUMPAD_7', "Numpad 7"),
593 ('NUMPAD_8', "Numpad 8"),
594 ('NUMPAD_9', "Numpad 9"),
595 ('NUMPAD_0', "Numpad 0"),
596 ('NUMPAD_PERIOD', "Numpad ."),
597 ('NUMPAD_SLASH', "Numpad /"),
598 ('NUMPAD_ASTERIX', "Numpad *"),
599 ('NUMPAD_MINUS', "Numpad -"),
600 ('NUMPAD_ENTER', "Numpad Enter"),
601 ('NUMPAD_PLUS', "Numpad +"),
604 for (ugly
, nice
) in pairs
:
609 nice_punc
= punc
.replace("_", " ").title()
613 def force_update(context
):
614 context
.space_data
.node_tree
.update_tag()
618 prefs
= bpy
.context
.user_preferences
.system
619 return prefs
.dpi
* prefs
.pixel_size
/ 72
622 def node_mid_pt(node
, axis
):
624 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
626 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
632 def autolink(node1
, node2
, links
):
635 for outp
in node1
.outputs
:
636 for inp
in node2
.inputs
:
637 if not inp
.is_linked
and inp
.name
== outp
.name
:
642 for outp
in node1
.outputs
:
643 for inp
in node2
.inputs
:
644 if not inp
.is_linked
and inp
.type == outp
.type:
649 # force some connection even if the type doesn't match
650 for outp
in node1
.outputs
:
651 for inp
in node2
.inputs
:
652 if not inp
.is_linked
:
657 # even if no sockets are open, force one of matching type
658 for outp
in node1
.outputs
:
659 for inp
in node2
.inputs
:
660 if inp
.type == outp
.type:
666 for outp
in node1
.outputs
:
667 for inp
in node2
.inputs
:
672 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
676 def node_at_pos(nodes
, context
, event
):
677 nodes_near_mouse
= []
678 nodes_under_mouse
= []
681 store_mouse_cursor(context
, event
)
682 x
, y
= context
.space_data
.cursor_location
686 # Make a list of each corner (and middle of border) for each node.
687 # Will be sorted to find nearest point and thus nearest node
688 node_points_with_dist
= []
691 if node
.type != 'FRAME': # no point trying to link to a frame node
692 locx
= node
.location
.x
693 locy
= node
.location
.y
694 dimx
= node
.dimensions
.x
/dpifac()
695 dimy
= node
.dimensions
.y
/dpifac()
697 locx
+= node
.parent
.location
.x
698 locy
+= node
.parent
.location
.y
699 if node
.parent
.parent
:
700 locx
+= node
.parent
.parent
.location
.x
701 locy
+= node
.parent
.parent
.location
.y
702 if node
.parent
.parent
.parent
:
703 locx
+= node
.parent
.parent
.parent
.location
.x
704 locy
+= node
.parent
.parent
.parent
.location
.y
705 if node
.parent
.parent
.parent
.parent
:
706 # Support three levels or parenting
707 # There's got to be a better way to do this...
710 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- locy
)]) # Top Left
711 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- locy
)]) # Top Right
712 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- dimy
))]) # Bottom Left
713 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- dimy
))]) # Bottom Right
715 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- locy
)]) # Mid Top
716 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- (locy
- dimy
))]) # Mid Bottom
717 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- (dimy
/ 2)))]) # Mid Left
718 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- (dimy
/ 2)))]) # Mid Right
720 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
723 if node
.type != 'FRAME' and skipnode
== False:
724 locx
= node
.location
.x
725 locy
= node
.location
.y
726 dimx
= node
.dimensions
.x
/dpifac()
727 dimy
= node
.dimensions
.y
/dpifac()
729 locx
+= node
.parent
.location
.x
730 locy
+= node
.parent
.location
.y
731 if (locx
<= x
<= locx
+ dimx
) and \
732 (locy
- dimy
<= y
<= locy
):
733 nodes_under_mouse
.append(node
)
735 if len(nodes_under_mouse
) == 1:
736 if nodes_under_mouse
[0] != nearest_node
:
737 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
739 target_node
= nearest_node
# else use the nearest node
741 target_node
= nearest_node
745 def store_mouse_cursor(context
, event
):
746 space
= context
.space_data
747 v2d
= context
.region
.view2d
748 tree
= space
.edit_tree
750 # convert mouse position to the View2D for later node placement
751 if context
.region
.type == 'WINDOW':
752 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
754 space
.cursor_location
= tree
.view_center
757 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=[1.0, 1.0, 1.0, 0.7]):
758 shademodel_state
= bgl
.Buffer(bgl
.GL_INT
, 1)
759 bgl
.glGetIntegerv(bgl
.GL_SHADE_MODEL
, shademodel_state
)
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
)
776 bgl
.glShadeModel(shademodel_state
[0])
777 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
780 def draw_circle(mx
, my
, radius
, colour
=[1.0, 1.0, 1.0, 0.7]):
781 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
782 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
783 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
784 radius
= radius
* dpifac()
786 for i
in range(sides
+ 1):
787 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
788 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
789 bgl
.glVertex2f(cosine
, sine
)
791 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
794 def draw_rounded_node_border(node
, radius
=8, colour
=[1.0, 1.0, 1.0, 0.7]):
795 bgl
.glEnable(bgl
.GL_BLEND
)
796 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
798 area_width
= bpy
.context
.area
.width
- (16*dpifac()) - 1
799 bottom_bar
= (16*dpifac()) + 1
801 radius
= radius
*dpifac()
802 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
804 nlocx
= (node
.location
.x
+1)*dpifac()
805 nlocy
= (node
.location
.y
+1)*dpifac()
806 ndimx
= node
.dimensions
.x
807 ndimy
= node
.dimensions
.y
808 # This is a stupid way to do this... TODO use while loop
810 nlocx
+= node
.parent
.location
.x
811 nlocy
+= node
.parent
.location
.y
812 if node
.parent
.parent
:
813 nlocx
+= node
.parent
.parent
.location
.x
814 nlocy
+= node
.parent
.parent
.location
.y
815 if node
.parent
.parent
.parent
:
816 nlocx
+= node
.parent
.parent
.parent
.location
.x
817 nlocy
+= node
.parent
.parent
.parent
.location
.y
822 if node
.type == 'REROUTE':
830 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
831 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
832 bgl
.glVertex2f(mx
,my
)
833 for i
in range(sides
+1):
835 if my
> bottom_bar
and mx
< area_width
:
836 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
837 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
838 bgl
.glVertex2f(cosine
, sine
)
842 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
843 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
844 bgl
.glVertex2f(mx
,my
)
845 for i
in range(sides
+1):
847 if my
> bottom_bar
and mx
< area_width
:
848 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
849 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
850 bgl
.glVertex2f(cosine
, sine
)
854 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
855 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
856 bgl
.glVertex2f(mx
,my
)
857 for i
in range(sides
+1):
859 if my
> bottom_bar
and mx
< area_width
:
860 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
861 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
862 bgl
.glVertex2f(cosine
, sine
)
865 # Bottom right corner
866 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
867 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
868 bgl
.glVertex2f(mx
,my
)
869 for i
in range(sides
+1):
871 if my
> bottom_bar
and mx
< area_width
:
872 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
873 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
874 bgl
.glVertex2f(cosine
, sine
)
879 bgl
.glBegin(bgl
.GL_QUADS
)
880 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
881 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
882 m1y
= max(m1y
, bottom_bar
)
883 m2y
= max(m2y
, bottom_bar
)
884 if m1x
< area_width
and m2x
< area_width
:
885 bgl
.glVertex2f(m2x
-radius
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
886 bgl
.glVertex2f(m2x
,m2y
)
887 bgl
.glVertex2f(m1x
,m1y
)
888 bgl
.glVertex2f(m1x
-radius
,m1y
)
892 bgl
.glBegin(bgl
.GL_QUADS
)
893 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
894 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
895 m1x
= min(m1x
, area_width
)
896 m2x
= min(m2x
, area_width
)
897 if m1y
> bottom_bar
and m2y
> bottom_bar
:
898 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
899 bgl
.glVertex2f(m2x
,m2y
)
900 bgl
.glVertex2f(m2x
,m1y
+radius
)
901 bgl
.glVertex2f(m1x
,m1y
+radius
)
905 bgl
.glBegin(bgl
.GL_QUADS
)
906 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
907 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
908 m1y
= max(m1y
, bottom_bar
)
909 m2y
= max(m2y
, bottom_bar
)
910 if m1x
< area_width
and m2x
< area_width
:
911 bgl
.glVertex2f(m2x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
912 bgl
.glVertex2f(m2x
+radius
,m2y
)
913 bgl
.glVertex2f(m1x
+radius
,m1y
)
914 bgl
.glVertex2f(m1x
,m1y
)
918 bgl
.glBegin(bgl
.GL_QUADS
)
919 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
, clip
=False)
920 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
, clip
=False)
921 m1x
= min(m1x
, area_width
)
922 m2x
= min(m2x
, area_width
)
923 if m1y
> bottom_bar
and m2y
> bottom_bar
:
924 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
925 bgl
.glVertex2f(m2x
,m2y
)
926 bgl
.glVertex2f(m2x
,m1y
-radius
)
927 bgl
.glVertex2f(m1x
,m1y
-radius
)
932 bgl
.glDisable(bgl
.GL_BLEND
)
933 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
936 def draw_callback_nodeoutline(self
, context
, mode
):
938 nodes
, links
= get_nodes_links(context
)
939 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
942 col_outer
= [1.0, 0.2, 0.2, 0.4]
943 col_inner
= [0.0, 0.0, 0.0, 0.5]
944 col_circle_inner
= [0.3, 0.05, 0.05, 1.0]
945 elif mode
== "LINKMENU":
946 col_outer
= [0.4, 0.6, 1.0, 0.4]
947 col_inner
= [0.0, 0.0, 0.0, 0.5]
948 col_circle_inner
= [0.08, 0.15, .3, 1.0]
950 col_outer
= [0.2, 1.0, 0.2, 0.4]
951 col_inner
= [0.0, 0.0, 0.0, 0.5]
952 col_circle_inner
= [0.05, 0.3, 0.05, 1.0]
954 m1x
= self
.mouse_path
[0][0]
955 m1y
= self
.mouse_path
[0][1]
956 m2x
= self
.mouse_path
[-1][0]
957 m2y
= self
.mouse_path
[-1][1]
959 n1
= nodes
[context
.scene
.NWLazySource
]
960 n2
= nodes
[context
.scene
.NWLazyTarget
]
963 col_outer
= [0.4, 0.4, 0.4, 0.4]
964 col_inner
= [0.0, 0.0, 0.0, 0.5]
965 col_circle_inner
= [0.2, 0.2, 0.2, 1.0]
967 draw_rounded_node_border(n1
, radius
=6, colour
=col_outer
) # outline
968 draw_rounded_node_border(n1
, radius
=5, colour
=col_inner
) # inner
969 draw_rounded_node_border(n2
, radius
=6, colour
=col_outer
) # outline
970 draw_rounded_node_border(n2
, radius
=5, colour
=col_inner
) # inner
972 draw_line(m1x
, m1y
, m2x
, m2y
, 5, col_outer
) # line outline
973 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
976 draw_circle(m1x
, m1y
, 7, col_outer
)
977 draw_circle(m2x
, m2y
, 7, col_outer
)
980 draw_circle(m1x
, m1y
, 5, col_circle_inner
)
981 draw_circle(m2x
, m2y
, 5, col_circle_inner
)
983 # restore opengl defaults
985 bgl
.glDisable(bgl
.GL_BLEND
)
986 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
988 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
991 def get_nodes_links(context
):
992 tree
= context
.space_data
.node_tree
994 # Get nodes from currently edited tree.
995 # If user is editing a group, space_data.node_tree is still the base level (outside group).
996 # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
997 # the same as context.active_node, the user is in a group.
998 # Check recursively until we find the real active node_tree:
999 if tree
.nodes
.active
:
1000 while tree
.nodes
.active
!= context
.active_node
:
1001 tree
= tree
.nodes
.active
.node_tree
1003 return tree
.nodes
, tree
.links
1006 class NWPrincipledPreferences(bpy
.types
.PropertyGroup
):
1007 base_color
: StringProperty(
1009 default
='diffuse diff albedo base col color',
1010 description
='Naming Components for Base Color maps')
1011 sss_color
: StringProperty(
1012 name
='Subsurface Color',
1013 default
='sss subsurface',
1014 description
='Naming Components for Subsurface Color maps')
1015 metallic
: StringProperty(
1017 default
='metallic metalness metal mtl',
1018 description
='Naming Components for metallness maps')
1019 specular
: StringProperty(
1021 default
='specularity specular spec spc',
1022 description
='Naming Components for Specular maps')
1023 normal
: StringProperty(
1025 default
='normal nor nrm nrml norm',
1026 description
='Naming Components for Normal maps')
1027 bump
: StringProperty(
1030 description
='Naming Components for bump maps')
1031 rough
: StringProperty(
1033 default
='roughness rough rgh',
1034 description
='Naming Components for roughness maps')
1035 gloss
: StringProperty(
1037 default
='gloss glossy glossyness',
1038 description
='Naming Components for glossy maps')
1039 displacement
: StringProperty(
1040 name
='Displacement',
1041 default
='displacement displace disp dsp height heightmap',
1042 description
='Naming Components for displacement maps')
1045 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1046 bl_idname
= __name__
1048 merge_hide
: EnumProperty(
1049 name
="Hide Mix nodes",
1051 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1052 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1053 ("NEVER", "Never", "Never collapse the new merge nodes")
1055 default
='NON_SHADER',
1056 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy whether to collapse them or show the full node with options expanded")
1057 merge_position
: EnumProperty(
1058 name
="Mix Node Position",
1060 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1061 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1064 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy the position of the new nodes")
1066 show_hotkey_list
: BoolProperty(
1067 name
="Show Hotkey List",
1069 description
="Expand this box into a list of all the hotkeys for functions in this addon"
1071 hotkey_list_filter
: StringProperty(
1072 name
=" Filter by Name",
1074 description
="Show only hotkeys that have this text in their name"
1076 show_principled_lists
: BoolProperty(
1077 name
="Show Principled naming tags",
1079 description
="Expand this box into a list of all naming tags for principled texture setup"
1081 principled_tags
: bpy
.props
.PointerProperty(type=NWPrincipledPreferences
)
1083 def draw(self
, context
):
1084 layout
= self
.layout
1085 col
= layout
.column()
1086 col
.prop(self
, "merge_position")
1087 col
.prop(self
, "merge_hide")
1090 col
= box
.column(align
=True)
1091 col
.prop(self
, "show_principled_lists", text
='Edit tags for auto texture detection in Principled BSDF setup', toggle
=True)
1092 if self
.show_principled_lists
:
1093 tags
= self
.principled_tags
1095 col
.prop(tags
, "base_color")
1096 col
.prop(tags
, "sss_color")
1097 col
.prop(tags
, "metallic")
1098 col
.prop(tags
, "specular")
1099 col
.prop(tags
, "rough")
1100 col
.prop(tags
, "gloss")
1101 col
.prop(tags
, "normal")
1102 col
.prop(tags
, "bump")
1103 col
.prop(tags
, "displacement")
1106 col
= box
.column(align
=True)
1107 hotkey_button_name
= "Show Hotkey List"
1108 if self
.show_hotkey_list
:
1109 hotkey_button_name
= "Hide Hotkey List"
1110 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
1111 if self
.show_hotkey_list
:
1112 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
1114 for hotkey
in kmi_defs
:
1116 hotkey_name
= hotkey
[7]
1118 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
1119 row
= col
.row(align
=True)
1120 row
.label(hotkey_name
)
1121 keystr
= nice_hotkey_name(hotkey
[1])
1123 keystr
= "Shift " + keystr
1125 keystr
= "Alt " + keystr
1127 keystr
= "Ctrl " + keystr
1132 def nw_check(context
):
1133 space
= context
.space_data
1134 valid_trees
= ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
1137 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and space
.tree_type
in valid_trees
:
1144 def poll(cls
, context
):
1145 return nw_check(context
)
1149 class NWLazyMix(Operator
, NWBase
):
1150 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1151 bl_idname
= "node.nw_lazy_mix"
1152 bl_label
= "Mix Nodes"
1153 bl_options
= {'REGISTER', 'UNDO'}
1155 def modal(self
, context
, event
):
1156 context
.area
.tag_redraw()
1157 nodes
, links
= get_nodes_links(context
)
1160 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1163 if not context
.scene
.NWBusyDrawing
:
1164 node1
= node_at_pos(nodes
, context
, event
)
1166 context
.scene
.NWBusyDrawing
= node1
.name
1168 if context
.scene
.NWBusyDrawing
!= 'STOP':
1169 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1171 context
.scene
.NWLazySource
= node1
.name
1172 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1174 if event
.type == 'MOUSEMOVE':
1175 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1177 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1178 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1179 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1182 node2
= node_at_pos(nodes
, context
, event
)
1184 context
.scene
.NWBusyDrawing
= node2
.name
1196 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1198 context
.scene
.NWBusyDrawing
= ""
1201 elif event
.type == 'ESC':
1203 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1204 return {'CANCELLED'}
1206 return {'RUNNING_MODAL'}
1208 def invoke(self
, context
, event
):
1209 if context
.area
.type == 'NODE_EDITOR':
1210 # the arguments we pass the the callback
1211 args
= (self
, context
, 'MIX')
1212 # Add the region OpenGL drawing callback
1213 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1214 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1216 self
.mouse_path
= []
1218 context
.window_manager
.modal_handler_add(self
)
1219 return {'RUNNING_MODAL'}
1221 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1222 return {'CANCELLED'}
1225 class NWLazyConnect(Operator
, NWBase
):
1226 """Connect two nodes without clicking a specific socket (automatically determined"""
1227 bl_idname
= "node.nw_lazy_connect"
1228 bl_label
= "Lazy Connect"
1229 bl_options
= {'REGISTER', 'UNDO'}
1230 with_menu
: BoolProperty()
1232 def modal(self
, context
, event
):
1233 context
.area
.tag_redraw()
1234 nodes
, links
= get_nodes_links(context
)
1237 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1240 if not context
.scene
.NWBusyDrawing
:
1241 node1
= node_at_pos(nodes
, context
, event
)
1243 context
.scene
.NWBusyDrawing
= node1
.name
1245 if context
.scene
.NWBusyDrawing
!= 'STOP':
1246 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1248 context
.scene
.NWLazySource
= node1
.name
1249 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1251 if event
.type == 'MOUSEMOVE':
1252 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1254 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1255 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1256 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1259 node2
= node_at_pos(nodes
, context
, event
)
1261 context
.scene
.NWBusyDrawing
= node2
.name
1266 link_success
= False
1272 if node
.select
== True:
1274 original_sel
.append(node
)
1276 original_unsel
.append(node
)
1280 #link_success = autolink(node1, node2, links)
1282 if len(node1
.outputs
) > 1 and node2
.inputs
:
1283 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1284 elif len(node1
.outputs
) == 1:
1285 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1287 link_success
= autolink(node1
, node2
, links
)
1289 for node
in original_sel
:
1291 for node
in original_unsel
:
1295 force_update(context
)
1296 context
.scene
.NWBusyDrawing
= ""
1299 elif event
.type == 'ESC':
1300 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1301 return {'CANCELLED'}
1303 return {'RUNNING_MODAL'}
1305 def invoke(self
, context
, event
):
1306 if context
.area
.type == 'NODE_EDITOR':
1307 nodes
, links
= get_nodes_links(context
)
1308 node
= node_at_pos(nodes
, context
, event
)
1310 context
.scene
.NWBusyDrawing
= node
.name
1312 # the arguments we pass the the callback
1316 args
= (self
, context
, mode
)
1317 # Add the region OpenGL drawing callback
1318 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1319 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1321 self
.mouse_path
= []
1323 context
.window_manager
.modal_handler_add(self
)
1324 return {'RUNNING_MODAL'}
1326 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1327 return {'CANCELLED'}
1330 class NWDeleteUnused(Operator
, NWBase
):
1331 """Delete all nodes whose output is not used"""
1332 bl_idname
= 'node.nw_del_unused'
1333 bl_label
= 'Delete Unused Nodes'
1334 bl_options
= {'REGISTER', 'UNDO'}
1336 delete_muted
: BoolProperty(name
="Delete Muted", description
="Delete (but reconnect, like Ctrl-X) all muted nodes", default
=True)
1337 delete_frames
: BoolProperty(name
="Delete Empty Frames", description
="Delete all frames that have no nodes inside them", default
=True)
1339 def is_unused_node(self
, node
):
1340 end_types
= ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1341 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
1342 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
1343 if node
.type in end_types
:
1346 for output
in node
.outputs
:
1352 def poll(cls
, context
):
1354 if nw_check(context
):
1355 if context
.space_data
.node_tree
.nodes
:
1359 def execute(self
, context
):
1360 nodes
, links
= get_nodes_links(context
)
1365 if node
.select
== True:
1366 selection
.append(node
.name
)
1372 temp_deleted_nodes
= []
1373 del_unused_iterations
= len(nodes
)
1374 for it
in range(0, del_unused_iterations
):
1375 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1377 if self
.is_unused_node(node
):
1379 deleted_nodes
.append(node
.name
)
1380 bpy
.ops
.node
.delete()
1382 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1385 if self
.delete_frames
:
1393 frames_in_use
.append(node
.parent
)
1395 if node
.type == 'FRAME' and node
not in frames_in_use
:
1398 repeat
= True # repeat for nested frames
1400 if node
not in frames_in_use
:
1402 deleted_nodes
.append(node
.name
)
1403 bpy
.ops
.node
.delete()
1405 if self
.delete_muted
:
1409 deleted_nodes
.append(node
.name
)
1410 bpy
.ops
.node
.delete_reconnect()
1412 # get unique list of deleted nodes (iterations would count the same node more than once)
1413 deleted_nodes
= list(set(deleted_nodes
))
1414 for n
in deleted_nodes
:
1415 self
.report({'INFO'}, "Node " + n
+ " deleted")
1416 num_deleted
= len(deleted_nodes
)
1421 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1423 self
.report({'INFO'}, "Nothing deleted")
1426 nodes
, links
= get_nodes_links(context
)
1428 if node
.name
in selection
:
1432 def invoke(self
, context
, event
):
1433 return context
.window_manager
.invoke_confirm(self
, event
)
1436 class NWSwapLinks(Operator
, NWBase
):
1437 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1438 bl_idname
= 'node.nw_swap_links'
1439 bl_label
= 'Swap Links'
1440 bl_options
= {'REGISTER', 'UNDO'}
1443 def poll(cls
, context
):
1445 if nw_check(context
):
1446 if context
.selected_nodes
:
1447 valid
= len(context
.selected_nodes
) <= 2
1450 def execute(self
, context
):
1451 nodes
, links
= get_nodes_links(context
)
1452 selected_nodes
= context
.selected_nodes
1453 n1
= selected_nodes
[0]
1456 if len(selected_nodes
) == 2:
1457 n2
= selected_nodes
[1]
1458 if n1
.outputs
and n2
.outputs
:
1463 for output
in n1
.outputs
:
1465 for link
in output
.links
:
1466 n1_outputs
.append([out_index
, link
.to_socket
])
1471 for output
in n2
.outputs
:
1473 for link
in output
.links
:
1474 n2_outputs
.append([out_index
, link
.to_socket
])
1478 for connection
in n1_outputs
:
1480 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1482 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1483 for connection
in n2_outputs
:
1485 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1487 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1489 if n1
.outputs
or n2
.outputs
:
1490 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1492 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1495 elif len(selected_nodes
) == 1:
1499 for i1
in n1
.inputs
:
1502 for i2
in n1
.inputs
:
1503 if i1
.type == i2
.type and i2
.is_linked
:
1505 types
.append ([i1
, similar_types
, i
])
1507 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1512 for i2
in n1
.inputs
:
1513 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1515 i1f
= pair
[0].links
[0].from_socket
1516 i1t
= pair
[0].links
[0].to_socket
1517 i2f
= pair
[1].links
[0].from_socket
1518 i2t
= pair
[1].links
[0].to_socket
1523 fs
= t
[0].links
[0].from_socket
1525 links
.remove(t
[0].links
[0])
1526 if i
+1 == len(n1
.inputs
):
1529 while n1
.inputs
[i
].is_linked
:
1531 links
.new(fs
, n1
.inputs
[i
])
1532 elif len(types
) == 2:
1533 i1f
= types
[0][0].links
[0].from_socket
1534 i1t
= types
[0][0].links
[0].to_socket
1535 i2f
= types
[1][0].links
[0].from_socket
1536 i2t
= types
[1][0].links
[0].to_socket
1541 self
.report({'WARNING'}, "This node has no input connections to swap!")
1543 self
.report({'WARNING'}, "This node has no inputs to swap!")
1545 force_update(context
)
1549 class NWResetBG(Operator
, NWBase
):
1550 """Reset the zoom and position of the background image"""
1551 bl_idname
= 'node.nw_bg_reset'
1552 bl_label
= 'Reset Backdrop'
1553 bl_options
= {'REGISTER', 'UNDO'}
1556 def poll(cls
, context
):
1558 if nw_check(context
):
1559 snode
= context
.space_data
1560 valid
= snode
.tree_type
== 'CompositorNodeTree'
1563 def execute(self
, context
):
1564 context
.space_data
.backdrop_zoom
= 1
1565 context
.space_data
.backdrop_x
= 0
1566 context
.space_data
.backdrop_y
= 0
1570 class NWAddAttrNode(Operator
, NWBase
):
1571 """Add an Attribute node with this name"""
1572 bl_idname
= 'node.nw_add_attr_node'
1573 bl_label
= 'Add UV map'
1574 attr_name
= StringProperty()
1575 bl_options
= {'REGISTER', 'UNDO'}
1577 def execute(self
, context
):
1578 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1579 nodes
, links
= get_nodes_links(context
)
1580 nodes
.active
.attribute_name
= self
.attr_name
1584 class NWEmissionViewer(Operator
, NWBase
):
1585 bl_idname
= "node.nw_emission_viewer"
1586 bl_label
= "Emission Viewer"
1587 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1588 bl_options
= {'REGISTER', 'UNDO'}
1591 def poll(cls
, context
):
1592 is_cycles
= is_cycles_or_eevee(context
)
1593 if nw_check(context
):
1594 space
= context
.space_data
1595 if space
.tree_type
== 'ShaderNodeTree' and is_cycles
:
1596 if context
.active_node
:
1597 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1603 def invoke(self
, context
, event
):
1604 space
= context
.space_data
1605 shader_type
= space
.shader_type
1606 if shader_type
== 'OBJECT':
1607 if space
.id not in [light
for light
in bpy
.data
.lights
]: # cannot use bpy.data.lights directly as iterable
1608 shader_output_type
= "OUTPUT_MATERIAL"
1609 shader_output_ident
= "ShaderNodeOutputMaterial"
1610 shader_viewer_ident
= "ShaderNodeEmission"
1612 shader_output_type
= "OUTPUT_LIGHT"
1613 shader_output_ident
= "ShaderNodeOutputLight"
1614 shader_viewer_ident
= "ShaderNodeEmission"
1616 elif shader_type
== 'WORLD':
1617 shader_output_type
= "OUTPUT_WORLD"
1618 shader_output_ident
= "ShaderNodeOutputWorld"
1619 shader_viewer_ident
= "ShaderNodeBackground"
1620 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1621 mlocx
= event
.mouse_region_x
1622 mlocy
= event
.mouse_region_y
1623 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1624 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1625 nodes
, links
= get_nodes_links(context
)
1626 in_group
= context
.active_node
!= space
.node_tree
.nodes
.active
1627 active
= nodes
.active
1628 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1631 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1632 for out
in active
.outputs
:
1637 # get material_output node, store selection, deselect all
1638 materialout
= None # placeholder node
1641 if node
.type == shader_output_type
:
1644 selection
.append(node
.name
)
1647 # get right-most location
1648 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1649 max_xloc_node
= sorted_by_xloc
[-1]
1650 if max_xloc_node
.name
== 'Emission Viewer':
1651 max_xloc_node
= sorted_by_xloc
[-2]
1653 # get average y location
1656 sum_yloc
+= node
.location
.y
1658 new_locx
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1659 new_locy
= sum_yloc
/ len(nodes
)
1661 materialout
= nodes
.new(shader_output_ident
)
1662 materialout
.location
.x
= new_locx
1663 materialout
.location
.y
= new_locy
1664 materialout
.select
= False
1665 # Analyze outputs, add "Emission Viewer" if needed, make links
1668 for i
, out
in enumerate(active
.outputs
):
1670 valid_outputs
.append(i
)
1672 out_i
= valid_outputs
[0] # Start index of node's outputs
1673 for i
, valid_i
in enumerate(valid_outputs
):
1674 for out_link
in active
.outputs
[valid_i
].links
:
1675 if "Emission Viewer" in out_link
.to_node
.name
or (out_link
.to_node
== materialout
and out_link
.to_socket
== materialout
.inputs
[0]):
1676 if i
< len(valid_outputs
) - 1:
1677 out_i
= valid_outputs
[i
+ 1]
1679 out_i
= valid_outputs
[0]
1680 make_links
= [] # store sockets for new links
1682 # If output type not 'SHADER' - "Emission Viewer" needed
1683 if active
.outputs
[out_i
].type != 'SHADER':
1684 # get Emission Viewer node
1685 emission_exists
= False
1686 emission_placeholder
= nodes
[0]
1688 if "Emission Viewer" in node
.name
:
1689 emission_exists
= True
1690 emission_placeholder
= node
1691 if not emission_exists
:
1692 emission
= nodes
.new(shader_viewer_ident
)
1693 emission
.hide
= True
1694 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1695 emission
.label
= "Viewer"
1696 emission
.name
= "Emission Viewer"
1697 emission
.use_custom_color
= True
1698 emission
.color
= (0.6, 0.5, 0.4)
1699 emission
.select
= False
1701 emission
= emission_placeholder
1702 make_links
.append((active
.outputs
[out_i
], emission
.inputs
[0]))
1704 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1705 if emission
.outputs
[0].links
.__len
__() > 0:
1706 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1707 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1709 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1711 # Set brightness of viewer to compensate for Film and CM exposure
1712 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1713 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1714 emission
.inputs
[1].default_value
= intensity
1717 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1718 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[1 if active
.outputs
[out_i
].name
== "Volume" else 0]))
1720 if node
.name
== 'Emission Viewer':
1722 bpy
.ops
.node
.delete()
1723 for li_from
, li_to
in make_links
:
1724 links
.new(li_from
, li_to
)
1726 nodes
.active
= active
1728 if node
.name
in selection
:
1730 force_update(context
)
1733 return {'CANCELLED'}
1736 class NWFrameSelected(Operator
, NWBase
):
1737 bl_idname
= "node.nw_frame_selected"
1738 bl_label
= "Frame Selected"
1739 bl_description
= "Add a frame node and parent the selected nodes to it"
1740 bl_options
= {'REGISTER', 'UNDO'}
1741 label_prop
= StringProperty(name
='Label', default
=' ', description
='The visual name of the frame node')
1742 color_prop
= FloatVectorProperty(name
="Color", description
="The color of the frame node", default
=(0.6, 0.6, 0.6),
1743 min=0, max=1, step
=1, precision
=3, subtype
='COLOR_GAMMA', size
=3)
1745 def execute(self
, context
):
1746 nodes
, links
= get_nodes_links(context
)
1749 if node
.select
== True:
1750 selected
.append(node
)
1752 bpy
.ops
.node
.add_node(type='NodeFrame')
1754 frm
.label
= self
.label_prop
1755 frm
.use_custom_color
= True
1756 frm
.color
= self
.color_prop
1758 for node
in selected
:
1764 class NWReloadImages(Operator
, NWBase
):
1765 bl_idname
= "node.nw_reload_images"
1766 bl_label
= "Reload Images"
1767 bl_description
= "Update all the image nodes to match their files on disk"
1769 def execute(self
, context
):
1770 nodes
, links
= get_nodes_links(context
)
1771 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1774 if node
.type in image_types
:
1775 if node
.type == "TEXTURE":
1776 if node
.texture
: # node has texture assigned
1777 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1778 if node
.texture
.image
: # texture has image assigned
1779 node
.texture
.image
.reload()
1787 self
.report({'INFO'}, "Reloaded images")
1788 print("Reloaded " + str(num_reloaded
) + " images")
1789 force_update(context
)
1792 self
.report({'WARNING'}, "No images found to reload in this node tree")
1793 return {'CANCELLED'}
1796 class NWSwitchNodeType(Operator
, NWBase
):
1797 """Switch type of selected nodes """
1798 bl_idname
= "node.nw_swtch_node_type"
1799 bl_label
= "Switch Node Type"
1800 bl_options
= {'REGISTER', 'UNDO'}
1802 to_type
: EnumProperty(
1803 name
="Switch to type",
1804 items
=list(shaders_input_nodes_props
) +
1805 list(shaders_output_nodes_props
) +
1806 list(shaders_shader_nodes_props
) +
1807 list(shaders_texture_nodes_props
) +
1808 list(shaders_color_nodes_props
) +
1809 list(shaders_vector_nodes_props
) +
1810 list(shaders_converter_nodes_props
) +
1811 list(shaders_layout_nodes_props
) +
1812 list(compo_input_nodes_props
) +
1813 list(compo_output_nodes_props
) +
1814 list(compo_color_nodes_props
) +
1815 list(compo_converter_nodes_props
) +
1816 list(compo_filter_nodes_props
) +
1817 list(compo_vector_nodes_props
) +
1818 list(compo_matte_nodes_props
) +
1819 list(compo_distort_nodes_props
) +
1820 list(compo_layout_nodes_props
) +
1821 list(blender_mat_input_nodes_props
) +
1822 list(blender_mat_output_nodes_props
) +
1823 list(blender_mat_color_nodes_props
) +
1824 list(blender_mat_vector_nodes_props
) +
1825 list(blender_mat_converter_nodes_props
) +
1826 list(blender_mat_layout_nodes_props
) +
1827 list(texture_input_nodes_props
) +
1828 list(texture_output_nodes_props
) +
1829 list(texture_color_nodes_props
) +
1830 list(texture_pattern_nodes_props
) +
1831 list(texture_textures_nodes_props
) +
1832 list(texture_converter_nodes_props
) +
1833 list(texture_distort_nodes_props
) +
1834 list(texture_layout_nodes_props
)
1837 def execute(self
, context
):
1838 nodes
, links
= get_nodes_links(context
)
1839 to_type
= self
.to_type
1840 # Those types of nodes will not swap.
1841 src_excludes
= ('NodeFrame')
1842 # Those attributes of nodes will be copied if possible
1843 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1844 'show_options', 'show_preview', 'show_texture',
1845 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1847 selected
= [n
for n
in nodes
if n
.select
]
1849 for node
in [n
for n
in selected
if
1850 n
.rna_type
.identifier
not in src_excludes
and
1851 n
.rna_type
.identifier
!= to_type
]:
1852 new_node
= nodes
.new(to_type
)
1853 for attr
in attrs_to_pass
:
1854 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1855 setattr(new_node
, attr
, getattr(node
, attr
))
1856 # set image datablock of dst to image of src
1857 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1859 new_node
.image
= node
.image
1861 if new_node
.type == 'SWITCH':
1862 new_node
.hide
= True
1863 # Dictionaries: src_sockets and dst_sockets:
1864 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1865 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1866 # in 'INPUTS' and 'OUTPUTS':
1867 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1869 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1871 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1872 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1875 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1876 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1878 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1879 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1880 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1881 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1882 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1883 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1884 # enumerate in inputs, then in outputs
1885 # find name, default value and links of socket
1886 for i
, socket
in enumerate(in_out
):
1887 the_name
= socket
.name
1889 # Not every socket, especially in outputs has "default_value"
1890 if hasattr(socket
, 'default_value'):
1891 dval
= socket
.default_value
1893 for lnk
in socket
.links
:
1894 socket_links
.append(lnk
)
1895 # check type of socket to fill proper keys.
1896 for the_type
in types_order_one
:
1897 if socket
.type == the_type
:
1898 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1899 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1900 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1901 # Check which of the types in inputs/outputs is considered to be "main".
1902 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1903 for type_check
in types_order_one
:
1904 if sockets
[in_out_name
][type_check
]:
1905 sockets
[in_out_name
]['MAIN'] = type_check
1909 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1910 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1913 for inout
, soctype
in (
1914 ('INPUTS', 'MAIN',),
1915 ('INPUTS', 'SHADER',),
1916 ('INPUTS', 'RGBA',),
1917 ('INPUTS', 'VECTOR',),
1918 ('INPUTS', 'VALUE',),
1919 ('OUTPUTS', 'MAIN',),
1920 ('OUTPUTS', 'SHADER',),
1921 ('OUTPUTS', 'RGBA',),
1922 ('OUTPUTS', 'VECTOR',),
1923 ('OUTPUTS', 'VALUE',),
1925 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1926 if soctype
== 'MAIN':
1927 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1928 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1930 sc
= src_sockets
[inout
][soctype
]
1931 dt
= dst_sockets
[inout
][soctype
]
1932 # start with 'dt' to determine number of possibilities.
1933 for i
, soc
in enumerate(dt
):
1934 # if src main has enough entries - match them with dst main sockets by indexes.
1936 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1937 # add 'VALUE_NAME' criterion to inputs.
1938 if inout
== 'INPUTS' and soctype
== 'VALUE':
1940 if s
[2] == soc
[2]: # if names match
1941 # append src (index, dval), dst (index, dval)
1942 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1944 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1945 # This creates better links when relinking textures.
1946 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1947 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1949 # Pass default values and RELINK:
1950 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1951 # INPUTS: Base on matches in proper order.
1952 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1954 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1955 new_node
.inputs
[dst_i
].default_value
= src_dval
1956 # Special case: switch to math
1957 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1958 new_node
.type == 'MATH' and\
1960 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1961 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1962 if node
.type == 'MIX_RGB':
1963 if node
.blend_type
in [o
[0] for o
in operations
]:
1964 new_node
.operation
= node
.blend_type
1965 # Special case: switch from math to some types
1966 if node
.type == 'MATH' and\
1967 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1970 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1971 if new_node
.type == 'MIX_RGB':
1972 if node
.operation
in [t
[0] for t
in blend_types
]:
1973 new_node
.blend_type
= node
.operation
1974 # Set Fac of MIX_RGB to 1.0
1975 new_node
.inputs
[0].default_value
= 1.0
1976 # make link only when dst matching input is not linked already.
1977 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1978 in_src_link
= node
.inputs
[src_i
].links
[0]
1979 in_dst_socket
= new_node
.inputs
[dst_i
]
1980 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1981 links
.remove(in_src_link
)
1982 # OUTPUTS: Base on matches in proper order.
1983 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1984 for out_src_link
in node
.outputs
[src_i
].links
:
1985 out_dst_socket
= new_node
.outputs
[dst_i
]
1986 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1987 # relink rest inputs if possible, no criteria
1988 for src_inp
in node
.inputs
:
1989 for dst_inp
in new_node
.inputs
:
1990 if src_inp
.links
and not dst_inp
.links
:
1991 src_link
= src_inp
.links
[0]
1992 links
.new(src_link
.from_socket
, dst_inp
)
1993 links
.remove(src_link
)
1994 # relink rest outputs if possible, base on node kind if any left.
1995 for src_o
in node
.outputs
:
1996 for out_src_link
in src_o
.links
:
1997 for dst_o
in new_node
.outputs
:
1998 if src_o
.type == dst_o
.type:
1999 links
.new(dst_o
, out_src_link
.to_socket
)
2000 # relink rest outputs no criteria if any left. Link all from first output.
2001 for src_o
in node
.outputs
:
2002 for out_src_link
in src_o
.links
:
2003 if new_node
.outputs
:
2004 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
2006 force_update(context
)
2010 class NWMergeNodes(Operator
, NWBase
):
2011 bl_idname
= "node.nw_merge_nodes"
2012 bl_label
= "Merge Nodes"
2013 bl_description
= "Merge Selected Nodes"
2014 bl_options
= {'REGISTER', 'UNDO'}
2018 description
="All possible blend types and math operations",
2019 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2021 merge_type
: EnumProperty(
2023 description
="Type of Merge to be used",
2025 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2026 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2027 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2028 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2029 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2030 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2034 def execute(self
, context
):
2035 settings
= context
.user_preferences
.addons
[__name__
].preferences
2036 merge_hide
= settings
.merge_hide
2037 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2040 do_hide_shader
= False
2041 if merge_hide
== 'ALWAYS':
2043 do_hide_shader
= True
2044 elif merge_hide
== 'NON_SHADER':
2047 tree_type
= context
.space_data
.node_tree
.type
2048 if tree_type
== 'COMPOSITING':
2049 node_type
= 'CompositorNode'
2050 elif tree_type
== 'SHADER':
2051 node_type
= 'ShaderNode'
2052 elif tree_type
== 'TEXTURE':
2053 node_type
= 'TextureNode'
2054 nodes
, links
= get_nodes_links(context
)
2056 merge_type
= self
.merge_type
2057 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2058 # 'ZCOMBINE' works only if mode == 'MIX'
2059 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2060 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2063 selected_mix
= [] # entry = [index, loc]
2064 selected_shader
= [] # entry = [index, loc]
2065 selected_math
= [] # entry = [index, loc]
2066 selected_z
= [] # entry = [index, loc]
2067 selected_alphaover
= [] # entry = [index, loc]
2069 for i
, node
in enumerate(nodes
):
2070 if node
.select
and node
.outputs
:
2071 if merge_type
== 'AUTO':
2072 for (type, types_list
, dst
) in (
2073 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2074 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2075 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2077 output_type
= node
.outputs
[0].type
2078 valid_mode
= mode
in types_list
2079 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2080 # Cheat that output type is 'RGBA',
2081 # and that 'MIX' exists in math operations list.
2082 # This way when selected_mix list is analyzed:
2083 # Node data will be appended even though it doesn't meet requirements.
2084 if output_type
!= 'SHADER' and mode
== 'MIX':
2085 output_type
= 'RGBA'
2087 if output_type
== type and valid_mode
:
2088 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2090 for (type, types_list
, dst
) in (
2091 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2092 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2093 ('MATH', [t
[0] for t
in operations
], selected_math
),
2094 ('ZCOMBINE', ('MIX', ), selected_z
),
2095 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2097 if merge_type
== type and mode
in types_list
:
2098 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2099 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2100 # use only 'Mix' nodes for merging.
2101 # For that we add selected_math list to selected_mix list and clear selected_math.
2102 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2103 selected_mix
+= selected_math
2106 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2108 count_before
= len(nodes
)
2109 # sort list by loc_x - reversed
2110 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2112 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2113 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2114 if merge_position
== 'CENTER':
2115 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)
2116 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2122 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2126 if nodes_list
== selected_shader
and not do_hide_shader
:
2128 the_range
= len(nodes_list
) - 1
2129 if len(nodes_list
) == 1:
2131 for i
in range(the_range
):
2132 if nodes_list
== selected_mix
:
2133 add_type
= node_type
+ 'MixRGB'
2134 add
= nodes
.new(add_type
)
2135 add
.blend_type
= mode
2137 add
.inputs
[0].default_value
= 1.0
2138 add
.show_preview
= False
2144 add
.width_hidden
= 100.0
2145 elif nodes_list
== selected_math
:
2146 add_type
= node_type
+ 'Math'
2147 add
= nodes
.new(add_type
)
2148 add
.operation
= mode
2154 add
.width_hidden
= 100.0
2155 elif nodes_list
== selected_shader
:
2157 add_type
= node_type
+ 'MixShader'
2158 add
= nodes
.new(add_type
)
2159 add
.hide
= do_hide_shader
2164 add
.width_hidden
= 100.0
2166 add_type
= node_type
+ 'AddShader'
2167 add
= nodes
.new(add_type
)
2168 add
.hide
= do_hide_shader
2173 add
.width_hidden
= 100.0
2174 elif nodes_list
== selected_z
:
2175 add
= nodes
.new('CompositorNodeZcombine')
2176 add
.show_preview
= False
2182 add
.width_hidden
= 100.0
2183 elif nodes_list
== selected_alphaover
:
2184 add
= nodes
.new('CompositorNodeAlphaOver')
2185 add
.show_preview
= False
2191 add
.width_hidden
= 100.0
2192 add
.location
= loc_x
, loc_y
2196 count_after
= len(nodes
)
2197 index
= count_after
- 1
2198 first_selected
= nodes
[nodes_list
[0][0]]
2199 # "last" node has been added as first, so its index is count_before.
2200 last_add
= nodes
[count_before
]
2202 # Two nodes were selected and first selected has no output links, second selected has output links.
2203 # Then add links from last add to all links 'to_socket' of out links of second selected.
2204 if len(nodes_list
) == 2:
2205 if not first_selected
.outputs
[0].links
:
2206 second_selected
= nodes
[nodes_list
[1][0]]
2207 for ss_link
in second_selected
.outputs
[0].links
:
2208 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2209 # Create list of invalid indexes.
2210 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2211 # Link only if "to_node" index not in invalid indexes list.
2212 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2213 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2214 # add links from last_add to all links 'to_socket' of out links of first selected.
2215 for fs_link
in first_selected
.outputs
[0].links
:
2216 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2217 # Create list of invalid indexes.
2218 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2219 # Link only if "to_node" index not in invalid indexes list.
2220 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2221 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2222 # add link from "first" selected and "first" add node
2223 node_to
= nodes
[count_after
- 1]
2224 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2225 if node_to
.type == 'ZCOMBINE':
2226 for fs_out
in first_selected
.outputs
:
2227 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2228 links
.new(fs_out
, node_to
.inputs
[1])
2230 # add links between added ADD nodes and between selected and ADD nodes
2231 for i
in range(count_adds
):
2232 if i
< count_adds
- 1:
2233 node_from
= nodes
[index
]
2234 node_to
= nodes
[index
- 1]
2235 node_to_input_i
= first
2236 node_to_z_i
= 1 # if z combine - link z to first z input
2237 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2238 if node_to
.type == 'ZCOMBINE':
2239 for from_out
in node_from
.outputs
:
2240 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2241 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2242 if len(nodes_list
) > 1:
2243 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2244 node_to
= nodes
[index
]
2245 node_to_input_i
= second
2246 node_to_z_i
= 3 # if z combine - link z to second z input
2247 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2248 if node_to
.type == 'ZCOMBINE':
2249 for from_out
in node_from
.outputs
:
2250 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2251 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2253 # set "last" of added nodes as active
2254 nodes
.active
= last_add
2255 for i
, x
, y
, dx
, h
in nodes_list
:
2256 nodes
[i
].select
= False
2261 class NWBatchChangeNodes(Operator
, NWBase
):
2262 bl_idname
= "node.nw_batch_change"
2263 bl_label
= "Batch Change"
2264 bl_description
= "Batch Change Blend Type and Math Operation"
2265 bl_options
= {'REGISTER', 'UNDO'}
2267 blend_type
: EnumProperty(
2269 items
=blend_types
+ navs
,
2271 operation
: EnumProperty(
2273 items
=operations
+ navs
,
2276 def execute(self
, context
):
2278 nodes
, links
= get_nodes_links(context
)
2279 blend_type
= self
.blend_type
2280 operation
= self
.operation
2281 for node
in context
.selected_nodes
:
2282 if node
.type == 'MIX_RGB':
2283 if not blend_type
in [nav
[0] for nav
in navs
]:
2284 node
.blend_type
= blend_type
2286 if blend_type
== 'NEXT':
2287 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2288 #index = blend_types.index(node.blend_type)
2289 if index
== len(blend_types
) - 1:
2290 node
.blend_type
= blend_types
[0][0]
2292 node
.blend_type
= blend_types
[index
+ 1][0]
2294 if blend_type
== 'PREV':
2295 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2297 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2299 node
.blend_type
= blend_types
[index
- 1][0]
2301 if node
.type == 'MATH':
2302 if not operation
in [nav
[0] for nav
in navs
]:
2303 node
.operation
= operation
2305 if operation
== 'NEXT':
2306 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2307 #index = operations.index(node.operation)
2308 if index
== len(operations
) - 1:
2309 node
.operation
= operations
[0][0]
2311 node
.operation
= operations
[index
+ 1][0]
2313 if operation
== 'PREV':
2314 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2315 #index = operations.index(node.operation)
2317 node
.operation
= operations
[len(operations
) - 1][0]
2319 node
.operation
= operations
[index
- 1][0]
2324 class NWChangeMixFactor(Operator
, NWBase
):
2325 bl_idname
= "node.nw_factor"
2326 bl_label
= "Change Factor"
2327 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2328 bl_options
= {'REGISTER', 'UNDO'}
2330 # option: Change factor.
2331 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2332 # Else - change factor by option value.
2333 option
: FloatProperty()
2335 def execute(self
, context
):
2336 nodes
, links
= get_nodes_links(context
)
2337 option
= self
.option
2338 selected
= [] # entry = index
2339 for si
, node
in enumerate(nodes
):
2341 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2345 fac
= nodes
[si
].inputs
[0]
2346 nodes
[si
].hide
= False
2347 if option
in {0.0, 1.0}:
2348 fac
.default_value
= option
2350 fac
.default_value
+= option
2355 class NWCopySettings(Operator
, NWBase
):
2356 bl_idname
= "node.nw_copy_settings"
2357 bl_label
= "Copy Settings"
2358 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2359 bl_options
= {'REGISTER', 'UNDO'}
2362 def poll(cls
, context
):
2364 if nw_check(context
):
2365 if context
.active_node
is not None and context
.active_node
.type is not 'FRAME':
2369 def execute(self
, context
):
2370 node_active
= context
.active_node
2371 node_selected
= context
.selected_nodes
2374 if not (len(node_selected
) > 1):
2375 self
.report({'ERROR'}, "2 nodes must be selected at least")
2376 return {'CANCELLED'}
2378 # Check if active node is in the selection
2379 selected_node_names
= [n
.name
for n
in node_selected
]
2380 if node_active
.name
not in selected_node_names
:
2381 self
.report({'ERROR'}, "No active node")
2382 return {'CANCELLED'}
2384 # Get nodes in selection by type
2385 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2387 if not (len(valid_nodes
) > 1) and node_active
:
2388 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2389 return {'CANCELLED'}
2391 if len(valid_nodes
) != len(node_selected
):
2392 # Report nodes that are not valid
2393 valid_node_names
= [n
.name
for n
in valid_nodes
]
2394 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2395 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2397 # Reference original
2399 #node_selected_names = [n.name for n in node_selected]
2404 # Deselect all nodes
2405 for i
in node_selected
:
2408 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2409 # Run through all other nodes
2410 for node
in valid_nodes
[1:]:
2412 # Check for frame node
2413 parent
= node
.parent
if node
.parent
else None
2414 node_loc
= [node
.location
.x
, node
.location
.y
]
2416 # Select original to duplicate
2419 # Duplicate selected node
2420 bpy
.ops
.node
.duplicate()
2421 new_node
= context
.selected_nodes
[0]
2424 new_node
.select
= False
2426 # Properties to copy
2427 node_tree
= node
.id_data
2428 props_to_copy
= 'bl_idname name location height width'.split(' ')
2432 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2433 for i
in (i
for i
in mappings
if i
.is_linked
):
2435 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2438 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2439 props_to_copy
.pop(0)
2441 for prop
in props_to_copy
:
2442 setattr(new_node
, prop
, props
[prop
])
2444 # Get the node tree to remove the old node
2445 nodes
= node_tree
.nodes
2447 new_node
.name
= props
['name']
2450 new_node
.parent
= parent
2451 new_node
.location
= node_loc
2453 for str_from
, str_to
in reconnections
:
2454 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2456 success_names
.append(new_node
.name
)
2459 node_tree
.nodes
.active
= orig
2460 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2464 class NWCopyLabel(Operator
, NWBase
):
2465 bl_idname
= "node.nw_copy_label"
2466 bl_label
= "Copy Label"
2467 bl_options
= {'REGISTER', 'UNDO'}
2469 option
: EnumProperty(
2471 description
="Source of name of label",
2473 ('FROM_ACTIVE', 'from active', 'from active node',),
2474 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2475 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2479 def execute(self
, context
):
2480 nodes
, links
= get_nodes_links(context
)
2481 option
= self
.option
2482 active
= nodes
.active
2483 if option
== 'FROM_ACTIVE':
2485 src_label
= active
.label
2486 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2487 node
.label
= src_label
2488 elif option
== 'FROM_NODE':
2489 selected
= [n
for n
in nodes
if n
.select
]
2490 for node
in selected
:
2491 for input in node
.inputs
:
2493 src
= input.links
[0].from_node
2494 node
.label
= src
.label
2496 elif option
== 'FROM_SOCKET':
2497 selected
= [n
for n
in nodes
if n
.select
]
2498 for node
in selected
:
2499 for input in node
.inputs
:
2501 src
= input.links
[0].from_socket
2502 node
.label
= src
.name
2508 class NWClearLabel(Operator
, NWBase
):
2509 bl_idname
= "node.nw_clear_label"
2510 bl_label
= "Clear Label"
2511 bl_options
= {'REGISTER', 'UNDO'}
2513 option
: BoolProperty()
2515 def execute(self
, context
):
2516 nodes
, links
= get_nodes_links(context
)
2517 for node
in [n
for n
in nodes
if n
.select
]:
2522 def invoke(self
, context
, event
):
2524 return self
.execute(context
)
2526 return context
.window_manager
.invoke_confirm(self
, event
)
2529 class NWModifyLabels(Operator
, NWBase
):
2530 """Modify Labels of all selected nodes"""
2531 bl_idname
= "node.nw_modify_labels"
2532 bl_label
= "Modify Labels"
2533 bl_options
= {'REGISTER', 'UNDO'}
2535 prepend
= StringProperty(
2536 name
="Add to Beginning"
2538 append
= StringProperty(
2541 replace_from
= StringProperty(
2542 name
="Text to Replace"
2544 replace_to
= StringProperty(
2548 def execute(self
, context
):
2549 nodes
, links
= get_nodes_links(context
)
2550 for node
in [n
for n
in nodes
if n
.select
]:
2551 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2555 def invoke(self
, context
, event
):
2559 return context
.window_manager
.invoke_props_dialog(self
)
2562 class NWAddTextureSetup(Operator
, NWBase
):
2563 bl_idname
= "node.nw_add_texture"
2564 bl_label
= "Texture Setup"
2565 bl_description
= "Add Texture Node Setup to Selected Shaders"
2566 bl_options
= {'REGISTER', 'UNDO'}
2568 add_mapping
: BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2571 def poll(cls
, context
):
2573 if nw_check(context
):
2574 space
= context
.space_data
2575 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2579 def execute(self
, context
):
2580 nodes
, links
= get_nodes_links(context
)
2581 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2582 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2583 selected_nodes
= [n
for n
in nodes
if n
.select
]
2584 for t_node
in selected_nodes
:
2588 for index
, i
in enumerate(t_node
.inputs
):
2594 locx
= t_node
.location
.x
2595 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2597 xoffset
= [500, 700]
2599 if t_node
.type in texture_types
+ ['MAPPING']:
2600 xoffset
= [290, 500]
2604 image_type
= 'ShaderNodeTexImage'
2606 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2607 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2608 if t_node
.type == 'BACKGROUND':
2609 image_type
= 'ShaderNodeTexEnvironment'
2612 tex
= nodes
.new(image_type
)
2613 tex
.location
= [locx
- 200, locy
+ 112]
2615 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2617 t_node
.select
= False
2618 if self
.add_mapping
or is_texture
:
2619 if t_node
.type != 'MAPPING':
2620 m
= nodes
.new('ShaderNodeMapping')
2621 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2625 coord
= nodes
.new('ShaderNodeTexCoord')
2626 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2629 links
.new(m
.outputs
[0], tex
.inputs
[0])
2630 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2633 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2634 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2636 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2640 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2641 bl_idname
= "node.nw_add_textures_for_principled"
2642 bl_label
= "Principled Texture Setup"
2643 bl_description
= "Add Texture Node Setup for Principled BSDF"
2644 bl_options
= {'REGISTER', 'UNDO'}
2646 directory
= StringProperty(
2650 description
='Folder to search in for image files')
2651 files
= CollectionProperty(
2652 type=bpy
.types
.OperatorFileListElement
,
2653 options
={'HIDDEN', 'SKIP_SAVE'})
2661 def poll(cls
, context
):
2663 if nw_check(context
):
2664 space
= context
.space_data
2665 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2669 def execute(self
, context
):
2670 # Check if everything is ok
2671 if not self
.directory
:
2672 self
.report({'INFO'}, 'No Folder Selected')
2673 return {'CANCELLED'}
2674 if not self
.files
[:]:
2675 self
.report({'INFO'}, 'No Files Selected')
2676 return {'CANCELLED'}
2678 nodes
, links
= get_nodes_links(context
)
2679 active_node
= nodes
.active
2680 if not active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled':
2681 self
.report({'INFO'}, 'Select Principled BSDF')
2682 return {'CANCELLED'}
2685 def split_into__components(fname
):
2686 # Split filename into components
2687 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2689 fname
= path
.splitext(fname
)[0]
2691 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2692 # Seperate CamelCase by space
2693 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2694 # Replace common separators with SPACE
2695 seperators
= ['_', '.', '-', '__', '--', '#']
2696 for sep
in seperators
:
2697 fname
= fname
.replace(sep
, ' ')
2699 components
= fname
.split(' ')
2700 components
= [c
.lower() for c
in components
]
2703 # Filter textures names for texturetypes in filenames
2704 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2705 tags
= context
.user_preferences
.addons
[__name__
].preferences
.principled_tags
2706 normal_abbr
= tags
.normal
.split(' ')
2707 bump_abbr
= tags
.bump
.split(' ')
2708 gloss_abbr
= tags
.gloss
.split(' ')
2709 rough_abbr
= tags
.rough
.split(' ')
2711 ['Displacement', tags
.displacement
.split(' '), None],
2712 ['Base Color', tags
.base_color
.split(' '), None],
2713 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2714 ['Metallic', tags
.metallic
.split(' '), None],
2715 ['Specular', tags
.specular
.split(' '), None],
2716 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2717 ['Normal', normal_abbr
+ bump_abbr
, None],
2720 # Look through texture_types and set value as filename of first matched file
2721 def match_files_to_socket_names():
2722 for sname
in socketnames
:
2723 for file in self
.files
:
2725 filenamecomponents
= split_into__components(fname
)
2726 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
2727 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
2732 match_files_to_socket_names()
2733 # Remove socketnames without found files
2734 socketnames
= [s
for s
in socketnames
if s
[2]
2735 and path
.exists(self
.directory
+s
[2])]
2737 self
.report({'INFO'}, 'No matching images found')
2738 print('No matching images found')
2739 return {'CANCELLED'}
2742 print('\nMatched Textures:')
2746 roughness_node
= None
2747 for i
, sname
in enumerate(socketnames
):
2748 print(i
, sname
[0], sname
[2])
2750 # DISPLACEMENT NODES
2751 if sname
[0] == 'Displacement':
2752 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
2753 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2754 disp_texture
.image
= img
2755 disp_texture
.label
= 'Displacement'
2756 disp_texture
.color_space
= 'NONE'
2758 # Add displacement offset nodes
2759 math_sub
= nodes
.new(type='ShaderNodeMath')
2760 math_sub
.operation
= 'SUBTRACT'
2761 math_sub
.label
= 'Offset'
2762 math_sub
.location
= active_node
.location
+ Vector((0, -560))
2763 math_mul
= nodes
.new(type='ShaderNodeMath')
2764 math_mul
.operation
= 'MULTIPLY'
2765 math_mul
.label
= 'Strength'
2766 math_mul
.location
= math_sub
.location
+ Vector((200, 0))
2767 link
= links
.new(math_mul
.inputs
[0], math_sub
.outputs
[0])
2768 link
= links
.new(math_sub
.inputs
[0], disp_texture
.outputs
[0])
2770 # Turn on true displacement in the material
2771 # Too complicated for now
2774 # Frame. Does not update immediatly
2775 # Seems to need an editor redraw
2776 frame = nodes.new(type='NodeFrame')
2777 frame.label = 'Displacement'
2778 math_sub.parent = frame
2779 math_mul.parent = frame
2784 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
2786 if not output_node
[0].inputs
[2].is_linked
:
2787 link
= links
.new(output_node
[0].inputs
[2], math_mul
.outputs
[0])
2791 if not active_node
.inputs
[sname
[0]].is_linked
:
2792 # No texture node connected -> add texture node with new image
2793 texture_node
= nodes
.new(type='ShaderNodeTexImage')
2794 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2795 texture_node
.image
= img
2798 if sname
[0] == 'Normal':
2799 # Test if new texture node is normal or bump map
2800 fname_components
= split_into__components(sname
[2])
2801 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
2802 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
2804 # If Normal add normal node in between
2805 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
2806 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
2808 # If Bump add bump node in between
2809 normal_node
= nodes
.new(type='ShaderNodeBump')
2810 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
2812 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
2813 normal_node_texture
= texture_node
2815 elif sname
[0] == 'Roughness':
2816 # Test if glossy or roughness map
2817 fname_components
= split_into__components(sname
[2])
2818 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
2819 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
2822 # If Roughness nothing to to
2823 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2826 # If Gloss Map add invert node
2827 invert_node
= nodes
.new(type='ShaderNodeInvert')
2828 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
2830 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
2831 roughness_node
= texture_node
2834 # This is a simple connection Texture --> Input slot
2835 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2837 # Use non-color for all but 'Base Color' Textures
2838 if not sname
[0] in ['Base Color']:
2839 texture_node
.color_space
= 'NONE'
2842 # If already texture connected. add to node list for alignment
2843 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
2845 # This are all connected texture nodes
2846 texture_nodes
.append(texture_node
)
2847 texture_node
.label
= sname
[0]
2850 texture_nodes
.append(disp_texture
)
2853 for i
, texture_node
in enumerate(texture_nodes
):
2854 offset
= Vector((-400, (i
* -260) + 200))
2855 texture_node
.location
= active_node
.location
+ offset
2858 # Extra alignment if normal node was added
2859 normal_node
.location
= normal_node_texture
.location
+ Vector((200, 0))
2862 # Alignment of invert node if glossy map
2863 invert_node
.location
= roughness_node
.location
+ Vector((200, 0))
2865 # Add texture input + mapping
2866 mapping
= nodes
.new(type='ShaderNodeMapping')
2867 mapping
.location
= active_node
.location
+ Vector((-900, 0))
2868 if len(texture_nodes
) > 1:
2869 # If more than one texture add reroute node in between
2870 reroute
= nodes
.new(type='NodeReroute')
2871 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
2872 reroute
.location
= tex_coords
+ Vector((-50, -120))
2873 for texture_node
in texture_nodes
:
2874 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
2875 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
2877 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
2879 # Connect texture_coordiantes to mapping node
2880 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
2881 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
2882 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
2885 active_node
.select
= False
2888 force_update(context
)
2892 class NWAddReroutes(Operator
, NWBase
):
2893 """Add Reroute Nodes and link them to outputs of selected nodes"""
2894 bl_idname
= "node.nw_add_reroutes"
2895 bl_label
= "Add Reroutes"
2896 bl_description
= "Add Reroutes to Outputs"
2897 bl_options
= {'REGISTER', 'UNDO'}
2899 option
: EnumProperty(
2902 ('ALL', 'to all', 'Add to all outputs'),
2903 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2904 ('LINKED', 'to linked', 'Add only to linked outputs'),
2908 def execute(self
, context
):
2909 tree_type
= context
.space_data
.node_tree
.type
2910 option
= self
.option
2911 nodes
, links
= get_nodes_links(context
)
2912 # output valid when option is 'all' or when 'loose' output has no links
2914 post_select
= [] # nodes to be selected after execution
2915 # create reroutes and recreate links
2916 for node
in [n
for n
in nodes
if n
.select
]:
2921 # unhide 'REROUTE' nodes to avoid issues with location.y
2922 if node
.type == 'REROUTE':
2924 # When node is hidden - width_hidden not usable.
2925 # Hack needed to calculate real width
2927 bpy
.ops
.node
.select_all(action
='DESELECT')
2928 helper
= nodes
.new('NodeReroute')
2929 helper
.select
= True
2931 # resize node and helper to zero. Then check locations to calculate width
2932 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2933 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2934 # restore node location
2935 node
.location
= x
, y
2938 # only helper is selected now
2939 bpy
.ops
.node
.delete()
2940 x
= node
.location
.x
+ width
+ 20.0
2941 if node
.type != 'REROUTE':
2945 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2946 for out_i
, output
in enumerate(node
.outputs
):
2947 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2948 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2949 if node
.type != 'R_LAYERS':
2951 else: # if 'R_LAYERS' check if output represent used render pass
2952 node_scene
= node
.scene
2953 node_layer
= node
.layer
2954 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2955 if output
.name
== 'Alpha':
2958 # check entries in global 'rl_outputs' variable
2959 #for render_pass, output_name, exr_name, in_internal, in_cycles in rl_outputs:
2960 for rlo
in rl_outputs
:
2961 if output
.name
== rlo
.output_name
or output
.name
== rlo
.exr_output_name
:
2962 pass_used
= getattr(node_scene
.render
.layers
[node_layer
], rlo
.render_pass
)
2965 valid
= ((option
== 'ALL') or
2966 (option
== 'LOOSE' and not output
.links
) or
2967 (option
== 'LINKED' and output
.links
))
2968 # Add reroutes only if valid, but offset location in all cases.
2970 n
= nodes
.new('NodeReroute')
2972 for link
in output
.links
:
2973 links
.new(n
.outputs
[0], link
.to_socket
)
2974 links
.new(output
, n
.inputs
[0])
2976 post_select
.append(n
)
2980 # disselect the node so that after execution of script only newly created nodes are selected
2982 # nicer reroutes distribution along y when node.hide
2984 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2985 for reroute
in [r
for r
in nodes
if r
.select
]:
2986 reroute
.location
.y
-= y_translate
2987 for node
in post_select
:
2993 class NWLinkActiveToSelected(Operator
, NWBase
):
2994 """Link active node to selected nodes basing on various criteria"""
2995 bl_idname
= "node.nw_link_active_to_selected"
2996 bl_label
= "Link Active Node to Selected"
2997 bl_options
= {'REGISTER', 'UNDO'}
2999 replace
: BoolProperty()
3000 use_node_name
: BoolProperty()
3001 use_outputs_names
: BoolProperty()
3004 def poll(cls
, context
):
3006 if nw_check(context
):
3007 if context
.active_node
is not None:
3008 if context
.active_node
.select
:
3012 def execute(self
, context
):
3013 nodes
, links
= get_nodes_links(context
)
3014 replace
= self
.replace
3015 use_node_name
= self
.use_node_name
3016 use_outputs_names
= self
.use_outputs_names
3017 active
= nodes
.active
3018 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3019 outputs
= [] # Only usable outputs of active nodes will be stored here.
3020 for out
in active
.outputs
:
3021 if active
.type != 'R_LAYERS':
3024 # 'R_LAYERS' node type needs special handling.
3025 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3026 # Only outputs that represent used passes should be taken into account
3027 # Check if pass represented by output is used.
3028 # global 'rl_outputs' list will be used for that
3029 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3030 pass_used
= False # initial value. Will be set to True if pass is used
3031 if out
.name
== 'Alpha':
3032 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3034 elif out
.name
== out_name
:
3035 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3036 pass_used
= getattr(active
.scene
.render
.layers
[active
.layer
], render_pass
)
3040 doit
= True # Will be changed to False when links successfully added to previous output.
3043 for node
in selected
:
3044 dst_name
= node
.name
# Will be compared with src_name if needed.
3045 # When node has label - use it as dst_name
3047 dst_name
= node
.label
3048 valid
= True # Initial value. Will be changed to False if names don't match.
3049 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3051 # Set src_name to source node name or label
3052 src_name
= active
.name
3054 src_name
= active
.label
3055 elif use_outputs_names
:
3056 src_name
= (out
.name
, )
3057 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3058 if out
.name
in {out_name
, exr_name
}:
3059 src_name
= (out_name
, exr_name
)
3060 if dst_name
not in src_name
:
3063 for input in node
.inputs
:
3064 if input.type == out
.type or node
.type == 'REROUTE':
3065 if replace
or not input.is_linked
:
3066 links
.new(out
, input)
3067 if not use_node_name
and not use_outputs_names
:
3074 class NWAlignNodes(Operator
, NWBase
):
3075 '''Align the selected nodes neatly in a row/column'''
3076 bl_idname
= "node.nw_align_nodes"
3077 bl_label
= "Align Nodes"
3078 bl_options
= {'REGISTER', 'UNDO'}
3079 margin
: IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3081 def execute(self
, context
):
3082 nodes
, links
= get_nodes_links(context
)
3083 margin
= self
.margin
3087 if node
.select
and node
.type != 'FRAME':
3088 selection
.append(node
)
3090 # If no nodes are selected, align all nodes
3094 elif nodes
.active
in selection
:
3095 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3097 # Check if nodes should be layed out horizontally or vertically
3098 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3099 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3100 x_range
= max(x_locs
) - min(x_locs
)
3101 y_range
= max(y_locs
) - min(y_locs
)
3102 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3103 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3104 horizontal
= x_range
> y_range
3106 # Sort selection by location of node mid-point
3108 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3110 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3114 for node
in selection
:
3115 current_margin
= margin
3116 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3119 node
.location
.x
= current_pos
3120 current_pos
+= current_margin
+ node
.dimensions
.x
3121 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3123 node
.location
.y
= current_pos
3124 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3125 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3127 # If active node is selected, center nodes around it
3128 if active_loc
is not None:
3129 active_loc_diff
= active_loc
- nodes
.active
.location
3130 for node
in selection
:
3131 node
.location
+= active_loc_diff
3132 else: # Position nodes centered around where they used to be
3133 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
])
3134 new_mid
= (max(locs
) + min(locs
)) / 2
3135 for node
in selection
:
3137 node
.location
.x
+= (mid_x
- new_mid
)
3139 node
.location
.y
+= (mid_y
- new_mid
)
3144 class NWSelectParentChildren(Operator
, NWBase
):
3145 bl_idname
= "node.nw_select_parent_child"
3146 bl_label
= "Select Parent or Children"
3147 bl_options
= {'REGISTER', 'UNDO'}
3149 option
: EnumProperty(
3152 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3153 ('CHILD', 'Select Children', 'Select members of selected frame'),
3157 def execute(self
, context
):
3158 nodes
, links
= get_nodes_links(context
)
3159 option
= self
.option
3160 selected
= [node
for node
in nodes
if node
.select
]
3161 if option
== 'PARENT':
3162 for sel
in selected
:
3165 parent
.select
= True
3166 else: # option == 'CHILD'
3167 for sel
in selected
:
3168 children
= [node
for node
in nodes
if node
.parent
== sel
]
3169 for kid
in children
:
3175 class NWDetachOutputs(Operator
, NWBase
):
3176 """Detach outputs of selected node leaving inputs linked"""
3177 bl_idname
= "node.nw_detach_outputs"
3178 bl_label
= "Detach Outputs"
3179 bl_options
= {'REGISTER', 'UNDO'}
3181 def execute(self
, context
):
3182 nodes
, links
= get_nodes_links(context
)
3183 selected
= context
.selected_nodes
3184 bpy
.ops
.node
.duplicate_move_keep_inputs()
3185 new_nodes
= context
.selected_nodes
3186 bpy
.ops
.node
.select_all(action
="DESELECT")
3187 for node
in selected
:
3189 bpy
.ops
.node
.delete_reconnect()
3190 for new_node
in new_nodes
:
3191 new_node
.select
= True
3192 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3197 class NWLinkToOutputNode(Operator
, NWBase
):
3198 """Link to Composite node or Material Output node"""
3199 bl_idname
= "node.nw_link_out"
3200 bl_label
= "Connect to Output"
3201 bl_options
= {'REGISTER', 'UNDO'}
3204 def poll(cls
, context
):
3206 if nw_check(context
):
3207 if context
.active_node
is not None:
3208 for out
in context
.active_node
.outputs
:
3214 def execute(self
, context
):
3215 nodes
, links
= get_nodes_links(context
)
3216 active
= nodes
.active
3219 tree_type
= context
.space_data
.tree_type
3220 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3221 output_types_compo
= ['COMPOSITE']
3222 output_types_blender_mat
= ['OUTPUT']
3223 output_types_textures
= ['OUTPUT']
3224 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3226 if node
.type in output_types
:
3230 bpy
.ops
.node
.select_all(action
="DESELECT")
3231 if tree_type
== 'ShaderNodeTree':
3232 if is_cycles_or_eevee(context
):
3233 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3235 output_node
= nodes
.new('ShaderNodeOutput')
3236 elif tree_type
== 'CompositorNodeTree':
3237 output_node
= nodes
.new('CompositorNodeComposite')
3238 elif tree_type
== 'TextureNodeTree':
3239 output_node
= nodes
.new('TextureNodeOutput')
3240 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3241 output_node
.location
.y
= active
.location
.y
3242 if (output_node
and active
.outputs
):
3243 for i
, output
in enumerate(active
.outputs
):
3247 for i
, output
in enumerate(active
.outputs
):
3248 if output
.type == output_node
.inputs
[0].type and not output
.hide
:
3253 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3254 if active
.outputs
[output_index
].name
== 'Volume':
3256 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3258 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3260 force_update(context
) # viewport render does not update
3265 class NWMakeLink(Operator
, NWBase
):
3266 """Make a link from one socket to another"""
3267 bl_idname
= 'node.nw_make_link'
3268 bl_label
= 'Make Link'
3269 bl_options
= {'REGISTER', 'UNDO'}
3270 from_socket
: IntProperty()
3271 to_socket
: IntProperty()
3273 def execute(self
, context
):
3274 nodes
, links
= get_nodes_links(context
)
3276 n1
= nodes
[context
.scene
.NWLazySource
]
3277 n2
= nodes
[context
.scene
.NWLazyTarget
]
3279 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3281 force_update(context
)
3286 class NWCallInputsMenu(Operator
, NWBase
):
3287 """Link from this output"""
3288 bl_idname
= 'node.nw_call_inputs_menu'
3289 bl_label
= 'Make Link'
3290 bl_options
= {'REGISTER', 'UNDO'}
3291 from_socket
: IntProperty()
3293 def execute(self
, context
):
3294 nodes
, links
= get_nodes_links(context
)
3296 context
.scene
.NWSourceSocket
= self
.from_socket
3298 n1
= nodes
[context
.scene
.NWLazySource
]
3299 n2
= nodes
[context
.scene
.NWLazyTarget
]
3300 if len(n2
.inputs
) > 1:
3301 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3302 elif len(n2
.inputs
) == 1:
3303 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3307 class NWAddSequence(Operator
, ImportHelper
):
3308 """Add an Image Sequence"""
3309 bl_idname
= 'node.nw_add_sequence'
3310 bl_label
= 'Import Image Sequence'
3311 bl_options
= {'REGISTER', 'UNDO'}
3312 directory
= StringProperty(subtype
="DIR_PATH")
3313 filename
= StringProperty(subtype
="FILE_NAME")
3314 files
= CollectionProperty(type=bpy
.types
.OperatorFileListElement
, options
={'HIDDEN', 'SKIP_SAVE'})
3316 def execute(self
, context
):
3317 nodes
, links
= get_nodes_links(context
)
3318 directory
= self
.directory
3319 filename
= self
.filename
3321 tree
= context
.space_data
.node_tree
3324 # print ("\nDIR:", directory)
3325 # print ("FN:", filename)
3326 # print ("Fs:", list(f.name for f in files), '\n')
3328 if tree
.type == 'SHADER':
3329 node_type
= "ShaderNodeTexImage"
3330 elif tree
.type == 'COMPOSITING':
3331 node_type
= "CompositorNodeImage"
3333 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3334 return {'CANCELLED'}
3336 if not files
[0].name
and not filename
:
3337 self
.report({'ERROR'}, "No file chosen")
3338 return {'CANCELLED'}
3339 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3340 # User has selected multiple files without an active one, or the active one is non-existant
3341 filename
= files
[0].name
3343 if not path
.exists(directory
+filename
):
3344 self
.report({'ERROR'}, filename
+" does not exist!")
3345 return {'CANCELLED'}
3347 without_ext
= '.'.join(filename
.split('.')[:-1])
3349 # if last digit isn't a number, it's not a sequence
3350 if not without_ext
[-1].isdigit():
3351 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3352 return {'CANCELLED'}
3355 extension
= filename
.split('.')[-1]
3356 reverse
= without_ext
[::-1] # reverse string
3359 for char
in reverse
:
3365 without_num
= without_ext
[:count_numbers
*-1]
3367 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3369 num_frames
= len(files
)
3371 nodes_list
= [node
for node
in nodes
]
3373 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3374 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3378 yloc
+= node_mid_pt(node
, 'y')
3379 yloc
= yloc
/len(nodes
)
3384 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3386 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3388 node
.label
= name_with_hashes
3390 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3391 img
.source
= 'SEQUENCE'
3392 img
.name
= name_with_hashes
3394 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3395 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
3396 image_user
.frame_duration
= num_frames
3401 class NWAddMultipleImages(Operator
, ImportHelper
):
3402 """Add multiple images at once"""
3403 bl_idname
= 'node.nw_add_multiple_images'
3404 bl_label
= 'Open Selected Images'
3405 bl_options
= {'REGISTER', 'UNDO'}
3406 directory
= StringProperty(subtype
="DIR_PATH")
3407 files
= CollectionProperty(type=bpy
.types
.OperatorFileListElement
, options
={'HIDDEN', 'SKIP_SAVE'})
3409 def execute(self
, context
):
3410 nodes
, links
= get_nodes_links(context
)
3412 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3414 if context
.space_data
.node_tree
.type == 'SHADER':
3415 node_type
= "ShaderNodeTexImage"
3416 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3417 node_type
= "CompositorNodeImage"
3419 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3420 return {'CANCELLED'}
3423 for f
in self
.files
:
3426 node
= nodes
.new(node_type
)
3427 new_nodes
.append(node
)
3430 node
.width_hidden
= 100
3431 node
.location
.x
= xloc
3432 node
.location
.y
= yloc
3435 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3438 # shift new nodes up to center of tree
3439 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3441 if node
in new_nodes
:
3443 node
.location
.y
+= (list_size
/2)
3449 class NWViewerFocus(bpy
.types
.Operator
):
3450 """Set the viewer tile center to the mouse position"""
3451 bl_idname
= "node.nw_viewer_focus"
3452 bl_label
= "Viewer Focus"
3454 x
: bpy
.props
.IntProperty()
3455 y
: bpy
.props
.IntProperty()
3458 def poll(cls
, context
):
3459 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3461 def execute(self
, context
):
3464 def invoke(self
, context
, event
):
3465 render
= context
.scene
.render
3466 space
= context
.space_data
3467 percent
= render
.resolution_percentage
*0.01
3469 nodes
, links
= get_nodes_links(context
)
3470 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3473 mlocx
= event
.mouse_region_x
3474 mlocy
= event
.mouse_region_y
3475 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3477 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3478 region_x
= context
.region
.width
3479 region_y
= context
.region
.height
3481 region_center_x
= context
.region
.width
/ 2
3482 region_center_y
= context
.region
.height
/ 2
3484 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3485 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3487 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_x
3488 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_y
3490 margin_x
= region_center_x
- backdrop_center_x
3491 margin_y
= region_center_y
- backdrop_center_y
3493 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3494 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3496 for node
in viewers
:
3497 node
.center_x
= abs_mouse_x
3498 node
.center_y
= abs_mouse_y
3500 return {'PASS_THROUGH'}
3502 return self
.execute(context
)
3505 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3506 """Save the current viewer node to an image file"""
3507 bl_idname
= "node.nw_save_viewer"
3508 bl_label
= "Save This Image"
3509 filepath
: StringProperty(subtype
="FILE_PATH")
3510 filename_ext
: EnumProperty(
3512 description
="Choose the file format to save to",
3513 items
=(('.bmp', "PNG", ""),
3514 ('.rgb', 'IRIS', ""),
3515 ('.png', 'PNG', ""),
3516 ('.jpg', 'JPEG', ""),
3517 ('.jp2', 'JPEG2000', ""),
3518 ('.tga', 'TARGA', ""),
3519 ('.cin', 'CINEON', ""),
3520 ('.dpx', 'DPX', ""),
3521 ('.exr', 'OPEN_EXR', ""),
3522 ('.hdr', 'HDR', ""),
3523 ('.tif', 'TIFF', "")),
3528 def poll(cls
, context
):
3530 if nw_check(context
):
3531 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3532 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3533 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3537 def execute(self
, context
):
3554 basename
, ext
= path
.splitext(fp
)
3555 old_render_format
= context
.scene
.render
.image_settings
.file_format
3556 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3557 context
.area
.type = "IMAGE_EDITOR"
3558 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3559 context
.area
.spaces
[0].image
.save_render(fp
)
3560 context
.area
.type = "NODE_EDITOR"
3561 context
.scene
.render
.image_settings
.file_format
= old_render_format
3565 class NWResetNodes(bpy
.types
.Operator
):
3566 """Reset Nodes in Selection"""
3567 bl_idname
= "node.nw_reset_nodes"
3568 bl_label
= "Reset Nodes"
3569 bl_options
= {'REGISTER', 'UNDO'}
3572 def poll(cls
, context
):
3573 space
= context
.space_data
3574 return space
.type == 'NODE_EDITOR'
3576 def execute(self
, context
):
3577 node_active
= context
.active_node
3578 node_selected
= context
.selected_nodes
3579 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3581 # Check if one node is selected at least
3582 if not (len(node_selected
) > 0):
3583 self
.report({'ERROR'}, "1 node must be selected at least")
3584 return {'CANCELLED'}
3586 active_node_name
= node_active
.name
if node_active
.select
else None
3587 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3589 # Create output lists
3590 selected_node_names
= [n
.name
for n
in node_selected
]
3593 # Reset all valid children in a frame
3594 node_active_is_frame
= False
3595 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3596 node_tree
= node_active
.id_data
3597 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3599 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3600 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3601 node_active_is_frame
= True
3603 # Check if valid nodes in selection
3604 if not (len(valid_nodes
) > 0):
3605 # Check for frames only
3606 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3607 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3608 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3610 self
.report({'ERROR'}, "No valid node(s) in selection")
3611 return {'CANCELLED'}
3613 # Report nodes that are not valid
3614 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3615 valid_node_names
= [n
.name
for n
in valid_nodes
]
3616 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3617 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3619 # Deselect all nodes
3620 for i
in node_selected
:
3623 # Run through all valid nodes
3624 for node
in valid_nodes
:
3626 parent
= node
.parent
if node
.parent
else None
3627 node_loc
= [node
.location
.x
, node
.location
.y
]
3629 node_tree
= node
.id_data
3630 props_to_copy
= 'bl_idname name location height width'.split(' ')
3633 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3634 for i
in (i
for i
in mappings
if i
.is_linked
):
3636 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3638 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3640 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3641 props_to_copy
.pop(0)
3643 for prop
in props_to_copy
:
3644 setattr(new_node
, prop
, props
[prop
])
3646 nodes
= node_tree
.nodes
3648 new_node
.name
= props
['name']
3651 new_node
.parent
= parent
3652 new_node
.location
= node_loc
3654 for str_from
, str_to
in reconnections
:
3655 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3657 new_node
.select
= False
3658 success_names
.append(new_node
.name
)
3660 # Reselect all nodes
3661 if selected_node_names
and node_active_is_frame
is False:
3662 for i
in selected_node_names
:
3663 node_tree
.nodes
[i
].select
= True
3665 if active_node_name
is not None:
3666 node_tree
.nodes
[active_node_name
].select
= True
3667 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3669 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3677 def drawlayout(context
, layout
, mode
='non-panel'):
3678 tree_type
= context
.space_data
.tree_type
3680 col
= layout
.column(align
=True)
3681 col
.menu(NWMergeNodesMenu
.bl_idname
)
3684 col
= layout
.column(align
=True)
3685 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3688 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3689 col
= layout
.column(align
=True)
3690 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3691 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3694 col
= layout
.column(align
=True)
3695 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3696 col
.operator(NWSwapLinks
.bl_idname
)
3697 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3700 col
= layout
.column(align
=True)
3701 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3702 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3705 col
= layout
.column(align
=True)
3707 row
= col
.row(align
=True)
3708 row
.operator(NWClearLabel
.bl_idname
).option
= True
3709 row
.operator(NWModifyLabels
.bl_idname
)
3711 col
.operator(NWClearLabel
.bl_idname
).option
= True
3712 col
.operator(NWModifyLabels
.bl_idname
)
3713 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3715 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
3718 col
= layout
.column(align
=True)
3719 if tree_type
== 'CompositorNodeTree':
3720 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
3721 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
3724 col
= layout
.column(align
=True)
3725 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3728 col
= layout
.column(align
=True)
3729 col
.operator(NWAlignNodes
.bl_idname
, icon
='ALIGN')
3732 col
= layout
.column(align
=True)
3733 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
3737 class NodeWranglerPanel(Panel
, NWBase
):
3738 bl_idname
= "NODE_PT_nw_node_wrangler"
3739 bl_space_type
= 'NODE_EDITOR'
3740 bl_label
= "Node Wrangler"
3741 bl_region_type
= "TOOLS"
3742 bl_category
= "Node Wrangler"
3744 prepend
: StringProperty(
3747 append
: StringProperty()
3748 remove
: StringProperty()
3750 def draw(self
, context
):
3751 self
.layout
.label(text
="(Quick access: Ctrl+Space)")
3752 drawlayout(context
, self
.layout
, mode
='panel')
3758 class NodeWranglerMenu(Menu
, NWBase
):
3759 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
3760 bl_label
= "Node Wrangler"
3762 def draw(self
, context
):
3763 drawlayout(context
, self
.layout
)
3766 class NWMergeNodesMenu(Menu
, NWBase
):
3767 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
3768 bl_label
= "Merge Selected Nodes"
3770 def draw(self
, context
):
3771 type = context
.space_data
.tree_type
3772 layout
= self
.layout
3773 if type == 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3774 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
3775 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
3776 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
3777 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
3779 props
.merge_type
= 'ZCOMBINE'
3780 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
3782 props
.merge_type
= 'ALPHAOVER'
3785 class NWMergeShadersMenu(Menu
, NWBase
):
3786 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
3787 bl_label
= "Merge Selected Nodes using Shaders"
3789 def draw(self
, context
):
3790 layout
= self
.layout
3791 for type in ('MIX', 'ADD'):
3792 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
3794 props
.merge_type
= 'SHADER'
3797 class NWMergeMixMenu(Menu
, NWBase
):
3798 bl_idname
= "NODE_MT_nw_merge_mix_menu"
3799 bl_label
= "Merge Selected Nodes using Mix"
3801 def draw(self
, context
):
3802 layout
= self
.layout
3803 for type, name
, description
in blend_types
:
3804 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3806 props
.merge_type
= 'MIX'
3809 class NWConnectionListOutputs(Menu
, NWBase
):
3810 bl_idname
= "NODE_MT_nw_connection_list_out"
3813 def draw(self
, context
):
3814 layout
= self
.layout
3815 nodes
, links
= get_nodes_links(context
)
3817 n1
= nodes
[context
.scene
.NWLazySource
]
3819 if n1
.type == "R_LAYERS":
3821 for o
in n1
.outputs
:
3822 if o
.enabled
: # Check which passes the render layer has enabled
3823 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3827 for o
in n1
.outputs
:
3828 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3832 class NWConnectionListInputs(Menu
, NWBase
):
3833 bl_idname
= "NODE_MT_nw_connection_list_in"
3836 def draw(self
, context
):
3837 layout
= self
.layout
3838 nodes
, links
= get_nodes_links(context
)
3840 n2
= nodes
[context
.scene
.NWLazyTarget
]
3844 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3845 op
.from_socket
= context
.scene
.NWSourceSocket
3846 op
.to_socket
= index
3850 class NWMergeMathMenu(Menu
, NWBase
):
3851 bl_idname
= "NODE_MT_nw_merge_math_menu"
3852 bl_label
= "Merge Selected Nodes using Math"
3854 def draw(self
, context
):
3855 layout
= self
.layout
3856 for type, name
, description
in operations
:
3857 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3859 props
.merge_type
= 'MATH'
3862 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3863 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3864 bl_label
= "Batch Change Selected Nodes"
3866 def draw(self
, context
):
3867 layout
= self
.layout
3868 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3869 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3872 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3873 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3874 bl_label
= "Batch Change Blend Type"
3876 def draw(self
, context
):
3877 layout
= self
.layout
3878 for type, name
, description
in blend_types
:
3879 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3880 props
.blend_type
= type
3881 props
.operation
= 'CURRENT'
3884 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3885 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3886 bl_label
= "Batch Change Math Operation"
3888 def draw(self
, context
):
3889 layout
= self
.layout
3890 for type, name
, description
in operations
:
3891 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3892 props
.blend_type
= 'CURRENT'
3893 props
.operation
= type
3896 class NWCopyToSelectedMenu(Menu
, NWBase
):
3897 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3898 bl_label
= "Copy to Selected"
3900 def draw(self
, context
):
3901 layout
= self
.layout
3902 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3903 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3906 class NWCopyLabelMenu(Menu
, NWBase
):
3907 bl_idname
= "NODE_MT_nw_copy_label_menu"
3908 bl_label
= "Copy Label"
3910 def draw(self
, context
):
3911 layout
= self
.layout
3912 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3913 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3914 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3917 class NWAddReroutesMenu(Menu
, NWBase
):
3918 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3919 bl_label
= "Add Reroutes"
3920 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3922 def draw(self
, context
):
3923 layout
= self
.layout
3924 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3925 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3926 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3929 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3930 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3931 bl_label
= "Link Active to Selected"
3933 def draw(self
, context
):
3934 layout
= self
.layout
3935 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3936 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3937 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3940 class NWLinkStandardMenu(Menu
, NWBase
):
3941 bl_idname
= "NODE_MT_nw_link_standard_menu"
3942 bl_label
= "To All Selected"
3944 def draw(self
, context
):
3945 layout
= self
.layout
3946 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3947 props
.replace
= False
3948 props
.use_node_name
= False
3949 props
.use_outputs_names
= False
3950 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3951 props
.replace
= True
3952 props
.use_node_name
= False
3953 props
.use_outputs_names
= False
3956 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3957 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3958 bl_label
= "Use Node Name/Label"
3960 def draw(self
, context
):
3961 layout
= self
.layout
3962 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3963 props
.replace
= False
3964 props
.use_node_name
= True
3965 props
.use_outputs_names
= False
3966 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3967 props
.replace
= True
3968 props
.use_node_name
= True
3969 props
.use_outputs_names
= False
3972 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3973 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
3974 bl_label
= "Use Outputs Names"
3976 def draw(self
, context
):
3977 layout
= self
.layout
3978 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3979 props
.replace
= False
3980 props
.use_node_name
= False
3981 props
.use_outputs_names
= True
3982 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3983 props
.replace
= True
3984 props
.use_node_name
= False
3985 props
.use_outputs_names
= True
3988 class NWVertColMenu(bpy
.types
.Menu
):
3989 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
3990 bl_label
= "Vertex Colors"
3993 def poll(cls
, context
):
3995 if nw_check(context
):
3996 snode
= context
.space_data
3997 valid
= snode
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
)
4000 def draw(self
, context
):
4002 nodes
, links
= get_nodes_links(context
)
4003 mat
= context
.object.active_material
4006 for obj
in bpy
.data
.objects
:
4007 for slot
in obj
.material_slots
:
4008 if slot
.material
== mat
:
4012 if obj
.data
.vertex_colors
:
4013 for vcol
in obj
.data
.vertex_colors
:
4014 vcols
.append(vcol
.name
)
4015 vcols
= list(set(vcols
)) # get a unique list
4019 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4021 l
.label("No Vertex Color layers on objects with this material")
4024 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4025 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4026 bl_label
= "Switch Type to..."
4028 def draw(self
, context
):
4029 layout
= self
.layout
4030 tree
= context
.space_data
.node_tree
4031 if tree
.type == 'SHADER':
4032 if is_cycles_or_eevee(context
):
4033 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4034 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4035 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4036 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4037 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4038 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4039 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4040 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4042 layout
.menu(NWSwitchMatInputSubmenu
.bl_idname
)
4043 layout
.menu(NWSwitchMatOutputSubmenu
.bl_idname
)
4044 layout
.menu(NWSwitchMatColorSubmenu
.bl_idname
)
4045 layout
.menu(NWSwitchMatVectorSubmenu
.bl_idname
)
4046 layout
.menu(NWSwitchMatConverterSubmenu
.bl_idname
)
4047 layout
.menu(NWSwitchMatLayoutSubmenu
.bl_idname
)
4048 if tree
.type == 'COMPOSITING':
4049 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4050 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4051 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4052 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4053 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4054 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4055 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4056 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4057 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4058 if tree
.type == 'TEXTURE':
4059 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4060 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4061 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4062 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4063 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4064 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4065 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4066 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4069 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4070 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4073 def draw(self
, context
):
4074 layout
= self
.layout
4075 for ident
, node_type
, rna_name
in sorted(shaders_input_nodes_props
, key
=lambda k
: k
[2]):
4076 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4077 props
.to_type
= ident
4080 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4081 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4084 def draw(self
, context
):
4085 layout
= self
.layout
4086 for ident
, node_type
, rna_name
in sorted(shaders_output_nodes_props
, key
=lambda k
: k
[2]):
4087 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4088 props
.to_type
= ident
4091 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4092 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4095 def draw(self
, context
):
4096 layout
= self
.layout
4097 for ident
, node_type
, rna_name
in sorted(shaders_shader_nodes_props
, key
=lambda k
: k
[2]):
4098 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4099 props
.to_type
= ident
4102 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4103 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4104 bl_label
= "Texture"
4106 def draw(self
, context
):
4107 layout
= self
.layout
4108 for ident
, node_type
, rna_name
in sorted(shaders_texture_nodes_props
, key
=lambda k
: k
[2]):
4109 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4110 props
.to_type
= ident
4113 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4114 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4117 def draw(self
, context
):
4118 layout
= self
.layout
4119 for ident
, node_type
, rna_name
in sorted(shaders_color_nodes_props
, key
=lambda k
: k
[2]):
4120 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4121 props
.to_type
= ident
4124 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4125 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4128 def draw(self
, context
):
4129 layout
= self
.layout
4130 for ident
, node_type
, rna_name
in sorted(shaders_vector_nodes_props
, key
=lambda k
: k
[2]):
4131 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4132 props
.to_type
= ident
4135 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4136 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4137 bl_label
= "Converter"
4139 def draw(self
, context
):
4140 layout
= self
.layout
4141 for ident
, node_type
, rna_name
in sorted(shaders_converter_nodes_props
, key
=lambda k
: k
[2]):
4142 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4143 props
.to_type
= ident
4146 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4147 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4150 def draw(self
, context
):
4151 layout
= self
.layout
4152 for ident
, node_type
, rna_name
in sorted(shaders_layout_nodes_props
, key
=lambda k
: k
[2]):
4153 if node_type
!= 'FRAME':
4154 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4155 props
.to_type
= ident
4158 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4159 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4162 def draw(self
, context
):
4163 layout
= self
.layout
4164 for ident
, node_type
, rna_name
in sorted(compo_input_nodes_props
, key
=lambda k
: k
[2]):
4165 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4166 props
.to_type
= ident
4169 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4170 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4173 def draw(self
, context
):
4174 layout
= self
.layout
4175 for ident
, node_type
, rna_name
in sorted(compo_output_nodes_props
, key
=lambda k
: k
[2]):
4176 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4177 props
.to_type
= ident
4180 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4181 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4184 def draw(self
, context
):
4185 layout
= self
.layout
4186 for ident
, node_type
, rna_name
in sorted(compo_color_nodes_props
, key
=lambda k
: k
[2]):
4187 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4188 props
.to_type
= ident
4191 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4192 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4193 bl_label
= "Converter"
4195 def draw(self
, context
):
4196 layout
= self
.layout
4197 for ident
, node_type
, rna_name
in sorted(compo_converter_nodes_props
, key
=lambda k
: k
[2]):
4198 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4199 props
.to_type
= ident
4202 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4203 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4206 def draw(self
, context
):
4207 layout
= self
.layout
4208 for ident
, node_type
, rna_name
in sorted(compo_filter_nodes_props
, key
=lambda k
: k
[2]):
4209 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4210 props
.to_type
= ident
4213 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4214 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4217 def draw(self
, context
):
4218 layout
= self
.layout
4219 for ident
, node_type
, rna_name
in sorted(compo_vector_nodes_props
, key
=lambda k
: k
[2]):
4220 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4221 props
.to_type
= ident
4224 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4225 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4228 def draw(self
, context
):
4229 layout
= self
.layout
4230 for ident
, node_type
, rna_name
in sorted(compo_matte_nodes_props
, key
=lambda k
: k
[2]):
4231 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4232 props
.to_type
= ident
4235 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4236 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4237 bl_label
= "Distort"
4239 def draw(self
, context
):
4240 layout
= self
.layout
4241 for ident
, node_type
, rna_name
in sorted(compo_distort_nodes_props
, key
=lambda k
: k
[2]):
4242 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4243 props
.to_type
= ident
4246 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4247 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4250 def draw(self
, context
):
4251 layout
= self
.layout
4252 for ident
, node_type
, rna_name
in sorted(compo_layout_nodes_props
, key
=lambda k
: k
[2]):
4253 if node_type
!= 'FRAME':
4254 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4255 props
.to_type
= ident
4258 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4259 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4262 def draw(self
, context
):
4263 layout
= self
.layout
4264 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4265 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4266 props
.to_type
= ident
4269 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4270 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4273 def draw(self
, context
):
4274 layout
= self
.layout
4275 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4276 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4277 props
.to_type
= ident
4280 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4281 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4284 def draw(self
, context
):
4285 layout
= self
.layout
4286 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4287 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4288 props
.to_type
= ident
4291 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4292 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4295 def draw(self
, context
):
4296 layout
= self
.layout
4297 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4298 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4299 props
.to_type
= ident
4302 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4303 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4304 bl_label
= "Converter"
4306 def draw(self
, context
):
4307 layout
= self
.layout
4308 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4309 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4310 props
.to_type
= ident
4313 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4314 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4317 def draw(self
, context
):
4318 layout
= self
.layout
4319 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4320 if node_type
!= 'FRAME':
4321 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4322 props
.to_type
= ident
4325 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4326 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4329 def draw(self
, context
):
4330 layout
= self
.layout
4331 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4332 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4333 props
.to_type
= ident
4336 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4337 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4340 def draw(self
, context
):
4341 layout
= self
.layout
4342 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4343 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4344 props
.to_type
= ident
4347 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4348 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4351 def draw(self
, context
):
4352 layout
= self
.layout
4353 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4354 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4355 props
.to_type
= ident
4358 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4359 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4360 bl_label
= "Pattern"
4362 def draw(self
, context
):
4363 layout
= self
.layout
4364 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4365 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4366 props
.to_type
= ident
4369 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4370 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4371 bl_label
= "Textures"
4373 def draw(self
, context
):
4374 layout
= self
.layout
4375 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4376 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4377 props
.to_type
= ident
4380 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4381 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4382 bl_label
= "Converter"
4384 def draw(self
, context
):
4385 layout
= self
.layout
4386 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4387 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4388 props
.to_type
= ident
4391 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4392 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4393 bl_label
= "Distort"
4395 def draw(self
, context
):
4396 layout
= self
.layout
4397 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4398 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4399 props
.to_type
= ident
4402 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4403 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4406 def draw(self
, context
):
4407 layout
= self
.layout
4408 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4409 if node_type
!= 'FRAME':
4410 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4411 props
.to_type
= ident
4415 # APPENDAGES TO EXISTING UI
4419 def select_parent_children_buttons(self
, context
):
4420 layout
= self
.layout
4421 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4422 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4425 def attr_nodes_menu_func(self
, context
):
4426 col
= self
.layout
.column(align
=True)
4427 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4431 def multipleimages_menu_func(self
, context
):
4432 col
= self
.layout
.column(align
=True)
4433 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4434 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4438 def bgreset_menu_func(self
, context
):
4439 self
.layout
.operator(NWResetBG
.bl_idname
)
4442 def save_viewer_menu_func(self
, context
):
4443 if nw_check(context
):
4444 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4445 if context
.scene
.node_tree
.nodes
.active
:
4446 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4447 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4450 def reset_nodes_button(self
, context
):
4451 node_active
= context
.active_node
4452 node_selected
= context
.selected_nodes
4453 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4455 # Check if active node is in the selection and respective type
4456 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4457 row
= self
.layout
.row()
4458 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4459 self
.layout
.separator()
4461 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4462 row
= self
.layout
.row()
4463 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4464 self
.layout
.separator()
4468 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4472 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4473 # props entry: (property name, property value)
4476 # NWMergeNodes with Ctrl (AUTO).
4477 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4478 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4479 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4480 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4481 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4482 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4483 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4484 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4485 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4486 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4487 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4488 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4489 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4490 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4491 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4492 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4493 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4494 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4495 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4496 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4497 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4498 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4499 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4500 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4501 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4502 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4503 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4504 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4505 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4506 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4507 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4508 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4509 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4510 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4511 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4512 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4513 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4514 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4515 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4516 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4517 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4518 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4519 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4520 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4521 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4522 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4523 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4524 # NWMergeNodes with Ctrl Shift (MATH)
4525 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4526 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4527 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4528 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4529 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4530 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4531 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4532 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4533 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4534 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4535 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4536 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4537 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4538 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4539 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4540 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4541 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4542 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4543 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4544 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4545 # BATCH CHANGE NODES
4546 # NWBatchChangeNodes with Alt
4547 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4548 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4549 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4550 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4551 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4552 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4553 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4554 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4555 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4556 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4557 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4558 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4559 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4560 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4561 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4562 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4563 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4564 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4565 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4566 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4567 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4568 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4569 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4570 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4571 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4572 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4573 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4574 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4575 # LINK ACTIVE TO SELECTED
4576 # Don't use names, don't replace links (K)
4577 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4578 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4579 # Don't use names, replace links (Shift K)
4580 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4581 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4582 # Use node name, don't replace links (')
4583 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4584 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4585 # Use node name, replace links (Shift ')
4586 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4587 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4588 # Don't use names, don't replace links (;)
4589 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4590 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4591 # Don't use names, replace links (')
4592 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4593 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4595 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4596 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4597 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4598 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4599 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4600 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4601 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4602 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4603 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4604 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4605 # CLEAR LABEL (Alt L)
4606 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4607 # MODIFY LABEL (Alt Shift L)
4608 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4609 # Copy Label from active to selected
4610 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4611 # DETACH OUTPUTS (Alt Shift D)
4612 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4613 # LINK TO OUTPUT NODE (O)
4614 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4615 # SELECT PARENT/CHILDREN
4617 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4619 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4621 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4622 # Add Principled BSDF Texture Setup
4623 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4625 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4627 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4629 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4631 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4633 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4635 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4637 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
4639 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, False, False, None, "Lazy Connect"),
4640 # Lazy Connect with Menu
4641 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4642 # Viewer Tile Center
4643 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4645 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4646 # Reset Nodes (Back Space)
4647 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4649 ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wranger menu"),
4650 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4651 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4652 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4653 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4654 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4659 NWPrincipledPreferences
,
4679 NWAddPrincipledSetup
,
4681 NWLinkActiveToSelected
,
4683 NWSelectParentChildren
,
4689 NWAddMultipleImages
,
4698 NWConnectionListOutputs
,
4699 NWConnectionListInputs
,
4701 NWBatchChangeNodesMenu
,
4702 NWBatchChangeBlendTypeMenu
,
4703 NWBatchChangeOperationMenu
,
4704 NWCopyToSelectedMenu
,
4707 NWLinkActiveToSelectedMenu
,
4709 NWLinkUseNodeNameMenu
,
4710 NWLinkUseOutputsNamesMenu
,
4712 NWSwitchNodeTypeMenu
,
4713 NWSwitchShadersInputSubmenu
,
4714 NWSwitchShadersOutputSubmenu
,
4715 NWSwitchShadersShaderSubmenu
,
4716 NWSwitchShadersTextureSubmenu
,
4717 NWSwitchShadersColorSubmenu
,
4718 NWSwitchShadersVectorSubmenu
,
4719 NWSwitchShadersConverterSubmenu
,
4720 NWSwitchShadersLayoutSubmenu
,
4721 NWSwitchCompoInputSubmenu
,
4722 NWSwitchCompoOutputSubmenu
,
4723 NWSwitchCompoColorSubmenu
,
4724 NWSwitchCompoConverterSubmenu
,
4725 NWSwitchCompoFilterSubmenu
,
4726 NWSwitchCompoVectorSubmenu
,
4727 NWSwitchCompoMatteSubmenu
,
4728 NWSwitchCompoDistortSubmenu
,
4729 NWSwitchCompoLayoutSubmenu
,
4730 NWSwitchMatInputSubmenu
,
4731 NWSwitchMatOutputSubmenu
,
4732 NWSwitchMatColorSubmenu
,
4733 NWSwitchMatVectorSubmenu
,
4734 NWSwitchMatConverterSubmenu
,
4735 NWSwitchMatLayoutSubmenu
,
4736 NWSwitchTexInputSubmenu
,
4737 NWSwitchTexOutputSubmenu
,
4738 NWSwitchTexColorSubmenu
,
4739 NWSwitchTexPatternSubmenu
,
4740 NWSwitchTexTexturesSubmenu
,
4741 NWSwitchTexConverterSubmenu
,
4742 NWSwitchTexDistortSubmenu
,
4743 NWSwitchTexLayoutSubmenu
,
4747 from bpy
.utils
import register_class
4750 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4751 name
="Busy Drawing!",
4753 description
="An internal property used to store only the first mouse position")
4754 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4755 name
="Lazy Source!",
4757 description
="An internal property used to store the first node in a Lazy Connect operation")
4758 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
4759 name
="Lazy Target!",
4761 description
="An internal property used to store the last node in a Lazy Connect operation")
4762 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
4763 name
="Source Socket!",
4765 description
="An internal property used to store the source socket in a Lazy Connect operation")
4771 addon_keymaps
.clear()
4772 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
4774 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
4775 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
4776 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
4778 for prop
, value
in props
:
4779 setattr(kmi
.properties
, prop
, value
)
4780 addon_keymaps
.append((km
, kmi
))
4783 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
4784 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4785 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4786 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
4787 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
4788 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4789 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4790 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4791 bpy
.types
.NODE_PT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4792 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
4793 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
4797 from bpy
.utils
import unregister_class
4800 del bpy
.types
.Scene
.NWBusyDrawing
4801 del bpy
.types
.Scene
.NWLazySource
4802 del bpy
.types
.Scene
.NWLazyTarget
4803 del bpy
.types
.Scene
.NWSourceSocket
4806 for km
, kmi
in addon_keymaps
:
4807 km
.keymap_items
.remove(kmi
)
4808 addon_keymaps
.clear()
4811 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
4812 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4813 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4814 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
4815 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
4816 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4817 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4818 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4819 bpy
.types
.NODE_PT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4820 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
4821 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
4824 unregister_class(cls
)
4826 if __name__
== "__main__":