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_offset
[0] = 0
1566 context
.space_data
.backdrop_offset
[1] = 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 bl_options
= {'REGISTER', 'UNDO'}
1576 attr_name
: StringProperty()
1578 def execute(self
, context
):
1579 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1580 nodes
, links
= get_nodes_links(context
)
1581 nodes
.active
.attribute_name
= self
.attr_name
1585 class NWEmissionViewer(Operator
, NWBase
):
1586 bl_idname
= "node.nw_emission_viewer"
1587 bl_label
= "Emission Viewer"
1588 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1589 bl_options
= {'REGISTER', 'UNDO'}
1592 def poll(cls
, context
):
1593 is_cycles
= is_cycles_or_eevee(context
)
1594 if nw_check(context
):
1595 space
= context
.space_data
1596 if space
.tree_type
== 'ShaderNodeTree' and is_cycles
:
1597 if context
.active_node
:
1598 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1604 def invoke(self
, context
, event
):
1605 space
= context
.space_data
1606 shader_type
= space
.shader_type
1607 if shader_type
== 'OBJECT':
1608 if space
.id not in [light
for light
in bpy
.data
.lights
]: # cannot use bpy.data.lights directly as iterable
1609 shader_output_type
= "OUTPUT_MATERIAL"
1610 shader_output_ident
= "ShaderNodeOutputMaterial"
1611 shader_viewer_ident
= "ShaderNodeEmission"
1613 shader_output_type
= "OUTPUT_LIGHT"
1614 shader_output_ident
= "ShaderNodeOutputLight"
1615 shader_viewer_ident
= "ShaderNodeEmission"
1617 elif shader_type
== 'WORLD':
1618 shader_output_type
= "OUTPUT_WORLD"
1619 shader_output_ident
= "ShaderNodeOutputWorld"
1620 shader_viewer_ident
= "ShaderNodeBackground"
1621 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1622 mlocx
= event
.mouse_region_x
1623 mlocy
= event
.mouse_region_y
1624 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1625 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1626 nodes
, links
= get_nodes_links(context
)
1627 in_group
= context
.active_node
!= space
.node_tree
.nodes
.active
1628 active
= nodes
.active
1629 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1632 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1633 for out
in active
.outputs
:
1638 # get material_output node, store selection, deselect all
1639 materialout
= None # placeholder node
1642 if node
.type == shader_output_type
:
1645 selection
.append(node
.name
)
1648 # get right-most location
1649 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1650 max_xloc_node
= sorted_by_xloc
[-1]
1651 if max_xloc_node
.name
== 'Emission Viewer':
1652 max_xloc_node
= sorted_by_xloc
[-2]
1654 # get average y location
1657 sum_yloc
+= node
.location
.y
1659 new_locx
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1660 new_locy
= sum_yloc
/ len(nodes
)
1662 materialout
= nodes
.new(shader_output_ident
)
1663 materialout
.location
.x
= new_locx
1664 materialout
.location
.y
= new_locy
1665 materialout
.select
= False
1666 # Analyze outputs, add "Emission Viewer" if needed, make links
1669 for i
, out
in enumerate(active
.outputs
):
1671 valid_outputs
.append(i
)
1673 out_i
= valid_outputs
[0] # Start index of node's outputs
1674 for i
, valid_i
in enumerate(valid_outputs
):
1675 for out_link
in active
.outputs
[valid_i
].links
:
1676 if "Emission Viewer" in out_link
.to_node
.name
or (out_link
.to_node
== materialout
and out_link
.to_socket
== materialout
.inputs
[0]):
1677 if i
< len(valid_outputs
) - 1:
1678 out_i
= valid_outputs
[i
+ 1]
1680 out_i
= valid_outputs
[0]
1681 make_links
= [] # store sockets for new links
1683 # If output type not 'SHADER' - "Emission Viewer" needed
1684 if active
.outputs
[out_i
].type != 'SHADER':
1685 # get Emission Viewer node
1686 emission_exists
= False
1687 emission_placeholder
= nodes
[0]
1689 if "Emission Viewer" in node
.name
:
1690 emission_exists
= True
1691 emission_placeholder
= node
1692 if not emission_exists
:
1693 emission
= nodes
.new(shader_viewer_ident
)
1694 emission
.hide
= True
1695 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1696 emission
.label
= "Viewer"
1697 emission
.name
= "Emission Viewer"
1698 emission
.use_custom_color
= True
1699 emission
.color
= (0.6, 0.5, 0.4)
1700 emission
.select
= False
1702 emission
= emission_placeholder
1703 make_links
.append((active
.outputs
[out_i
], emission
.inputs
[0]))
1705 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1706 if emission
.outputs
[0].links
.__len
__() > 0:
1707 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1708 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1710 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1712 # Set brightness of viewer to compensate for Film and CM exposure
1713 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1714 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1715 emission
.inputs
[1].default_value
= intensity
1718 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1719 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[1 if active
.outputs
[out_i
].name
== "Volume" else 0]))
1721 if node
.name
== 'Emission Viewer':
1723 bpy
.ops
.node
.delete()
1724 for li_from
, li_to
in make_links
:
1725 links
.new(li_from
, li_to
)
1727 nodes
.active
= active
1729 if node
.name
in selection
:
1731 force_update(context
)
1734 return {'CANCELLED'}
1737 class NWFrameSelected(Operator
, NWBase
):
1738 bl_idname
= "node.nw_frame_selected"
1739 bl_label
= "Frame Selected"
1740 bl_description
= "Add a frame node and parent the selected nodes to it"
1741 bl_options
= {'REGISTER', 'UNDO'}
1743 label_prop
: StringProperty(
1745 description
='The visual name of the frame node',
1748 color_prop
: FloatVectorProperty(
1750 description
="The color of the frame node",
1751 default
=(0.6, 0.6, 0.6),
1752 min=0, max=1, step
=1, precision
=3,
1753 subtype
='COLOR_GAMMA', size
=3
1756 def execute(self
, context
):
1757 nodes
, links
= get_nodes_links(context
)
1760 if node
.select
== True:
1761 selected
.append(node
)
1763 bpy
.ops
.node
.add_node(type='NodeFrame')
1765 frm
.label
= self
.label_prop
1766 frm
.use_custom_color
= True
1767 frm
.color
= self
.color_prop
1769 for node
in selected
:
1775 class NWReloadImages(Operator
, NWBase
):
1776 bl_idname
= "node.nw_reload_images"
1777 bl_label
= "Reload Images"
1778 bl_description
= "Update all the image nodes to match their files on disk"
1780 def execute(self
, context
):
1781 nodes
, links
= get_nodes_links(context
)
1782 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1785 if node
.type in image_types
:
1786 if node
.type == "TEXTURE":
1787 if node
.texture
: # node has texture assigned
1788 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1789 if node
.texture
.image
: # texture has image assigned
1790 node
.texture
.image
.reload()
1798 self
.report({'INFO'}, "Reloaded images")
1799 print("Reloaded " + str(num_reloaded
) + " images")
1800 force_update(context
)
1803 self
.report({'WARNING'}, "No images found to reload in this node tree")
1804 return {'CANCELLED'}
1807 class NWSwitchNodeType(Operator
, NWBase
):
1808 """Switch type of selected nodes """
1809 bl_idname
= "node.nw_swtch_node_type"
1810 bl_label
= "Switch Node Type"
1811 bl_options
= {'REGISTER', 'UNDO'}
1813 to_type
: EnumProperty(
1814 name
="Switch to type",
1815 items
=list(shaders_input_nodes_props
) +
1816 list(shaders_output_nodes_props
) +
1817 list(shaders_shader_nodes_props
) +
1818 list(shaders_texture_nodes_props
) +
1819 list(shaders_color_nodes_props
) +
1820 list(shaders_vector_nodes_props
) +
1821 list(shaders_converter_nodes_props
) +
1822 list(shaders_layout_nodes_props
) +
1823 list(compo_input_nodes_props
) +
1824 list(compo_output_nodes_props
) +
1825 list(compo_color_nodes_props
) +
1826 list(compo_converter_nodes_props
) +
1827 list(compo_filter_nodes_props
) +
1828 list(compo_vector_nodes_props
) +
1829 list(compo_matte_nodes_props
) +
1830 list(compo_distort_nodes_props
) +
1831 list(compo_layout_nodes_props
) +
1832 list(blender_mat_input_nodes_props
) +
1833 list(blender_mat_output_nodes_props
) +
1834 list(blender_mat_color_nodes_props
) +
1835 list(blender_mat_vector_nodes_props
) +
1836 list(blender_mat_converter_nodes_props
) +
1837 list(blender_mat_layout_nodes_props
) +
1838 list(texture_input_nodes_props
) +
1839 list(texture_output_nodes_props
) +
1840 list(texture_color_nodes_props
) +
1841 list(texture_pattern_nodes_props
) +
1842 list(texture_textures_nodes_props
) +
1843 list(texture_converter_nodes_props
) +
1844 list(texture_distort_nodes_props
) +
1845 list(texture_layout_nodes_props
)
1848 def execute(self
, context
):
1849 nodes
, links
= get_nodes_links(context
)
1850 to_type
= self
.to_type
1851 # Those types of nodes will not swap.
1852 src_excludes
= ('NodeFrame')
1853 # Those attributes of nodes will be copied if possible
1854 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1855 'show_options', 'show_preview', 'show_texture',
1856 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1858 selected
= [n
for n
in nodes
if n
.select
]
1860 for node
in [n
for n
in selected
if
1861 n
.rna_type
.identifier
not in src_excludes
and
1862 n
.rna_type
.identifier
!= to_type
]:
1863 new_node
= nodes
.new(to_type
)
1864 for attr
in attrs_to_pass
:
1865 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1866 setattr(new_node
, attr
, getattr(node
, attr
))
1867 # set image datablock of dst to image of src
1868 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1870 new_node
.image
= node
.image
1872 if new_node
.type == 'SWITCH':
1873 new_node
.hide
= True
1874 # Dictionaries: src_sockets and dst_sockets:
1875 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1876 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1877 # in 'INPUTS' and 'OUTPUTS':
1878 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1880 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1882 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1883 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1886 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1887 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1889 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1890 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1891 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1892 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1893 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1894 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1895 # enumerate in inputs, then in outputs
1896 # find name, default value and links of socket
1897 for i
, socket
in enumerate(in_out
):
1898 the_name
= socket
.name
1900 # Not every socket, especially in outputs has "default_value"
1901 if hasattr(socket
, 'default_value'):
1902 dval
= socket
.default_value
1904 for lnk
in socket
.links
:
1905 socket_links
.append(lnk
)
1906 # check type of socket to fill proper keys.
1907 for the_type
in types_order_one
:
1908 if socket
.type == the_type
:
1909 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1910 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1911 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1912 # Check which of the types in inputs/outputs is considered to be "main".
1913 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1914 for type_check
in types_order_one
:
1915 if sockets
[in_out_name
][type_check
]:
1916 sockets
[in_out_name
]['MAIN'] = type_check
1920 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1921 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1924 for inout
, soctype
in (
1925 ('INPUTS', 'MAIN',),
1926 ('INPUTS', 'SHADER',),
1927 ('INPUTS', 'RGBA',),
1928 ('INPUTS', 'VECTOR',),
1929 ('INPUTS', 'VALUE',),
1930 ('OUTPUTS', 'MAIN',),
1931 ('OUTPUTS', 'SHADER',),
1932 ('OUTPUTS', 'RGBA',),
1933 ('OUTPUTS', 'VECTOR',),
1934 ('OUTPUTS', 'VALUE',),
1936 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1937 if soctype
== 'MAIN':
1938 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1939 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1941 sc
= src_sockets
[inout
][soctype
]
1942 dt
= dst_sockets
[inout
][soctype
]
1943 # start with 'dt' to determine number of possibilities.
1944 for i
, soc
in enumerate(dt
):
1945 # if src main has enough entries - match them with dst main sockets by indexes.
1947 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1948 # add 'VALUE_NAME' criterion to inputs.
1949 if inout
== 'INPUTS' and soctype
== 'VALUE':
1951 if s
[2] == soc
[2]: # if names match
1952 # append src (index, dval), dst (index, dval)
1953 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1955 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1956 # This creates better links when relinking textures.
1957 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1958 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1960 # Pass default values and RELINK:
1961 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1962 # INPUTS: Base on matches in proper order.
1963 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1965 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1966 new_node
.inputs
[dst_i
].default_value
= src_dval
1967 # Special case: switch to math
1968 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1969 new_node
.type == 'MATH' and\
1971 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1972 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1973 if node
.type == 'MIX_RGB':
1974 if node
.blend_type
in [o
[0] for o
in operations
]:
1975 new_node
.operation
= node
.blend_type
1976 # Special case: switch from math to some types
1977 if node
.type == 'MATH' and\
1978 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1981 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1982 if new_node
.type == 'MIX_RGB':
1983 if node
.operation
in [t
[0] for t
in blend_types
]:
1984 new_node
.blend_type
= node
.operation
1985 # Set Fac of MIX_RGB to 1.0
1986 new_node
.inputs
[0].default_value
= 1.0
1987 # make link only when dst matching input is not linked already.
1988 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1989 in_src_link
= node
.inputs
[src_i
].links
[0]
1990 in_dst_socket
= new_node
.inputs
[dst_i
]
1991 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1992 links
.remove(in_src_link
)
1993 # OUTPUTS: Base on matches in proper order.
1994 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1995 for out_src_link
in node
.outputs
[src_i
].links
:
1996 out_dst_socket
= new_node
.outputs
[dst_i
]
1997 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1998 # relink rest inputs if possible, no criteria
1999 for src_inp
in node
.inputs
:
2000 for dst_inp
in new_node
.inputs
:
2001 if src_inp
.links
and not dst_inp
.links
:
2002 src_link
= src_inp
.links
[0]
2003 links
.new(src_link
.from_socket
, dst_inp
)
2004 links
.remove(src_link
)
2005 # relink rest outputs if possible, base on node kind if any left.
2006 for src_o
in node
.outputs
:
2007 for out_src_link
in src_o
.links
:
2008 for dst_o
in new_node
.outputs
:
2009 if src_o
.type == dst_o
.type:
2010 links
.new(dst_o
, out_src_link
.to_socket
)
2011 # relink rest outputs no criteria if any left. Link all from first output.
2012 for src_o
in node
.outputs
:
2013 for out_src_link
in src_o
.links
:
2014 if new_node
.outputs
:
2015 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
2017 force_update(context
)
2021 class NWMergeNodes(Operator
, NWBase
):
2022 bl_idname
= "node.nw_merge_nodes"
2023 bl_label
= "Merge Nodes"
2024 bl_description
= "Merge Selected Nodes"
2025 bl_options
= {'REGISTER', 'UNDO'}
2029 description
="All possible blend types and math operations",
2030 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2032 merge_type
: EnumProperty(
2034 description
="Type of Merge to be used",
2036 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2037 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2038 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2039 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2040 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2041 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2045 def execute(self
, context
):
2046 settings
= context
.user_preferences
.addons
[__name__
].preferences
2047 merge_hide
= settings
.merge_hide
2048 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2051 do_hide_shader
= False
2052 if merge_hide
== 'ALWAYS':
2054 do_hide_shader
= True
2055 elif merge_hide
== 'NON_SHADER':
2058 tree_type
= context
.space_data
.node_tree
.type
2059 if tree_type
== 'COMPOSITING':
2060 node_type
= 'CompositorNode'
2061 elif tree_type
== 'SHADER':
2062 node_type
= 'ShaderNode'
2063 elif tree_type
== 'TEXTURE':
2064 node_type
= 'TextureNode'
2065 nodes
, links
= get_nodes_links(context
)
2067 merge_type
= self
.merge_type
2068 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2069 # 'ZCOMBINE' works only if mode == 'MIX'
2070 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2071 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2074 selected_mix
= [] # entry = [index, loc]
2075 selected_shader
= [] # entry = [index, loc]
2076 selected_math
= [] # entry = [index, loc]
2077 selected_z
= [] # entry = [index, loc]
2078 selected_alphaover
= [] # entry = [index, loc]
2080 for i
, node
in enumerate(nodes
):
2081 if node
.select
and node
.outputs
:
2082 if merge_type
== 'AUTO':
2083 for (type, types_list
, dst
) in (
2084 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2085 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2086 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2088 output_type
= node
.outputs
[0].type
2089 valid_mode
= mode
in types_list
2090 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2091 # Cheat that output type is 'RGBA',
2092 # and that 'MIX' exists in math operations list.
2093 # This way when selected_mix list is analyzed:
2094 # Node data will be appended even though it doesn't meet requirements.
2095 if output_type
!= 'SHADER' and mode
== 'MIX':
2096 output_type
= 'RGBA'
2098 if output_type
== type and valid_mode
:
2099 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2101 for (type, types_list
, dst
) in (
2102 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2103 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2104 ('MATH', [t
[0] for t
in operations
], selected_math
),
2105 ('ZCOMBINE', ('MIX', ), selected_z
),
2106 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2108 if merge_type
== type and mode
in types_list
:
2109 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2110 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2111 # use only 'Mix' nodes for merging.
2112 # For that we add selected_math list to selected_mix list and clear selected_math.
2113 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2114 selected_mix
+= selected_math
2117 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2119 count_before
= len(nodes
)
2120 # sort list by loc_x - reversed
2121 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2123 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2124 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2125 if merge_position
== 'CENTER':
2126 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)
2127 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2133 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2137 if nodes_list
== selected_shader
and not do_hide_shader
:
2139 the_range
= len(nodes_list
) - 1
2140 if len(nodes_list
) == 1:
2142 for i
in range(the_range
):
2143 if nodes_list
== selected_mix
:
2144 add_type
= node_type
+ 'MixRGB'
2145 add
= nodes
.new(add_type
)
2146 add
.blend_type
= mode
2148 add
.inputs
[0].default_value
= 1.0
2149 add
.show_preview
= False
2155 add
.width_hidden
= 100.0
2156 elif nodes_list
== selected_math
:
2157 add_type
= node_type
+ 'Math'
2158 add
= nodes
.new(add_type
)
2159 add
.operation
= mode
2165 add
.width_hidden
= 100.0
2166 elif nodes_list
== selected_shader
:
2168 add_type
= node_type
+ 'MixShader'
2169 add
= nodes
.new(add_type
)
2170 add
.hide
= do_hide_shader
2175 add
.width_hidden
= 100.0
2177 add_type
= node_type
+ 'AddShader'
2178 add
= nodes
.new(add_type
)
2179 add
.hide
= do_hide_shader
2184 add
.width_hidden
= 100.0
2185 elif nodes_list
== selected_z
:
2186 add
= nodes
.new('CompositorNodeZcombine')
2187 add
.show_preview
= False
2193 add
.width_hidden
= 100.0
2194 elif nodes_list
== selected_alphaover
:
2195 add
= nodes
.new('CompositorNodeAlphaOver')
2196 add
.show_preview
= False
2202 add
.width_hidden
= 100.0
2203 add
.location
= loc_x
, loc_y
2207 count_after
= len(nodes
)
2208 index
= count_after
- 1
2209 first_selected
= nodes
[nodes_list
[0][0]]
2210 # "last" node has been added as first, so its index is count_before.
2211 last_add
= nodes
[count_before
]
2213 # Two nodes were selected and first selected has no output links, second selected has output links.
2214 # Then add links from last add to all links 'to_socket' of out links of second selected.
2215 if len(nodes_list
) == 2:
2216 if not first_selected
.outputs
[0].links
:
2217 second_selected
= nodes
[nodes_list
[1][0]]
2218 for ss_link
in second_selected
.outputs
[0].links
:
2219 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2220 # Create list of invalid indexes.
2221 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2222 # Link only if "to_node" index not in invalid indexes list.
2223 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2224 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2225 # add links from last_add to all links 'to_socket' of out links of first selected.
2226 for fs_link
in first_selected
.outputs
[0].links
:
2227 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2228 # Create list of invalid indexes.
2229 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2230 # Link only if "to_node" index not in invalid indexes list.
2231 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2232 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2233 # add link from "first" selected and "first" add node
2234 node_to
= nodes
[count_after
- 1]
2235 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2236 if node_to
.type == 'ZCOMBINE':
2237 for fs_out
in first_selected
.outputs
:
2238 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2239 links
.new(fs_out
, node_to
.inputs
[1])
2241 # add links between added ADD nodes and between selected and ADD nodes
2242 for i
in range(count_adds
):
2243 if i
< count_adds
- 1:
2244 node_from
= nodes
[index
]
2245 node_to
= nodes
[index
- 1]
2246 node_to_input_i
= first
2247 node_to_z_i
= 1 # if z combine - link z to first z input
2248 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2249 if node_to
.type == 'ZCOMBINE':
2250 for from_out
in node_from
.outputs
:
2251 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2252 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2253 if len(nodes_list
) > 1:
2254 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2255 node_to
= nodes
[index
]
2256 node_to_input_i
= second
2257 node_to_z_i
= 3 # if z combine - link z to second z input
2258 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2259 if node_to
.type == 'ZCOMBINE':
2260 for from_out
in node_from
.outputs
:
2261 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2262 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2264 # set "last" of added nodes as active
2265 nodes
.active
= last_add
2266 for i
, x
, y
, dx
, h
in nodes_list
:
2267 nodes
[i
].select
= False
2272 class NWBatchChangeNodes(Operator
, NWBase
):
2273 bl_idname
= "node.nw_batch_change"
2274 bl_label
= "Batch Change"
2275 bl_description
= "Batch Change Blend Type and Math Operation"
2276 bl_options
= {'REGISTER', 'UNDO'}
2278 blend_type
: EnumProperty(
2280 items
=blend_types
+ navs
,
2282 operation
: EnumProperty(
2284 items
=operations
+ navs
,
2287 def execute(self
, context
):
2289 nodes
, links
= get_nodes_links(context
)
2290 blend_type
= self
.blend_type
2291 operation
= self
.operation
2292 for node
in context
.selected_nodes
:
2293 if node
.type == 'MIX_RGB':
2294 if not blend_type
in [nav
[0] for nav
in navs
]:
2295 node
.blend_type
= blend_type
2297 if blend_type
== 'NEXT':
2298 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2299 #index = blend_types.index(node.blend_type)
2300 if index
== len(blend_types
) - 1:
2301 node
.blend_type
= blend_types
[0][0]
2303 node
.blend_type
= blend_types
[index
+ 1][0]
2305 if blend_type
== 'PREV':
2306 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2308 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2310 node
.blend_type
= blend_types
[index
- 1][0]
2312 if node
.type == 'MATH':
2313 if not operation
in [nav
[0] for nav
in navs
]:
2314 node
.operation
= operation
2316 if operation
== 'NEXT':
2317 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2318 #index = operations.index(node.operation)
2319 if index
== len(operations
) - 1:
2320 node
.operation
= operations
[0][0]
2322 node
.operation
= operations
[index
+ 1][0]
2324 if operation
== 'PREV':
2325 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2326 #index = operations.index(node.operation)
2328 node
.operation
= operations
[len(operations
) - 1][0]
2330 node
.operation
= operations
[index
- 1][0]
2335 class NWChangeMixFactor(Operator
, NWBase
):
2336 bl_idname
= "node.nw_factor"
2337 bl_label
= "Change Factor"
2338 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2339 bl_options
= {'REGISTER', 'UNDO'}
2341 # option: Change factor.
2342 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2343 # Else - change factor by option value.
2344 option
: FloatProperty()
2346 def execute(self
, context
):
2347 nodes
, links
= get_nodes_links(context
)
2348 option
= self
.option
2349 selected
= [] # entry = index
2350 for si
, node
in enumerate(nodes
):
2352 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2356 fac
= nodes
[si
].inputs
[0]
2357 nodes
[si
].hide
= False
2358 if option
in {0.0, 1.0}:
2359 fac
.default_value
= option
2361 fac
.default_value
+= option
2366 class NWCopySettings(Operator
, NWBase
):
2367 bl_idname
= "node.nw_copy_settings"
2368 bl_label
= "Copy Settings"
2369 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2370 bl_options
= {'REGISTER', 'UNDO'}
2373 def poll(cls
, context
):
2375 if nw_check(context
):
2376 if context
.active_node
is not None and context
.active_node
.type is not 'FRAME':
2380 def execute(self
, context
):
2381 node_active
= context
.active_node
2382 node_selected
= context
.selected_nodes
2385 if not (len(node_selected
) > 1):
2386 self
.report({'ERROR'}, "2 nodes must be selected at least")
2387 return {'CANCELLED'}
2389 # Check if active node is in the selection
2390 selected_node_names
= [n
.name
for n
in node_selected
]
2391 if node_active
.name
not in selected_node_names
:
2392 self
.report({'ERROR'}, "No active node")
2393 return {'CANCELLED'}
2395 # Get nodes in selection by type
2396 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2398 if not (len(valid_nodes
) > 1) and node_active
:
2399 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2400 return {'CANCELLED'}
2402 if len(valid_nodes
) != len(node_selected
):
2403 # Report nodes that are not valid
2404 valid_node_names
= [n
.name
for n
in valid_nodes
]
2405 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2406 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2408 # Reference original
2410 #node_selected_names = [n.name for n in node_selected]
2415 # Deselect all nodes
2416 for i
in node_selected
:
2419 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2420 # Run through all other nodes
2421 for node
in valid_nodes
[1:]:
2423 # Check for frame node
2424 parent
= node
.parent
if node
.parent
else None
2425 node_loc
= [node
.location
.x
, node
.location
.y
]
2427 # Select original to duplicate
2430 # Duplicate selected node
2431 bpy
.ops
.node
.duplicate()
2432 new_node
= context
.selected_nodes
[0]
2435 new_node
.select
= False
2437 # Properties to copy
2438 node_tree
= node
.id_data
2439 props_to_copy
= 'bl_idname name location height width'.split(' ')
2443 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2444 for i
in (i
for i
in mappings
if i
.is_linked
):
2446 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2449 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2450 props_to_copy
.pop(0)
2452 for prop
in props_to_copy
:
2453 setattr(new_node
, prop
, props
[prop
])
2455 # Get the node tree to remove the old node
2456 nodes
= node_tree
.nodes
2458 new_node
.name
= props
['name']
2461 new_node
.parent
= parent
2462 new_node
.location
= node_loc
2464 for str_from
, str_to
in reconnections
:
2465 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2467 success_names
.append(new_node
.name
)
2470 node_tree
.nodes
.active
= orig
2471 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2475 class NWCopyLabel(Operator
, NWBase
):
2476 bl_idname
= "node.nw_copy_label"
2477 bl_label
= "Copy Label"
2478 bl_options
= {'REGISTER', 'UNDO'}
2480 option
: EnumProperty(
2482 description
="Source of name of label",
2484 ('FROM_ACTIVE', 'from active', 'from active node',),
2485 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2486 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2490 def execute(self
, context
):
2491 nodes
, links
= get_nodes_links(context
)
2492 option
= self
.option
2493 active
= nodes
.active
2494 if option
== 'FROM_ACTIVE':
2496 src_label
= active
.label
2497 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2498 node
.label
= src_label
2499 elif option
== 'FROM_NODE':
2500 selected
= [n
for n
in nodes
if n
.select
]
2501 for node
in selected
:
2502 for input in node
.inputs
:
2504 src
= input.links
[0].from_node
2505 node
.label
= src
.label
2507 elif option
== 'FROM_SOCKET':
2508 selected
= [n
for n
in nodes
if n
.select
]
2509 for node
in selected
:
2510 for input in node
.inputs
:
2512 src
= input.links
[0].from_socket
2513 node
.label
= src
.name
2519 class NWClearLabel(Operator
, NWBase
):
2520 bl_idname
= "node.nw_clear_label"
2521 bl_label
= "Clear Label"
2522 bl_options
= {'REGISTER', 'UNDO'}
2524 option
: BoolProperty()
2526 def execute(self
, context
):
2527 nodes
, links
= get_nodes_links(context
)
2528 for node
in [n
for n
in nodes
if n
.select
]:
2533 def invoke(self
, context
, event
):
2535 return self
.execute(context
)
2537 return context
.window_manager
.invoke_confirm(self
, event
)
2540 class NWModifyLabels(Operator
, NWBase
):
2541 """Modify Labels of all selected nodes"""
2542 bl_idname
= "node.nw_modify_labels"
2543 bl_label
= "Modify Labels"
2544 bl_options
= {'REGISTER', 'UNDO'}
2546 prepend
: StringProperty(
2547 name
="Add to Beginning"
2549 append
: StringProperty(
2552 replace_from
: StringProperty(
2553 name
="Text to Replace"
2555 replace_to
: StringProperty(
2559 def execute(self
, context
):
2560 nodes
, links
= get_nodes_links(context
)
2561 for node
in [n
for n
in nodes
if n
.select
]:
2562 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2566 def invoke(self
, context
, event
):
2570 return context
.window_manager
.invoke_props_dialog(self
)
2573 class NWAddTextureSetup(Operator
, NWBase
):
2574 bl_idname
= "node.nw_add_texture"
2575 bl_label
= "Texture Setup"
2576 bl_description
= "Add Texture Node Setup to Selected Shaders"
2577 bl_options
= {'REGISTER', 'UNDO'}
2579 add_mapping
: BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2582 def poll(cls
, context
):
2584 if nw_check(context
):
2585 space
= context
.space_data
2586 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2590 def execute(self
, context
):
2591 nodes
, links
= get_nodes_links(context
)
2592 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2593 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2594 selected_nodes
= [n
for n
in nodes
if n
.select
]
2595 for t_node
in selected_nodes
:
2599 for index
, i
in enumerate(t_node
.inputs
):
2605 locx
= t_node
.location
.x
2606 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2608 xoffset
= [500, 700]
2610 if t_node
.type in texture_types
+ ['MAPPING']:
2611 xoffset
= [290, 500]
2615 image_type
= 'ShaderNodeTexImage'
2617 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2618 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2619 if t_node
.type == 'BACKGROUND':
2620 image_type
= 'ShaderNodeTexEnvironment'
2623 tex
= nodes
.new(image_type
)
2624 tex
.location
= [locx
- 200, locy
+ 112]
2626 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2628 t_node
.select
= False
2629 if self
.add_mapping
or is_texture
:
2630 if t_node
.type != 'MAPPING':
2631 m
= nodes
.new('ShaderNodeMapping')
2632 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2636 coord
= nodes
.new('ShaderNodeTexCoord')
2637 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2640 links
.new(m
.outputs
[0], tex
.inputs
[0])
2641 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2644 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2645 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2647 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2651 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2652 bl_idname
= "node.nw_add_textures_for_principled"
2653 bl_label
= "Principled Texture Setup"
2654 bl_description
= "Add Texture Node Setup for Principled BSDF"
2655 bl_options
= {'REGISTER', 'UNDO'}
2657 directory
: StringProperty(
2661 description
='Folder to search in for image files'
2663 files
: CollectionProperty(
2664 type=bpy
.types
.OperatorFileListElement
,
2665 options
={'HIDDEN', 'SKIP_SAVE'}
2674 def poll(cls
, context
):
2676 if nw_check(context
):
2677 space
= context
.space_data
2678 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2682 def execute(self
, context
):
2683 # Check if everything is ok
2684 if not self
.directory
:
2685 self
.report({'INFO'}, 'No Folder Selected')
2686 return {'CANCELLED'}
2687 if not self
.files
[:]:
2688 self
.report({'INFO'}, 'No Files Selected')
2689 return {'CANCELLED'}
2691 nodes
, links
= get_nodes_links(context
)
2692 active_node
= nodes
.active
2693 if not active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled':
2694 self
.report({'INFO'}, 'Select Principled BSDF')
2695 return {'CANCELLED'}
2698 def split_into__components(fname
):
2699 # Split filename into components
2700 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2702 fname
= path
.splitext(fname
)[0]
2704 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2705 # Seperate CamelCase by space
2706 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2707 # Replace common separators with SPACE
2708 seperators
= ['_', '.', '-', '__', '--', '#']
2709 for sep
in seperators
:
2710 fname
= fname
.replace(sep
, ' ')
2712 components
= fname
.split(' ')
2713 components
= [c
.lower() for c
in components
]
2716 # Filter textures names for texturetypes in filenames
2717 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2718 tags
= context
.user_preferences
.addons
[__name__
].preferences
.principled_tags
2719 normal_abbr
= tags
.normal
.split(' ')
2720 bump_abbr
= tags
.bump
.split(' ')
2721 gloss_abbr
= tags
.gloss
.split(' ')
2722 rough_abbr
= tags
.rough
.split(' ')
2724 ['Displacement', tags
.displacement
.split(' '), None],
2725 ['Base Color', tags
.base_color
.split(' '), None],
2726 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2727 ['Metallic', tags
.metallic
.split(' '), None],
2728 ['Specular', tags
.specular
.split(' '), None],
2729 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2730 ['Normal', normal_abbr
+ bump_abbr
, None],
2733 # Look through texture_types and set value as filename of first matched file
2734 def match_files_to_socket_names():
2735 for sname
in socketnames
:
2736 for file in self
.files
:
2738 filenamecomponents
= split_into__components(fname
)
2739 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
2740 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
2745 match_files_to_socket_names()
2746 # Remove socketnames without found files
2747 socketnames
= [s
for s
in socketnames
if s
[2]
2748 and path
.exists(self
.directory
+s
[2])]
2750 self
.report({'INFO'}, 'No matching images found')
2751 print('No matching images found')
2752 return {'CANCELLED'}
2755 print('\nMatched Textures:')
2759 roughness_node
= None
2760 for i
, sname
in enumerate(socketnames
):
2761 print(i
, sname
[0], sname
[2])
2763 # DISPLACEMENT NODES
2764 if sname
[0] == 'Displacement':
2765 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
2766 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2767 disp_texture
.image
= img
2768 disp_texture
.label
= 'Displacement'
2769 disp_texture
.color_space
= 'NONE'
2771 # Add displacement offset nodes
2772 math_sub
= nodes
.new(type='ShaderNodeMath')
2773 math_sub
.operation
= 'SUBTRACT'
2774 math_sub
.label
= 'Offset'
2775 math_sub
.location
= active_node
.location
+ Vector((0, -560))
2776 math_mul
= nodes
.new(type='ShaderNodeMath')
2777 math_mul
.operation
= 'MULTIPLY'
2778 math_mul
.label
= 'Strength'
2779 math_mul
.location
= math_sub
.location
+ Vector((200, 0))
2780 link
= links
.new(math_mul
.inputs
[0], math_sub
.outputs
[0])
2781 link
= links
.new(math_sub
.inputs
[0], disp_texture
.outputs
[0])
2783 # Turn on true displacement in the material
2784 # Too complicated for now
2787 # Frame. Does not update immediatly
2788 # Seems to need an editor redraw
2789 frame = nodes.new(type='NodeFrame')
2790 frame.label = 'Displacement'
2791 math_sub.parent = frame
2792 math_mul.parent = frame
2797 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
2799 if not output_node
[0].inputs
[2].is_linked
:
2800 link
= links
.new(output_node
[0].inputs
[2], math_mul
.outputs
[0])
2804 if not active_node
.inputs
[sname
[0]].is_linked
:
2805 # No texture node connected -> add texture node with new image
2806 texture_node
= nodes
.new(type='ShaderNodeTexImage')
2807 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2808 texture_node
.image
= img
2811 if sname
[0] == 'Normal':
2812 # Test if new texture node is normal or bump map
2813 fname_components
= split_into__components(sname
[2])
2814 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
2815 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
2817 # If Normal add normal node in between
2818 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
2819 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
2821 # If Bump add bump node in between
2822 normal_node
= nodes
.new(type='ShaderNodeBump')
2823 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
2825 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
2826 normal_node_texture
= texture_node
2828 elif sname
[0] == 'Roughness':
2829 # Test if glossy or roughness map
2830 fname_components
= split_into__components(sname
[2])
2831 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
2832 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
2835 # If Roughness nothing to to
2836 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2839 # If Gloss Map add invert node
2840 invert_node
= nodes
.new(type='ShaderNodeInvert')
2841 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
2843 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
2844 roughness_node
= texture_node
2847 # This is a simple connection Texture --> Input slot
2848 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2850 # Use non-color for all but 'Base Color' Textures
2851 if not sname
[0] in ['Base Color']:
2852 texture_node
.color_space
= 'NONE'
2855 # If already texture connected. add to node list for alignment
2856 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
2858 # This are all connected texture nodes
2859 texture_nodes
.append(texture_node
)
2860 texture_node
.label
= sname
[0]
2863 texture_nodes
.append(disp_texture
)
2866 for i
, texture_node
in enumerate(texture_nodes
):
2867 offset
= Vector((-400, (i
* -260) + 200))
2868 texture_node
.location
= active_node
.location
+ offset
2871 # Extra alignment if normal node was added
2872 normal_node
.location
= normal_node_texture
.location
+ Vector((200, 0))
2875 # Alignment of invert node if glossy map
2876 invert_node
.location
= roughness_node
.location
+ Vector((200, 0))
2878 # Add texture input + mapping
2879 mapping
= nodes
.new(type='ShaderNodeMapping')
2880 mapping
.location
= active_node
.location
+ Vector((-900, 0))
2881 if len(texture_nodes
) > 1:
2882 # If more than one texture add reroute node in between
2883 reroute
= nodes
.new(type='NodeReroute')
2884 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
2885 reroute
.location
= tex_coords
+ Vector((-50, -120))
2886 for texture_node
in texture_nodes
:
2887 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
2888 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
2890 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
2892 # Connect texture_coordiantes to mapping node
2893 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
2894 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
2895 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
2898 active_node
.select
= False
2901 force_update(context
)
2905 class NWAddReroutes(Operator
, NWBase
):
2906 """Add Reroute Nodes and link them to outputs of selected nodes"""
2907 bl_idname
= "node.nw_add_reroutes"
2908 bl_label
= "Add Reroutes"
2909 bl_description
= "Add Reroutes to Outputs"
2910 bl_options
= {'REGISTER', 'UNDO'}
2912 option
: EnumProperty(
2915 ('ALL', 'to all', 'Add to all outputs'),
2916 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2917 ('LINKED', 'to linked', 'Add only to linked outputs'),
2921 def execute(self
, context
):
2922 tree_type
= context
.space_data
.node_tree
.type
2923 option
= self
.option
2924 nodes
, links
= get_nodes_links(context
)
2925 # output valid when option is 'all' or when 'loose' output has no links
2927 post_select
= [] # nodes to be selected after execution
2928 # create reroutes and recreate links
2929 for node
in [n
for n
in nodes
if n
.select
]:
2934 # unhide 'REROUTE' nodes to avoid issues with location.y
2935 if node
.type == 'REROUTE':
2937 # When node is hidden - width_hidden not usable.
2938 # Hack needed to calculate real width
2940 bpy
.ops
.node
.select_all(action
='DESELECT')
2941 helper
= nodes
.new('NodeReroute')
2942 helper
.select
= True
2944 # resize node and helper to zero. Then check locations to calculate width
2945 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2946 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2947 # restore node location
2948 node
.location
= x
, y
2951 # only helper is selected now
2952 bpy
.ops
.node
.delete()
2953 x
= node
.location
.x
+ width
+ 20.0
2954 if node
.type != 'REROUTE':
2958 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2959 for out_i
, output
in enumerate(node
.outputs
):
2960 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2961 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2962 if node
.type != 'R_LAYERS':
2964 else: # if 'R_LAYERS' check if output represent used render pass
2965 node_scene
= node
.scene
2966 node_layer
= node
.layer
2967 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2968 if output
.name
== 'Alpha':
2971 # check entries in global 'rl_outputs' variable
2972 #for render_pass, output_name, exr_name, in_internal, in_cycles in rl_outputs:
2973 for rlo
in rl_outputs
:
2974 if output
.name
== rlo
.output_name
or output
.name
== rlo
.exr_output_name
:
2975 pass_used
= getattr(node_scene
.render
.layers
[node_layer
], rlo
.render_pass
)
2978 valid
= ((option
== 'ALL') or
2979 (option
== 'LOOSE' and not output
.links
) or
2980 (option
== 'LINKED' and output
.links
))
2981 # Add reroutes only if valid, but offset location in all cases.
2983 n
= nodes
.new('NodeReroute')
2985 for link
in output
.links
:
2986 links
.new(n
.outputs
[0], link
.to_socket
)
2987 links
.new(output
, n
.inputs
[0])
2989 post_select
.append(n
)
2993 # disselect the node so that after execution of script only newly created nodes are selected
2995 # nicer reroutes distribution along y when node.hide
2997 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2998 for reroute
in [r
for r
in nodes
if r
.select
]:
2999 reroute
.location
.y
-= y_translate
3000 for node
in post_select
:
3006 class NWLinkActiveToSelected(Operator
, NWBase
):
3007 """Link active node to selected nodes basing on various criteria"""
3008 bl_idname
= "node.nw_link_active_to_selected"
3009 bl_label
= "Link Active Node to Selected"
3010 bl_options
= {'REGISTER', 'UNDO'}
3012 replace
: BoolProperty()
3013 use_node_name
: BoolProperty()
3014 use_outputs_names
: BoolProperty()
3017 def poll(cls
, context
):
3019 if nw_check(context
):
3020 if context
.active_node
is not None:
3021 if context
.active_node
.select
:
3025 def execute(self
, context
):
3026 nodes
, links
= get_nodes_links(context
)
3027 replace
= self
.replace
3028 use_node_name
= self
.use_node_name
3029 use_outputs_names
= self
.use_outputs_names
3030 active
= nodes
.active
3031 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3032 outputs
= [] # Only usable outputs of active nodes will be stored here.
3033 for out
in active
.outputs
:
3034 if active
.type != 'R_LAYERS':
3037 # 'R_LAYERS' node type needs special handling.
3038 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3039 # Only outputs that represent used passes should be taken into account
3040 # Check if pass represented by output is used.
3041 # global 'rl_outputs' list will be used for that
3042 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3043 pass_used
= False # initial value. Will be set to True if pass is used
3044 if out
.name
== 'Alpha':
3045 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3047 elif out
.name
== out_name
:
3048 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3049 pass_used
= getattr(active
.scene
.render
.layers
[active
.layer
], render_pass
)
3053 doit
= True # Will be changed to False when links successfully added to previous output.
3056 for node
in selected
:
3057 dst_name
= node
.name
# Will be compared with src_name if needed.
3058 # When node has label - use it as dst_name
3060 dst_name
= node
.label
3061 valid
= True # Initial value. Will be changed to False if names don't match.
3062 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3064 # Set src_name to source node name or label
3065 src_name
= active
.name
3067 src_name
= active
.label
3068 elif use_outputs_names
:
3069 src_name
= (out
.name
, )
3070 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3071 if out
.name
in {out_name
, exr_name
}:
3072 src_name
= (out_name
, exr_name
)
3073 if dst_name
not in src_name
:
3076 for input in node
.inputs
:
3077 if input.type == out
.type or node
.type == 'REROUTE':
3078 if replace
or not input.is_linked
:
3079 links
.new(out
, input)
3080 if not use_node_name
and not use_outputs_names
:
3087 class NWAlignNodes(Operator
, NWBase
):
3088 '''Align the selected nodes neatly in a row/column'''
3089 bl_idname
= "node.nw_align_nodes"
3090 bl_label
= "Align Nodes"
3091 bl_options
= {'REGISTER', 'UNDO'}
3092 margin
: IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3094 def execute(self
, context
):
3095 nodes
, links
= get_nodes_links(context
)
3096 margin
= self
.margin
3100 if node
.select
and node
.type != 'FRAME':
3101 selection
.append(node
)
3103 # If no nodes are selected, align all nodes
3107 elif nodes
.active
in selection
:
3108 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3110 # Check if nodes should be layed out horizontally or vertically
3111 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3112 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3113 x_range
= max(x_locs
) - min(x_locs
)
3114 y_range
= max(y_locs
) - min(y_locs
)
3115 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3116 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3117 horizontal
= x_range
> y_range
3119 # Sort selection by location of node mid-point
3121 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3123 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3127 for node
in selection
:
3128 current_margin
= margin
3129 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3132 node
.location
.x
= current_pos
3133 current_pos
+= current_margin
+ node
.dimensions
.x
3134 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3136 node
.location
.y
= current_pos
3137 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3138 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3140 # If active node is selected, center nodes around it
3141 if active_loc
is not None:
3142 active_loc_diff
= active_loc
- nodes
.active
.location
3143 for node
in selection
:
3144 node
.location
+= active_loc_diff
3145 else: # Position nodes centered around where they used to be
3146 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
])
3147 new_mid
= (max(locs
) + min(locs
)) / 2
3148 for node
in selection
:
3150 node
.location
.x
+= (mid_x
- new_mid
)
3152 node
.location
.y
+= (mid_y
- new_mid
)
3157 class NWSelectParentChildren(Operator
, NWBase
):
3158 bl_idname
= "node.nw_select_parent_child"
3159 bl_label
= "Select Parent or Children"
3160 bl_options
= {'REGISTER', 'UNDO'}
3162 option
: EnumProperty(
3165 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3166 ('CHILD', 'Select Children', 'Select members of selected frame'),
3170 def execute(self
, context
):
3171 nodes
, links
= get_nodes_links(context
)
3172 option
= self
.option
3173 selected
= [node
for node
in nodes
if node
.select
]
3174 if option
== 'PARENT':
3175 for sel
in selected
:
3178 parent
.select
= True
3179 else: # option == 'CHILD'
3180 for sel
in selected
:
3181 children
= [node
for node
in nodes
if node
.parent
== sel
]
3182 for kid
in children
:
3188 class NWDetachOutputs(Operator
, NWBase
):
3189 """Detach outputs of selected node leaving inputs linked"""
3190 bl_idname
= "node.nw_detach_outputs"
3191 bl_label
= "Detach Outputs"
3192 bl_options
= {'REGISTER', 'UNDO'}
3194 def execute(self
, context
):
3195 nodes
, links
= get_nodes_links(context
)
3196 selected
= context
.selected_nodes
3197 bpy
.ops
.node
.duplicate_move_keep_inputs()
3198 new_nodes
= context
.selected_nodes
3199 bpy
.ops
.node
.select_all(action
="DESELECT")
3200 for node
in selected
:
3202 bpy
.ops
.node
.delete_reconnect()
3203 for new_node
in new_nodes
:
3204 new_node
.select
= True
3205 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3210 class NWLinkToOutputNode(Operator
, NWBase
):
3211 """Link to Composite node or Material Output node"""
3212 bl_idname
= "node.nw_link_out"
3213 bl_label
= "Connect to Output"
3214 bl_options
= {'REGISTER', 'UNDO'}
3217 def poll(cls
, context
):
3219 if nw_check(context
):
3220 if context
.active_node
is not None:
3221 for out
in context
.active_node
.outputs
:
3227 def execute(self
, context
):
3228 nodes
, links
= get_nodes_links(context
)
3229 active
= nodes
.active
3232 tree_type
= context
.space_data
.tree_type
3233 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3234 output_types_compo
= ['COMPOSITE']
3235 output_types_blender_mat
= ['OUTPUT']
3236 output_types_textures
= ['OUTPUT']
3237 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3239 if node
.type in output_types
:
3243 bpy
.ops
.node
.select_all(action
="DESELECT")
3244 if tree_type
== 'ShaderNodeTree':
3245 if is_cycles_or_eevee(context
):
3246 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3248 output_node
= nodes
.new('ShaderNodeOutput')
3249 elif tree_type
== 'CompositorNodeTree':
3250 output_node
= nodes
.new('CompositorNodeComposite')
3251 elif tree_type
== 'TextureNodeTree':
3252 output_node
= nodes
.new('TextureNodeOutput')
3253 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3254 output_node
.location
.y
= active
.location
.y
3255 if (output_node
and active
.outputs
):
3256 for i
, output
in enumerate(active
.outputs
):
3260 for i
, output
in enumerate(active
.outputs
):
3261 if output
.type == output_node
.inputs
[0].type and not output
.hide
:
3266 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3267 if active
.outputs
[output_index
].name
== 'Volume':
3269 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3271 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3273 force_update(context
) # viewport render does not update
3278 class NWMakeLink(Operator
, NWBase
):
3279 """Make a link from one socket to another"""
3280 bl_idname
= 'node.nw_make_link'
3281 bl_label
= 'Make Link'
3282 bl_options
= {'REGISTER', 'UNDO'}
3283 from_socket
: IntProperty()
3284 to_socket
: IntProperty()
3286 def execute(self
, context
):
3287 nodes
, links
= get_nodes_links(context
)
3289 n1
= nodes
[context
.scene
.NWLazySource
]
3290 n2
= nodes
[context
.scene
.NWLazyTarget
]
3292 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3294 force_update(context
)
3299 class NWCallInputsMenu(Operator
, NWBase
):
3300 """Link from this output"""
3301 bl_idname
= 'node.nw_call_inputs_menu'
3302 bl_label
= 'Make Link'
3303 bl_options
= {'REGISTER', 'UNDO'}
3304 from_socket
: IntProperty()
3306 def execute(self
, context
):
3307 nodes
, links
= get_nodes_links(context
)
3309 context
.scene
.NWSourceSocket
= self
.from_socket
3311 n1
= nodes
[context
.scene
.NWLazySource
]
3312 n2
= nodes
[context
.scene
.NWLazyTarget
]
3313 if len(n2
.inputs
) > 1:
3314 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3315 elif len(n2
.inputs
) == 1:
3316 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3320 class NWAddSequence(Operator
, ImportHelper
):
3321 """Add an Image Sequence"""
3322 bl_idname
= 'node.nw_add_sequence'
3323 bl_label
= 'Import Image Sequence'
3324 bl_options
= {'REGISTER', 'UNDO'}
3326 directory
: StringProperty(
3329 filename
: StringProperty(
3332 files
: CollectionProperty(
3333 type=bpy
.types
.OperatorFileListElement
,
3334 options
={'HIDDEN', 'SKIP_SAVE'}
3337 def execute(self
, context
):
3338 nodes
, links
= get_nodes_links(context
)
3339 directory
= self
.directory
3340 filename
= self
.filename
3342 tree
= context
.space_data
.node_tree
3345 # print ("\nDIR:", directory)
3346 # print ("FN:", filename)
3347 # print ("Fs:", list(f.name for f in files), '\n')
3349 if tree
.type == 'SHADER':
3350 node_type
= "ShaderNodeTexImage"
3351 elif tree
.type == 'COMPOSITING':
3352 node_type
= "CompositorNodeImage"
3354 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3355 return {'CANCELLED'}
3357 if not files
[0].name
and not filename
:
3358 self
.report({'ERROR'}, "No file chosen")
3359 return {'CANCELLED'}
3360 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3361 # User has selected multiple files without an active one, or the active one is non-existant
3362 filename
= files
[0].name
3364 if not path
.exists(directory
+filename
):
3365 self
.report({'ERROR'}, filename
+" does not exist!")
3366 return {'CANCELLED'}
3368 without_ext
= '.'.join(filename
.split('.')[:-1])
3370 # if last digit isn't a number, it's not a sequence
3371 if not without_ext
[-1].isdigit():
3372 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3373 return {'CANCELLED'}
3376 extension
= filename
.split('.')[-1]
3377 reverse
= without_ext
[::-1] # reverse string
3380 for char
in reverse
:
3386 without_num
= without_ext
[:count_numbers
*-1]
3388 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3390 num_frames
= len(files
)
3392 nodes_list
= [node
for node
in nodes
]
3394 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3395 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3399 yloc
+= node_mid_pt(node
, 'y')
3400 yloc
= yloc
/len(nodes
)
3405 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3407 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3409 node
.label
= name_with_hashes
3411 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3412 img
.source
= 'SEQUENCE'
3413 img
.name
= name_with_hashes
3415 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3416 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
3417 image_user
.frame_duration
= num_frames
3422 class NWAddMultipleImages(Operator
, ImportHelper
):
3423 """Add multiple images at once"""
3424 bl_idname
= 'node.nw_add_multiple_images'
3425 bl_label
= 'Open Selected Images'
3426 bl_options
= {'REGISTER', 'UNDO'}
3427 directory
: StringProperty(
3430 files
: CollectionProperty(
3431 type=bpy
.types
.OperatorFileListElement
,
3432 options
={'HIDDEN', 'SKIP_SAVE'}
3435 def execute(self
, context
):
3436 nodes
, links
= get_nodes_links(context
)
3438 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3440 if context
.space_data
.node_tree
.type == 'SHADER':
3441 node_type
= "ShaderNodeTexImage"
3442 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3443 node_type
= "CompositorNodeImage"
3445 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3446 return {'CANCELLED'}
3449 for f
in self
.files
:
3452 node
= nodes
.new(node_type
)
3453 new_nodes
.append(node
)
3456 node
.width_hidden
= 100
3457 node
.location
.x
= xloc
3458 node
.location
.y
= yloc
3461 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3464 # shift new nodes up to center of tree
3465 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3467 if node
in new_nodes
:
3469 node
.location
.y
+= (list_size
/2)
3475 class NWViewerFocus(bpy
.types
.Operator
):
3476 """Set the viewer tile center to the mouse position"""
3477 bl_idname
= "node.nw_viewer_focus"
3478 bl_label
= "Viewer Focus"
3480 x
: bpy
.props
.IntProperty()
3481 y
: bpy
.props
.IntProperty()
3484 def poll(cls
, context
):
3485 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3487 def execute(self
, context
):
3490 def invoke(self
, context
, event
):
3491 render
= context
.scene
.render
3492 space
= context
.space_data
3493 percent
= render
.resolution_percentage
*0.01
3495 nodes
, links
= get_nodes_links(context
)
3496 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3499 mlocx
= event
.mouse_region_x
3500 mlocy
= event
.mouse_region_y
3501 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3503 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3504 region_x
= context
.region
.width
3505 region_y
= context
.region
.height
3507 region_center_x
= context
.region
.width
/ 2
3508 region_center_y
= context
.region
.height
/ 2
3510 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3511 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3513 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_x
3514 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_y
3516 margin_x
= region_center_x
- backdrop_center_x
3517 margin_y
= region_center_y
- backdrop_center_y
3519 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3520 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3522 for node
in viewers
:
3523 node
.center_x
= abs_mouse_x
3524 node
.center_y
= abs_mouse_y
3526 return {'PASS_THROUGH'}
3528 return self
.execute(context
)
3531 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3532 """Save the current viewer node to an image file"""
3533 bl_idname
= "node.nw_save_viewer"
3534 bl_label
= "Save This Image"
3535 filepath
: StringProperty(subtype
="FILE_PATH")
3536 filename_ext
: EnumProperty(
3538 description
="Choose the file format to save to",
3539 items
=(('.bmp', "PNG", ""),
3540 ('.rgb', 'IRIS', ""),
3541 ('.png', 'PNG', ""),
3542 ('.jpg', 'JPEG', ""),
3543 ('.jp2', 'JPEG2000', ""),
3544 ('.tga', 'TARGA', ""),
3545 ('.cin', 'CINEON', ""),
3546 ('.dpx', 'DPX', ""),
3547 ('.exr', 'OPEN_EXR', ""),
3548 ('.hdr', 'HDR', ""),
3549 ('.tif', 'TIFF', "")),
3554 def poll(cls
, context
):
3556 if nw_check(context
):
3557 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3558 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3559 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3563 def execute(self
, context
):
3580 basename
, ext
= path
.splitext(fp
)
3581 old_render_format
= context
.scene
.render
.image_settings
.file_format
3582 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3583 context
.area
.type = "IMAGE_EDITOR"
3584 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3585 context
.area
.spaces
[0].image
.save_render(fp
)
3586 context
.area
.type = "NODE_EDITOR"
3587 context
.scene
.render
.image_settings
.file_format
= old_render_format
3591 class NWResetNodes(bpy
.types
.Operator
):
3592 """Reset Nodes in Selection"""
3593 bl_idname
= "node.nw_reset_nodes"
3594 bl_label
= "Reset Nodes"
3595 bl_options
= {'REGISTER', 'UNDO'}
3598 def poll(cls
, context
):
3599 space
= context
.space_data
3600 return space
.type == 'NODE_EDITOR'
3602 def execute(self
, context
):
3603 node_active
= context
.active_node
3604 node_selected
= context
.selected_nodes
3605 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3607 # Check if one node is selected at least
3608 if not (len(node_selected
) > 0):
3609 self
.report({'ERROR'}, "1 node must be selected at least")
3610 return {'CANCELLED'}
3612 active_node_name
= node_active
.name
if node_active
.select
else None
3613 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3615 # Create output lists
3616 selected_node_names
= [n
.name
for n
in node_selected
]
3619 # Reset all valid children in a frame
3620 node_active_is_frame
= False
3621 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3622 node_tree
= node_active
.id_data
3623 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3625 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3626 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3627 node_active_is_frame
= True
3629 # Check if valid nodes in selection
3630 if not (len(valid_nodes
) > 0):
3631 # Check for frames only
3632 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3633 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3634 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3636 self
.report({'ERROR'}, "No valid node(s) in selection")
3637 return {'CANCELLED'}
3639 # Report nodes that are not valid
3640 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3641 valid_node_names
= [n
.name
for n
in valid_nodes
]
3642 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3643 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3645 # Deselect all nodes
3646 for i
in node_selected
:
3649 # Run through all valid nodes
3650 for node
in valid_nodes
:
3652 parent
= node
.parent
if node
.parent
else None
3653 node_loc
= [node
.location
.x
, node
.location
.y
]
3655 node_tree
= node
.id_data
3656 props_to_copy
= 'bl_idname name location height width'.split(' ')
3659 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3660 for i
in (i
for i
in mappings
if i
.is_linked
):
3662 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3664 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3666 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3667 props_to_copy
.pop(0)
3669 for prop
in props_to_copy
:
3670 setattr(new_node
, prop
, props
[prop
])
3672 nodes
= node_tree
.nodes
3674 new_node
.name
= props
['name']
3677 new_node
.parent
= parent
3678 new_node
.location
= node_loc
3680 for str_from
, str_to
in reconnections
:
3681 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3683 new_node
.select
= False
3684 success_names
.append(new_node
.name
)
3686 # Reselect all nodes
3687 if selected_node_names
and node_active_is_frame
is False:
3688 for i
in selected_node_names
:
3689 node_tree
.nodes
[i
].select
= True
3691 if active_node_name
is not None:
3692 node_tree
.nodes
[active_node_name
].select
= True
3693 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3695 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3703 def drawlayout(context
, layout
, mode
='non-panel'):
3704 tree_type
= context
.space_data
.tree_type
3706 col
= layout
.column(align
=True)
3707 col
.menu(NWMergeNodesMenu
.bl_idname
)
3710 col
= layout
.column(align
=True)
3711 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3714 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3715 col
= layout
.column(align
=True)
3716 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3717 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3720 col
= layout
.column(align
=True)
3721 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3722 col
.operator(NWSwapLinks
.bl_idname
)
3723 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3726 col
= layout
.column(align
=True)
3727 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3728 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3731 col
= layout
.column(align
=True)
3733 row
= col
.row(align
=True)
3734 row
.operator(NWClearLabel
.bl_idname
).option
= True
3735 row
.operator(NWModifyLabels
.bl_idname
)
3737 col
.operator(NWClearLabel
.bl_idname
).option
= True
3738 col
.operator(NWModifyLabels
.bl_idname
)
3739 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3741 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
3744 col
= layout
.column(align
=True)
3745 if tree_type
== 'CompositorNodeTree':
3746 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
3747 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
3750 col
= layout
.column(align
=True)
3751 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3754 col
= layout
.column(align
=True)
3755 col
.operator(NWAlignNodes
.bl_idname
, icon
='ALIGN')
3758 col
= layout
.column(align
=True)
3759 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
3763 class NodeWranglerPanel(Panel
, NWBase
):
3764 bl_idname
= "NODE_PT_nw_node_wrangler"
3765 bl_space_type
= 'NODE_EDITOR'
3766 bl_label
= "Node Wrangler"
3767 bl_region_type
= "TOOLS"
3768 bl_category
= "Node Wrangler"
3770 prepend
: StringProperty(
3773 append
: StringProperty()
3774 remove
: StringProperty()
3776 def draw(self
, context
):
3777 self
.layout
.label(text
="(Quick access: Ctrl+Space)")
3778 drawlayout(context
, self
.layout
, mode
='panel')
3784 class NodeWranglerMenu(Menu
, NWBase
):
3785 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
3786 bl_label
= "Node Wrangler"
3788 def draw(self
, context
):
3789 drawlayout(context
, self
.layout
)
3792 class NWMergeNodesMenu(Menu
, NWBase
):
3793 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
3794 bl_label
= "Merge Selected Nodes"
3796 def draw(self
, context
):
3797 type = context
.space_data
.tree_type
3798 layout
= self
.layout
3799 if type == 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3800 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
3801 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
3802 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
3803 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
3805 props
.merge_type
= 'ZCOMBINE'
3806 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
3808 props
.merge_type
= 'ALPHAOVER'
3811 class NWMergeShadersMenu(Menu
, NWBase
):
3812 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
3813 bl_label
= "Merge Selected Nodes using Shaders"
3815 def draw(self
, context
):
3816 layout
= self
.layout
3817 for type in ('MIX', 'ADD'):
3818 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
3820 props
.merge_type
= 'SHADER'
3823 class NWMergeMixMenu(Menu
, NWBase
):
3824 bl_idname
= "NODE_MT_nw_merge_mix_menu"
3825 bl_label
= "Merge Selected Nodes using Mix"
3827 def draw(self
, context
):
3828 layout
= self
.layout
3829 for type, name
, description
in blend_types
:
3830 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3832 props
.merge_type
= 'MIX'
3835 class NWConnectionListOutputs(Menu
, NWBase
):
3836 bl_idname
= "NODE_MT_nw_connection_list_out"
3839 def draw(self
, context
):
3840 layout
= self
.layout
3841 nodes
, links
= get_nodes_links(context
)
3843 n1
= nodes
[context
.scene
.NWLazySource
]
3845 if n1
.type == "R_LAYERS":
3847 for o
in n1
.outputs
:
3848 if o
.enabled
: # Check which passes the render layer has enabled
3849 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3853 for o
in n1
.outputs
:
3854 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3858 class NWConnectionListInputs(Menu
, NWBase
):
3859 bl_idname
= "NODE_MT_nw_connection_list_in"
3862 def draw(self
, context
):
3863 layout
= self
.layout
3864 nodes
, links
= get_nodes_links(context
)
3866 n2
= nodes
[context
.scene
.NWLazyTarget
]
3870 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3871 op
.from_socket
= context
.scene
.NWSourceSocket
3872 op
.to_socket
= index
3876 class NWMergeMathMenu(Menu
, NWBase
):
3877 bl_idname
= "NODE_MT_nw_merge_math_menu"
3878 bl_label
= "Merge Selected Nodes using Math"
3880 def draw(self
, context
):
3881 layout
= self
.layout
3882 for type, name
, description
in operations
:
3883 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3885 props
.merge_type
= 'MATH'
3888 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3889 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3890 bl_label
= "Batch Change Selected Nodes"
3892 def draw(self
, context
):
3893 layout
= self
.layout
3894 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3895 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3898 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3899 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3900 bl_label
= "Batch Change Blend Type"
3902 def draw(self
, context
):
3903 layout
= self
.layout
3904 for type, name
, description
in blend_types
:
3905 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3906 props
.blend_type
= type
3907 props
.operation
= 'CURRENT'
3910 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3911 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3912 bl_label
= "Batch Change Math Operation"
3914 def draw(self
, context
):
3915 layout
= self
.layout
3916 for type, name
, description
in operations
:
3917 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3918 props
.blend_type
= 'CURRENT'
3919 props
.operation
= type
3922 class NWCopyToSelectedMenu(Menu
, NWBase
):
3923 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3924 bl_label
= "Copy to Selected"
3926 def draw(self
, context
):
3927 layout
= self
.layout
3928 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3929 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3932 class NWCopyLabelMenu(Menu
, NWBase
):
3933 bl_idname
= "NODE_MT_nw_copy_label_menu"
3934 bl_label
= "Copy Label"
3936 def draw(self
, context
):
3937 layout
= self
.layout
3938 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3939 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3940 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3943 class NWAddReroutesMenu(Menu
, NWBase
):
3944 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3945 bl_label
= "Add Reroutes"
3946 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3948 def draw(self
, context
):
3949 layout
= self
.layout
3950 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3951 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3952 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3955 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3956 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3957 bl_label
= "Link Active to Selected"
3959 def draw(self
, context
):
3960 layout
= self
.layout
3961 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3962 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3963 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3966 class NWLinkStandardMenu(Menu
, NWBase
):
3967 bl_idname
= "NODE_MT_nw_link_standard_menu"
3968 bl_label
= "To All Selected"
3970 def draw(self
, context
):
3971 layout
= self
.layout
3972 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3973 props
.replace
= False
3974 props
.use_node_name
= False
3975 props
.use_outputs_names
= False
3976 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3977 props
.replace
= True
3978 props
.use_node_name
= False
3979 props
.use_outputs_names
= False
3982 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3983 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3984 bl_label
= "Use Node Name/Label"
3986 def draw(self
, context
):
3987 layout
= self
.layout
3988 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3989 props
.replace
= False
3990 props
.use_node_name
= True
3991 props
.use_outputs_names
= False
3992 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3993 props
.replace
= True
3994 props
.use_node_name
= True
3995 props
.use_outputs_names
= False
3998 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3999 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
4000 bl_label
= "Use Outputs Names"
4002 def draw(self
, context
):
4003 layout
= self
.layout
4004 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
4005 props
.replace
= False
4006 props
.use_node_name
= False
4007 props
.use_outputs_names
= True
4008 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4009 props
.replace
= True
4010 props
.use_node_name
= False
4011 props
.use_outputs_names
= True
4014 class NWVertColMenu(bpy
.types
.Menu
):
4015 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
4016 bl_label
= "Vertex Colors"
4019 def poll(cls
, context
):
4021 if nw_check(context
):
4022 snode
= context
.space_data
4023 valid
= snode
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
)
4026 def draw(self
, context
):
4028 nodes
, links
= get_nodes_links(context
)
4029 mat
= context
.object.active_material
4032 for obj
in bpy
.data
.objects
:
4033 for slot
in obj
.material_slots
:
4034 if slot
.material
== mat
:
4038 if obj
.data
.vertex_colors
:
4039 for vcol
in obj
.data
.vertex_colors
:
4040 vcols
.append(vcol
.name
)
4041 vcols
= list(set(vcols
)) # get a unique list
4045 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4047 l
.label("No Vertex Color layers on objects with this material")
4050 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4051 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4052 bl_label
= "Switch Type to..."
4054 def draw(self
, context
):
4055 layout
= self
.layout
4056 tree
= context
.space_data
.node_tree
4057 if tree
.type == 'SHADER':
4058 if is_cycles_or_eevee(context
):
4059 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4060 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4061 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4062 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4063 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4064 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4065 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4066 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4068 layout
.menu(NWSwitchMatInputSubmenu
.bl_idname
)
4069 layout
.menu(NWSwitchMatOutputSubmenu
.bl_idname
)
4070 layout
.menu(NWSwitchMatColorSubmenu
.bl_idname
)
4071 layout
.menu(NWSwitchMatVectorSubmenu
.bl_idname
)
4072 layout
.menu(NWSwitchMatConverterSubmenu
.bl_idname
)
4073 layout
.menu(NWSwitchMatLayoutSubmenu
.bl_idname
)
4074 if tree
.type == 'COMPOSITING':
4075 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4076 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4077 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4078 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4079 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4080 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4081 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4082 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4083 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4084 if tree
.type == 'TEXTURE':
4085 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4086 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4087 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4088 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4089 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4090 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4091 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4092 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4095 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4096 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4099 def draw(self
, context
):
4100 layout
= self
.layout
4101 for ident
, node_type
, rna_name
in sorted(shaders_input_nodes_props
, key
=lambda k
: k
[2]):
4102 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4103 props
.to_type
= ident
4106 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4107 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4110 def draw(self
, context
):
4111 layout
= self
.layout
4112 for ident
, node_type
, rna_name
in sorted(shaders_output_nodes_props
, key
=lambda k
: k
[2]):
4113 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4114 props
.to_type
= ident
4117 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4118 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4121 def draw(self
, context
):
4122 layout
= self
.layout
4123 for ident
, node_type
, rna_name
in sorted(shaders_shader_nodes_props
, key
=lambda k
: k
[2]):
4124 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4125 props
.to_type
= ident
4128 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4129 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4130 bl_label
= "Texture"
4132 def draw(self
, context
):
4133 layout
= self
.layout
4134 for ident
, node_type
, rna_name
in sorted(shaders_texture_nodes_props
, key
=lambda k
: k
[2]):
4135 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4136 props
.to_type
= ident
4139 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4140 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4143 def draw(self
, context
):
4144 layout
= self
.layout
4145 for ident
, node_type
, rna_name
in sorted(shaders_color_nodes_props
, key
=lambda k
: k
[2]):
4146 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4147 props
.to_type
= ident
4150 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4151 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4154 def draw(self
, context
):
4155 layout
= self
.layout
4156 for ident
, node_type
, rna_name
in sorted(shaders_vector_nodes_props
, key
=lambda k
: k
[2]):
4157 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4158 props
.to_type
= ident
4161 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4162 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4163 bl_label
= "Converter"
4165 def draw(self
, context
):
4166 layout
= self
.layout
4167 for ident
, node_type
, rna_name
in sorted(shaders_converter_nodes_props
, key
=lambda k
: k
[2]):
4168 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4169 props
.to_type
= ident
4172 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4173 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4176 def draw(self
, context
):
4177 layout
= self
.layout
4178 for ident
, node_type
, rna_name
in sorted(shaders_layout_nodes_props
, key
=lambda k
: k
[2]):
4179 if node_type
!= 'FRAME':
4180 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4181 props
.to_type
= ident
4184 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4185 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4188 def draw(self
, context
):
4189 layout
= self
.layout
4190 for ident
, node_type
, rna_name
in sorted(compo_input_nodes_props
, key
=lambda k
: k
[2]):
4191 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4192 props
.to_type
= ident
4195 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4196 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4199 def draw(self
, context
):
4200 layout
= self
.layout
4201 for ident
, node_type
, rna_name
in sorted(compo_output_nodes_props
, key
=lambda k
: k
[2]):
4202 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4203 props
.to_type
= ident
4206 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4207 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4210 def draw(self
, context
):
4211 layout
= self
.layout
4212 for ident
, node_type
, rna_name
in sorted(compo_color_nodes_props
, key
=lambda k
: k
[2]):
4213 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4214 props
.to_type
= ident
4217 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4218 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4219 bl_label
= "Converter"
4221 def draw(self
, context
):
4222 layout
= self
.layout
4223 for ident
, node_type
, rna_name
in sorted(compo_converter_nodes_props
, key
=lambda k
: k
[2]):
4224 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4225 props
.to_type
= ident
4228 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4229 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4232 def draw(self
, context
):
4233 layout
= self
.layout
4234 for ident
, node_type
, rna_name
in sorted(compo_filter_nodes_props
, key
=lambda k
: k
[2]):
4235 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4236 props
.to_type
= ident
4239 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4240 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4243 def draw(self
, context
):
4244 layout
= self
.layout
4245 for ident
, node_type
, rna_name
in sorted(compo_vector_nodes_props
, key
=lambda k
: k
[2]):
4246 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4247 props
.to_type
= ident
4250 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4251 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4254 def draw(self
, context
):
4255 layout
= self
.layout
4256 for ident
, node_type
, rna_name
in sorted(compo_matte_nodes_props
, key
=lambda k
: k
[2]):
4257 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4258 props
.to_type
= ident
4261 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4262 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4263 bl_label
= "Distort"
4265 def draw(self
, context
):
4266 layout
= self
.layout
4267 for ident
, node_type
, rna_name
in sorted(compo_distort_nodes_props
, key
=lambda k
: k
[2]):
4268 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4269 props
.to_type
= ident
4272 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4273 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4276 def draw(self
, context
):
4277 layout
= self
.layout
4278 for ident
, node_type
, rna_name
in sorted(compo_layout_nodes_props
, key
=lambda k
: k
[2]):
4279 if node_type
!= 'FRAME':
4280 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4281 props
.to_type
= ident
4284 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4285 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4288 def draw(self
, context
):
4289 layout
= self
.layout
4290 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4291 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4292 props
.to_type
= ident
4295 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4296 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4299 def draw(self
, context
):
4300 layout
= self
.layout
4301 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4302 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4303 props
.to_type
= ident
4306 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4307 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4310 def draw(self
, context
):
4311 layout
= self
.layout
4312 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4313 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4314 props
.to_type
= ident
4317 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4318 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4321 def draw(self
, context
):
4322 layout
= self
.layout
4323 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4324 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4325 props
.to_type
= ident
4328 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4329 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4330 bl_label
= "Converter"
4332 def draw(self
, context
):
4333 layout
= self
.layout
4334 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4335 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4336 props
.to_type
= ident
4339 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4340 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4343 def draw(self
, context
):
4344 layout
= self
.layout
4345 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4346 if node_type
!= 'FRAME':
4347 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4348 props
.to_type
= ident
4351 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4352 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4355 def draw(self
, context
):
4356 layout
= self
.layout
4357 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4358 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4359 props
.to_type
= ident
4362 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4363 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4366 def draw(self
, context
):
4367 layout
= self
.layout
4368 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4369 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4370 props
.to_type
= ident
4373 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4374 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4377 def draw(self
, context
):
4378 layout
= self
.layout
4379 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4380 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4381 props
.to_type
= ident
4384 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4385 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4386 bl_label
= "Pattern"
4388 def draw(self
, context
):
4389 layout
= self
.layout
4390 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4391 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4392 props
.to_type
= ident
4395 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4396 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4397 bl_label
= "Textures"
4399 def draw(self
, context
):
4400 layout
= self
.layout
4401 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4402 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4403 props
.to_type
= ident
4406 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4407 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4408 bl_label
= "Converter"
4410 def draw(self
, context
):
4411 layout
= self
.layout
4412 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4413 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4414 props
.to_type
= ident
4417 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4418 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4419 bl_label
= "Distort"
4421 def draw(self
, context
):
4422 layout
= self
.layout
4423 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4424 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4425 props
.to_type
= ident
4428 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4429 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4432 def draw(self
, context
):
4433 layout
= self
.layout
4434 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4435 if node_type
!= 'FRAME':
4436 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4437 props
.to_type
= ident
4441 # APPENDAGES TO EXISTING UI
4445 def select_parent_children_buttons(self
, context
):
4446 layout
= self
.layout
4447 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4448 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4451 def attr_nodes_menu_func(self
, context
):
4452 col
= self
.layout
.column(align
=True)
4453 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4457 def multipleimages_menu_func(self
, context
):
4458 col
= self
.layout
.column(align
=True)
4459 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4460 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4464 def bgreset_menu_func(self
, context
):
4465 self
.layout
.operator(NWResetBG
.bl_idname
)
4468 def save_viewer_menu_func(self
, context
):
4469 if nw_check(context
):
4470 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4471 if context
.scene
.node_tree
.nodes
.active
:
4472 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4473 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4476 def reset_nodes_button(self
, context
):
4477 node_active
= context
.active_node
4478 node_selected
= context
.selected_nodes
4479 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4481 # Check if active node is in the selection and respective type
4482 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4483 row
= self
.layout
.row()
4484 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4485 self
.layout
.separator()
4487 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4488 row
= self
.layout
.row()
4489 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4490 self
.layout
.separator()
4494 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4498 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4499 # props entry: (property name, property value)
4502 # NWMergeNodes with Ctrl (AUTO).
4503 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4504 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4505 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4506 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4507 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4508 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4509 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4510 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4511 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4512 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4513 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4514 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4515 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4516 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4517 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4518 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4519 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4520 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4521 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4522 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4523 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4524 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4525 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4526 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4527 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4528 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4529 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4530 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4531 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4532 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4533 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4534 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4535 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4536 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4537 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4538 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4539 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4540 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4541 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4542 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4543 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4544 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4545 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4546 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4547 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4548 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4549 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4550 # NWMergeNodes with Ctrl Shift (MATH)
4551 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4552 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4553 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4554 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4555 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4556 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4557 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4558 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4559 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4560 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4561 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4562 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4563 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4564 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4565 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4566 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4567 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4568 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4569 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4570 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4571 # BATCH CHANGE NODES
4572 # NWBatchChangeNodes with Alt
4573 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4574 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4575 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4576 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4577 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4578 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4579 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4580 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4581 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4582 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4583 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4584 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4585 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4586 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4587 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4588 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4589 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4590 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4591 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4592 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4593 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4594 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4595 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4596 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4597 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4598 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4599 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4600 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4601 # LINK ACTIVE TO SELECTED
4602 # Don't use names, don't replace links (K)
4603 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4604 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4605 # Don't use names, replace links (Shift K)
4606 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4607 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4608 # Use node name, don't replace links (')
4609 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4610 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4611 # Use node name, replace links (Shift ')
4612 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4613 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4614 # Don't use names, don't replace links (;)
4615 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4616 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4617 # Don't use names, replace links (')
4618 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4619 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4621 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4622 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4623 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4624 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4625 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4626 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4627 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4628 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4629 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4630 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4631 # CLEAR LABEL (Alt L)
4632 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4633 # MODIFY LABEL (Alt Shift L)
4634 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4635 # Copy Label from active to selected
4636 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4637 # DETACH OUTPUTS (Alt Shift D)
4638 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4639 # LINK TO OUTPUT NODE (O)
4640 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4641 # SELECT PARENT/CHILDREN
4643 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4645 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4647 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4648 # Add Principled BSDF Texture Setup
4649 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4651 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4653 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4655 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4657 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4659 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4661 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4663 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
4665 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, False, False, None, "Lazy Connect"),
4666 # Lazy Connect with Menu
4667 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4668 # Viewer Tile Center
4669 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4671 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4672 # Reset Nodes (Back Space)
4673 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4675 ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wranger menu"),
4676 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4677 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4678 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4679 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4680 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4685 NWPrincipledPreferences
,
4705 NWAddPrincipledSetup
,
4707 NWLinkActiveToSelected
,
4709 NWSelectParentChildren
,
4715 NWAddMultipleImages
,
4724 NWConnectionListOutputs
,
4725 NWConnectionListInputs
,
4727 NWBatchChangeNodesMenu
,
4728 NWBatchChangeBlendTypeMenu
,
4729 NWBatchChangeOperationMenu
,
4730 NWCopyToSelectedMenu
,
4733 NWLinkActiveToSelectedMenu
,
4735 NWLinkUseNodeNameMenu
,
4736 NWLinkUseOutputsNamesMenu
,
4738 NWSwitchNodeTypeMenu
,
4739 NWSwitchShadersInputSubmenu
,
4740 NWSwitchShadersOutputSubmenu
,
4741 NWSwitchShadersShaderSubmenu
,
4742 NWSwitchShadersTextureSubmenu
,
4743 NWSwitchShadersColorSubmenu
,
4744 NWSwitchShadersVectorSubmenu
,
4745 NWSwitchShadersConverterSubmenu
,
4746 NWSwitchShadersLayoutSubmenu
,
4747 NWSwitchCompoInputSubmenu
,
4748 NWSwitchCompoOutputSubmenu
,
4749 NWSwitchCompoColorSubmenu
,
4750 NWSwitchCompoConverterSubmenu
,
4751 NWSwitchCompoFilterSubmenu
,
4752 NWSwitchCompoVectorSubmenu
,
4753 NWSwitchCompoMatteSubmenu
,
4754 NWSwitchCompoDistortSubmenu
,
4755 NWSwitchCompoLayoutSubmenu
,
4756 NWSwitchMatInputSubmenu
,
4757 NWSwitchMatOutputSubmenu
,
4758 NWSwitchMatColorSubmenu
,
4759 NWSwitchMatVectorSubmenu
,
4760 NWSwitchMatConverterSubmenu
,
4761 NWSwitchMatLayoutSubmenu
,
4762 NWSwitchTexInputSubmenu
,
4763 NWSwitchTexOutputSubmenu
,
4764 NWSwitchTexColorSubmenu
,
4765 NWSwitchTexPatternSubmenu
,
4766 NWSwitchTexTexturesSubmenu
,
4767 NWSwitchTexConverterSubmenu
,
4768 NWSwitchTexDistortSubmenu
,
4769 NWSwitchTexLayoutSubmenu
,
4773 from bpy
.utils
import register_class
4776 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4777 name
="Busy Drawing!",
4779 description
="An internal property used to store only the first mouse position")
4780 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4781 name
="Lazy Source!",
4783 description
="An internal property used to store the first node in a Lazy Connect operation")
4784 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
4785 name
="Lazy Target!",
4787 description
="An internal property used to store the last node in a Lazy Connect operation")
4788 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
4789 name
="Source Socket!",
4791 description
="An internal property used to store the source socket in a Lazy Connect operation")
4797 addon_keymaps
.clear()
4798 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
4800 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
4801 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
4802 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
4804 for prop
, value
in props
:
4805 setattr(kmi
.properties
, prop
, value
)
4806 addon_keymaps
.append((km
, kmi
))
4809 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
4810 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4811 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4812 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
4813 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
4814 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4815 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4816 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4817 bpy
.types
.NODE_PT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4818 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
4819 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
4823 from bpy
.utils
import unregister_class
4826 del bpy
.types
.Scene
.NWBusyDrawing
4827 del bpy
.types
.Scene
.NWLazySource
4828 del bpy
.types
.Scene
.NWLazyTarget
4829 del bpy
.types
.Scene
.NWSourceSocket
4832 for km
, kmi
in addon_keymaps
:
4833 km
.keymap_items
.remove(kmi
)
4834 addon_keymaps
.clear()
4837 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
4838 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4839 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4840 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
4841 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
4842 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4843 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4844 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4845 bpy
.types
.NODE_PT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4846 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
4847 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
4850 unregister_class(cls
)
4852 if __name__
== "__main__":