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 ('WHEELUPMOUSE', "Wheel Up"),
561 ('WHEELDOWNMOUSE', "Wheel Down"),
562 ('WHEELINMOUSE', "Wheel In"),
563 ('WHEELOUTMOUSE', "Wheel Out"),
576 ('LINE_FEED', "Enter"),
583 ('BACK_SLASH', "\\"),
585 ('NUMPAD_1', "Numpad 1"),
586 ('NUMPAD_2', "Numpad 2"),
587 ('NUMPAD_3', "Numpad 3"),
588 ('NUMPAD_4', "Numpad 4"),
589 ('NUMPAD_5', "Numpad 5"),
590 ('NUMPAD_6', "Numpad 6"),
591 ('NUMPAD_7', "Numpad 7"),
592 ('NUMPAD_8', "Numpad 8"),
593 ('NUMPAD_9', "Numpad 9"),
594 ('NUMPAD_0', "Numpad 0"),
595 ('NUMPAD_PERIOD', "Numpad ."),
596 ('NUMPAD_SLASH', "Numpad /"),
597 ('NUMPAD_ASTERIX', "Numpad *"),
598 ('NUMPAD_MINUS', "Numpad -"),
599 ('NUMPAD_ENTER', "Numpad Enter"),
600 ('NUMPAD_PLUS', "Numpad +"),
603 for (ugly
, nice
) in pairs
:
608 nice_punc
= punc
.replace("_", " ").title()
612 def force_update(context
):
613 context
.space_data
.node_tree
.update_tag()
617 prefs
= bpy
.context
.user_preferences
.system
618 return prefs
.dpi
* prefs
.pixel_size
/ 72
621 def node_mid_pt(node
, axis
):
623 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
625 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
631 def autolink(node1
, node2
, links
):
634 for outp
in node1
.outputs
:
635 for inp
in node2
.inputs
:
636 if not inp
.is_linked
and inp
.name
== outp
.name
:
641 for outp
in node1
.outputs
:
642 for inp
in node2
.inputs
:
643 if not inp
.is_linked
and inp
.type == outp
.type:
648 # force some connection even if the type doesn't match
649 for outp
in node1
.outputs
:
650 for inp
in node2
.inputs
:
651 if not inp
.is_linked
:
656 # even if no sockets are open, force one of matching type
657 for outp
in node1
.outputs
:
658 for inp
in node2
.inputs
:
659 if inp
.type == outp
.type:
665 for outp
in node1
.outputs
:
666 for inp
in node2
.inputs
:
671 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
675 def node_at_pos(nodes
, context
, event
):
676 nodes_near_mouse
= []
677 nodes_under_mouse
= []
680 store_mouse_cursor(context
, event
)
681 x
, y
= context
.space_data
.cursor_location
685 # Make a list of each corner (and middle of border) for each node.
686 # Will be sorted to find nearest point and thus nearest node
687 node_points_with_dist
= []
690 if node
.type != 'FRAME': # no point trying to link to a frame node
691 locx
= node
.location
.x
692 locy
= node
.location
.y
693 dimx
= node
.dimensions
.x
/dpifac()
694 dimy
= node
.dimensions
.y
/dpifac()
696 locx
+= node
.parent
.location
.x
697 locy
+= node
.parent
.location
.y
698 if node
.parent
.parent
:
699 locx
+= node
.parent
.parent
.location
.x
700 locy
+= node
.parent
.parent
.location
.y
701 if node
.parent
.parent
.parent
:
702 locx
+= node
.parent
.parent
.parent
.location
.x
703 locy
+= node
.parent
.parent
.parent
.location
.y
704 if node
.parent
.parent
.parent
.parent
:
705 # Support three levels or parenting
706 # There's got to be a better way to do this...
709 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- locy
)]) # Top Left
710 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- locy
)]) # Top Right
711 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- dimy
))]) # Bottom Left
712 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- dimy
))]) # Bottom Right
714 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- locy
)]) # Mid Top
715 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- (locy
- dimy
))]) # Mid Bottom
716 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- (dimy
/ 2)))]) # Mid Left
717 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- (dimy
/ 2)))]) # Mid Right
719 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
722 if node
.type != 'FRAME' and skipnode
== False:
723 locx
= node
.location
.x
724 locy
= node
.location
.y
725 dimx
= node
.dimensions
.x
/dpifac()
726 dimy
= node
.dimensions
.y
/dpifac()
728 locx
+= node
.parent
.location
.x
729 locy
+= node
.parent
.location
.y
730 if (locx
<= x
<= locx
+ dimx
) and \
731 (locy
- dimy
<= y
<= locy
):
732 nodes_under_mouse
.append(node
)
734 if len(nodes_under_mouse
) == 1:
735 if nodes_under_mouse
[0] != nearest_node
:
736 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
738 target_node
= nearest_node
# else use the nearest node
740 target_node
= nearest_node
744 def store_mouse_cursor(context
, event
):
745 space
= context
.space_data
746 v2d
= context
.region
.view2d
747 tree
= space
.edit_tree
749 # convert mouse position to the View2D for later node placement
750 if context
.region
.type == 'WINDOW':
751 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
753 space
.cursor_location
= tree
.view_center
756 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=[1.0, 1.0, 1.0, 0.7]):
757 shademodel_state
= bgl
.Buffer(bgl
.GL_INT
, 1)
758 bgl
.glGetIntegerv(bgl
.GL_SHADE_MODEL
, shademodel_state
)
760 bgl
.glEnable(bgl
.GL_BLEND
)
761 bgl
.glLineWidth(size
* dpifac())
762 bgl
.glShadeModel(bgl
.GL_SMOOTH
)
763 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
765 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
767 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)
768 bgl
.glVertex2f(x1
, y1
)
769 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
770 bgl
.glVertex2f(x2
, y2
)
775 bgl
.glShadeModel(shademodel_state
[0])
776 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
779 def draw_circle(mx
, my
, radius
, colour
=[1.0, 1.0, 1.0, 0.7]):
780 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
781 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
782 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
783 radius
= radius
* dpifac()
785 for i
in range(sides
+ 1):
786 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
787 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
788 bgl
.glVertex2f(cosine
, sine
)
790 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
793 def draw_rounded_node_border(node
, radius
=8, colour
=[1.0, 1.0, 1.0, 0.7]):
794 bgl
.glEnable(bgl
.GL_BLEND
)
795 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
797 area_width
= bpy
.context
.area
.width
- (16*dpifac()) - 1
798 bottom_bar
= (16*dpifac()) + 1
800 radius
= radius
*dpifac()
801 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
803 nlocx
= (node
.location
.x
+1)*dpifac()
804 nlocy
= (node
.location
.y
+1)*dpifac()
805 ndimx
= node
.dimensions
.x
806 ndimy
= node
.dimensions
.y
807 # This is a stupid way to do this... TODO use while loop
809 nlocx
+= node
.parent
.location
.x
810 nlocy
+= node
.parent
.location
.y
811 if node
.parent
.parent
:
812 nlocx
+= node
.parent
.parent
.location
.x
813 nlocy
+= node
.parent
.parent
.location
.y
814 if node
.parent
.parent
.parent
:
815 nlocx
+= node
.parent
.parent
.parent
.location
.x
816 nlocy
+= node
.parent
.parent
.parent
.location
.y
821 if node
.type == 'REROUTE':
829 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
830 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
831 bgl
.glVertex2f(mx
,my
)
832 for i
in range(sides
+1):
834 if my
> bottom_bar
and mx
< area_width
:
835 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
836 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
837 bgl
.glVertex2f(cosine
, sine
)
841 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
842 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
843 bgl
.glVertex2f(mx
,my
)
844 for i
in range(sides
+1):
846 if my
> bottom_bar
and mx
< area_width
:
847 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
848 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
849 bgl
.glVertex2f(cosine
, sine
)
853 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
854 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
855 bgl
.glVertex2f(mx
,my
)
856 for i
in range(sides
+1):
858 if my
> bottom_bar
and mx
< area_width
:
859 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
860 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
861 bgl
.glVertex2f(cosine
, sine
)
864 # Bottom right corner
865 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
866 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
867 bgl
.glVertex2f(mx
,my
)
868 for i
in range(sides
+1):
870 if my
> bottom_bar
and mx
< area_width
:
871 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
872 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
873 bgl
.glVertex2f(cosine
, sine
)
878 bgl
.glBegin(bgl
.GL_QUADS
)
879 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
880 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
881 m1y
= max(m1y
, bottom_bar
)
882 m2y
= max(m2y
, bottom_bar
)
883 if m1x
< area_width
and m2x
< area_width
:
884 bgl
.glVertex2f(m2x
-radius
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
885 bgl
.glVertex2f(m2x
,m2y
)
886 bgl
.glVertex2f(m1x
,m1y
)
887 bgl
.glVertex2f(m1x
-radius
,m1y
)
891 bgl
.glBegin(bgl
.GL_QUADS
)
892 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
893 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
894 m1x
= min(m1x
, area_width
)
895 m2x
= min(m2x
, area_width
)
896 if m1y
> bottom_bar
and m2y
> bottom_bar
:
897 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
898 bgl
.glVertex2f(m2x
,m2y
)
899 bgl
.glVertex2f(m2x
,m1y
+radius
)
900 bgl
.glVertex2f(m1x
,m1y
+radius
)
904 bgl
.glBegin(bgl
.GL_QUADS
)
905 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
906 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
907 m1y
= max(m1y
, bottom_bar
)
908 m2y
= max(m2y
, bottom_bar
)
909 if m1x
< area_width
and m2x
< area_width
:
910 bgl
.glVertex2f(m2x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
911 bgl
.glVertex2f(m2x
+radius
,m2y
)
912 bgl
.glVertex2f(m1x
+radius
,m1y
)
913 bgl
.glVertex2f(m1x
,m1y
)
917 bgl
.glBegin(bgl
.GL_QUADS
)
918 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
, clip
=False)
919 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
, clip
=False)
920 m1x
= min(m1x
, area_width
)
921 m2x
= min(m2x
, area_width
)
922 if m1y
> bottom_bar
and m2y
> bottom_bar
:
923 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
924 bgl
.glVertex2f(m2x
,m2y
)
925 bgl
.glVertex2f(m2x
,m1y
-radius
)
926 bgl
.glVertex2f(m1x
,m1y
-radius
)
931 bgl
.glDisable(bgl
.GL_BLEND
)
932 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
935 def draw_callback_nodeoutline(self
, context
, mode
):
937 nodes
, links
= get_nodes_links(context
)
938 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
941 col_outer
= [1.0, 0.2, 0.2, 0.4]
942 col_inner
= [0.0, 0.0, 0.0, 0.5]
943 col_circle_inner
= [0.3, 0.05, 0.05, 1.0]
944 elif mode
== "LINKMENU":
945 col_outer
= [0.4, 0.6, 1.0, 0.4]
946 col_inner
= [0.0, 0.0, 0.0, 0.5]
947 col_circle_inner
= [0.08, 0.15, .3, 1.0]
949 col_outer
= [0.2, 1.0, 0.2, 0.4]
950 col_inner
= [0.0, 0.0, 0.0, 0.5]
951 col_circle_inner
= [0.05, 0.3, 0.05, 1.0]
953 m1x
= self
.mouse_path
[0][0]
954 m1y
= self
.mouse_path
[0][1]
955 m2x
= self
.mouse_path
[-1][0]
956 m2y
= self
.mouse_path
[-1][1]
958 n1
= nodes
[context
.scene
.NWLazySource
]
959 n2
= nodes
[context
.scene
.NWLazyTarget
]
962 col_outer
= [0.4, 0.4, 0.4, 0.4]
963 col_inner
= [0.0, 0.0, 0.0, 0.5]
964 col_circle_inner
= [0.2, 0.2, 0.2, 1.0]
966 draw_rounded_node_border(n1
, radius
=6, colour
=col_outer
) # outline
967 draw_rounded_node_border(n1
, radius
=5, colour
=col_inner
) # inner
968 draw_rounded_node_border(n2
, radius
=6, colour
=col_outer
) # outline
969 draw_rounded_node_border(n2
, radius
=5, colour
=col_inner
) # inner
971 draw_line(m1x
, m1y
, m2x
, m2y
, 5, col_outer
) # line outline
972 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
975 draw_circle(m1x
, m1y
, 7, col_outer
)
976 draw_circle(m2x
, m2y
, 7, col_outer
)
979 draw_circle(m1x
, m1y
, 5, col_circle_inner
)
980 draw_circle(m2x
, m2y
, 5, col_circle_inner
)
982 # restore opengl defaults
984 bgl
.glDisable(bgl
.GL_BLEND
)
985 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
987 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
990 def get_nodes_links(context
):
991 tree
= context
.space_data
.node_tree
993 # Get nodes from currently edited tree.
994 # If user is editing a group, space_data.node_tree is still the base level (outside group).
995 # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
996 # the same as context.active_node, the user is in a group.
997 # Check recursively until we find the real active node_tree:
998 if tree
.nodes
.active
:
999 while tree
.nodes
.active
!= context
.active_node
:
1000 tree
= tree
.nodes
.active
.node_tree
1002 return tree
.nodes
, tree
.links
1005 class NWPrincipledPreferences(bpy
.types
.PropertyGroup
):
1006 base_color
: StringProperty(
1008 default
='diffuse diff albedo base col color',
1009 description
='Naming Components for Base Color maps')
1010 sss_color
: StringProperty(
1011 name
='Subsurface Color',
1012 default
='sss subsurface',
1013 description
='Naming Components for Subsurface Color maps')
1014 metallic
: StringProperty(
1016 default
='metallic metalness metal mtl',
1017 description
='Naming Components for metallness maps')
1018 specular
: StringProperty(
1020 default
='specularity specular spec spc',
1021 description
='Naming Components for Specular maps')
1022 normal
: StringProperty(
1024 default
='normal nor nrm nrml norm',
1025 description
='Naming Components for Normal maps')
1026 bump
: StringProperty(
1029 description
='Naming Components for bump maps')
1030 rough
: StringProperty(
1032 default
='roughness rough rgh',
1033 description
='Naming Components for roughness maps')
1034 gloss
: StringProperty(
1036 default
='gloss glossy glossyness',
1037 description
='Naming Components for glossy maps')
1038 displacement
: StringProperty(
1039 name
='Displacement',
1040 default
='displacement displace disp dsp height heightmap',
1041 description
='Naming Components for displacement maps')
1044 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1045 bl_idname
= __name__
1047 merge_hide
: EnumProperty(
1048 name
="Hide Mix nodes",
1050 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1051 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1052 ("NEVER", "Never", "Never collapse the new merge nodes")
1054 default
='NON_SHADER',
1055 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded")
1056 merge_position
: EnumProperty(
1057 name
="Mix Node Position",
1059 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1060 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1063 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
1065 show_hotkey_list
: BoolProperty(
1066 name
="Show Hotkey List",
1068 description
="Expand this box into a list of all the hotkeys for functions in this addon"
1070 hotkey_list_filter
: StringProperty(
1071 name
=" Filter by Name",
1073 description
="Show only hotkeys that have this text in their name"
1075 show_principled_lists
: BoolProperty(
1076 name
="Show Principled naming tags",
1078 description
="Expand this box into a list of all naming tags for principled texture setup"
1080 principled_tags
: bpy
.props
.PointerProperty(type=NWPrincipledPreferences
)
1082 def draw(self
, context
):
1083 layout
= self
.layout
1084 col
= layout
.column()
1085 col
.prop(self
, "merge_position")
1086 col
.prop(self
, "merge_hide")
1089 col
= box
.column(align
=True)
1090 col
.prop(self
, "show_principled_lists", text
='Edit tags for auto texture detection in Principled BSDF setup', toggle
=True)
1091 if self
.show_principled_lists
:
1092 tags
= self
.principled_tags
1094 col
.prop(tags
, "base_color")
1095 col
.prop(tags
, "sss_color")
1096 col
.prop(tags
, "metallic")
1097 col
.prop(tags
, "specular")
1098 col
.prop(tags
, "rough")
1099 col
.prop(tags
, "gloss")
1100 col
.prop(tags
, "normal")
1101 col
.prop(tags
, "bump")
1102 col
.prop(tags
, "displacement")
1105 col
= box
.column(align
=True)
1106 hotkey_button_name
= "Show Hotkey List"
1107 if self
.show_hotkey_list
:
1108 hotkey_button_name
= "Hide Hotkey List"
1109 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
1110 if self
.show_hotkey_list
:
1111 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
1113 for hotkey
in kmi_defs
:
1115 hotkey_name
= hotkey
[7]
1117 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
1118 row
= col
.row(align
=True)
1119 row
.label(text
=hotkey_name
)
1120 keystr
= nice_hotkey_name(hotkey
[1])
1122 keystr
= "Shift " + keystr
1124 keystr
= "Alt " + keystr
1126 keystr
= "Ctrl " + keystr
1127 row
.label(text
=keystr
)
1131 def nw_check(context
):
1132 space
= context
.space_data
1133 valid_trees
= ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
1136 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and space
.tree_type
in valid_trees
:
1143 def poll(cls
, context
):
1144 return nw_check(context
)
1148 class NWLazyMix(Operator
, NWBase
):
1149 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1150 bl_idname
= "node.nw_lazy_mix"
1151 bl_label
= "Mix Nodes"
1152 bl_options
= {'REGISTER', 'UNDO'}
1154 def modal(self
, context
, event
):
1155 context
.area
.tag_redraw()
1156 nodes
, links
= get_nodes_links(context
)
1159 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1162 if not context
.scene
.NWBusyDrawing
:
1163 node1
= node_at_pos(nodes
, context
, event
)
1165 context
.scene
.NWBusyDrawing
= node1
.name
1167 if context
.scene
.NWBusyDrawing
!= 'STOP':
1168 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1170 context
.scene
.NWLazySource
= node1
.name
1171 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1173 if event
.type == 'MOUSEMOVE':
1174 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1176 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1177 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1178 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1181 node2
= node_at_pos(nodes
, context
, event
)
1183 context
.scene
.NWBusyDrawing
= node2
.name
1195 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1197 context
.scene
.NWBusyDrawing
= ""
1200 elif event
.type == 'ESC':
1202 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1203 return {'CANCELLED'}
1205 return {'RUNNING_MODAL'}
1207 def invoke(self
, context
, event
):
1208 if context
.area
.type == 'NODE_EDITOR':
1209 # the arguments we pass the the callback
1210 args
= (self
, context
, 'MIX')
1211 # Add the region OpenGL drawing callback
1212 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1213 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1215 self
.mouse_path
= []
1217 context
.window_manager
.modal_handler_add(self
)
1218 return {'RUNNING_MODAL'}
1220 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1221 return {'CANCELLED'}
1224 class NWLazyConnect(Operator
, NWBase
):
1225 """Connect two nodes without clicking a specific socket (automatically determined"""
1226 bl_idname
= "node.nw_lazy_connect"
1227 bl_label
= "Lazy Connect"
1228 bl_options
= {'REGISTER', 'UNDO'}
1229 with_menu
: BoolProperty()
1231 def modal(self
, context
, event
):
1232 context
.area
.tag_redraw()
1233 nodes
, links
= get_nodes_links(context
)
1236 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1239 if not context
.scene
.NWBusyDrawing
:
1240 node1
= node_at_pos(nodes
, context
, event
)
1242 context
.scene
.NWBusyDrawing
= node1
.name
1244 if context
.scene
.NWBusyDrawing
!= 'STOP':
1245 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1247 context
.scene
.NWLazySource
= node1
.name
1248 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1250 if event
.type == 'MOUSEMOVE':
1251 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1253 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1254 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1255 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1258 node2
= node_at_pos(nodes
, context
, event
)
1260 context
.scene
.NWBusyDrawing
= node2
.name
1265 link_success
= False
1271 if node
.select
== True:
1273 original_sel
.append(node
)
1275 original_unsel
.append(node
)
1279 #link_success = autolink(node1, node2, links)
1281 if len(node1
.outputs
) > 1 and node2
.inputs
:
1282 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1283 elif len(node1
.outputs
) == 1:
1284 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1286 link_success
= autolink(node1
, node2
, links
)
1288 for node
in original_sel
:
1290 for node
in original_unsel
:
1294 force_update(context
)
1295 context
.scene
.NWBusyDrawing
= ""
1298 elif event
.type == 'ESC':
1299 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1300 return {'CANCELLED'}
1302 return {'RUNNING_MODAL'}
1304 def invoke(self
, context
, event
):
1305 if context
.area
.type == 'NODE_EDITOR':
1306 nodes
, links
= get_nodes_links(context
)
1307 node
= node_at_pos(nodes
, context
, event
)
1309 context
.scene
.NWBusyDrawing
= node
.name
1311 # the arguments we pass the the callback
1315 args
= (self
, context
, mode
)
1316 # Add the region OpenGL drawing callback
1317 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1318 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1320 self
.mouse_path
= []
1322 context
.window_manager
.modal_handler_add(self
)
1323 return {'RUNNING_MODAL'}
1325 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1326 return {'CANCELLED'}
1329 class NWDeleteUnused(Operator
, NWBase
):
1330 """Delete all nodes whose output is not used"""
1331 bl_idname
= 'node.nw_del_unused'
1332 bl_label
= 'Delete Unused Nodes'
1333 bl_options
= {'REGISTER', 'UNDO'}
1335 delete_muted
: BoolProperty(name
="Delete Muted", description
="Delete (but reconnect, like Ctrl-X) all muted nodes", default
=True)
1336 delete_frames
: BoolProperty(name
="Delete Empty Frames", description
="Delete all frames that have no nodes inside them", default
=True)
1338 def is_unused_node(self
, node
):
1339 end_types
= ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1340 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
1341 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
1342 if node
.type in end_types
:
1345 for output
in node
.outputs
:
1351 def poll(cls
, context
):
1353 if nw_check(context
):
1354 if context
.space_data
.node_tree
.nodes
:
1358 def execute(self
, context
):
1359 nodes
, links
= get_nodes_links(context
)
1364 if node
.select
== True:
1365 selection
.append(node
.name
)
1371 temp_deleted_nodes
= []
1372 del_unused_iterations
= len(nodes
)
1373 for it
in range(0, del_unused_iterations
):
1374 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1376 if self
.is_unused_node(node
):
1378 deleted_nodes
.append(node
.name
)
1379 bpy
.ops
.node
.delete()
1381 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1384 if self
.delete_frames
:
1392 frames_in_use
.append(node
.parent
)
1394 if node
.type == 'FRAME' and node
not in frames_in_use
:
1397 repeat
= True # repeat for nested frames
1399 if node
not in frames_in_use
:
1401 deleted_nodes
.append(node
.name
)
1402 bpy
.ops
.node
.delete()
1404 if self
.delete_muted
:
1408 deleted_nodes
.append(node
.name
)
1409 bpy
.ops
.node
.delete_reconnect()
1411 # get unique list of deleted nodes (iterations would count the same node more than once)
1412 deleted_nodes
= list(set(deleted_nodes
))
1413 for n
in deleted_nodes
:
1414 self
.report({'INFO'}, "Node " + n
+ " deleted")
1415 num_deleted
= len(deleted_nodes
)
1420 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1422 self
.report({'INFO'}, "Nothing deleted")
1425 nodes
, links
= get_nodes_links(context
)
1427 if node
.name
in selection
:
1431 def invoke(self
, context
, event
):
1432 return context
.window_manager
.invoke_confirm(self
, event
)
1435 class NWSwapLinks(Operator
, NWBase
):
1436 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1437 bl_idname
= 'node.nw_swap_links'
1438 bl_label
= 'Swap Links'
1439 bl_options
= {'REGISTER', 'UNDO'}
1442 def poll(cls
, context
):
1444 if nw_check(context
):
1445 if context
.selected_nodes
:
1446 valid
= len(context
.selected_nodes
) <= 2
1449 def execute(self
, context
):
1450 nodes
, links
= get_nodes_links(context
)
1451 selected_nodes
= context
.selected_nodes
1452 n1
= selected_nodes
[0]
1455 if len(selected_nodes
) == 2:
1456 n2
= selected_nodes
[1]
1457 if n1
.outputs
and n2
.outputs
:
1462 for output
in n1
.outputs
:
1464 for link
in output
.links
:
1465 n1_outputs
.append([out_index
, link
.to_socket
])
1470 for output
in n2
.outputs
:
1472 for link
in output
.links
:
1473 n2_outputs
.append([out_index
, link
.to_socket
])
1477 for connection
in n1_outputs
:
1479 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1481 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1482 for connection
in n2_outputs
:
1484 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1486 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1488 if n1
.outputs
or n2
.outputs
:
1489 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1491 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1494 elif len(selected_nodes
) == 1:
1498 for i1
in n1
.inputs
:
1501 for i2
in n1
.inputs
:
1502 if i1
.type == i2
.type and i2
.is_linked
:
1504 types
.append ([i1
, similar_types
, i
])
1506 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1511 for i2
in n1
.inputs
:
1512 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1514 i1f
= pair
[0].links
[0].from_socket
1515 i1t
= pair
[0].links
[0].to_socket
1516 i2f
= pair
[1].links
[0].from_socket
1517 i2t
= pair
[1].links
[0].to_socket
1522 fs
= t
[0].links
[0].from_socket
1524 links
.remove(t
[0].links
[0])
1525 if i
+1 == len(n1
.inputs
):
1528 while n1
.inputs
[i
].is_linked
:
1530 links
.new(fs
, n1
.inputs
[i
])
1531 elif len(types
) == 2:
1532 i1f
= types
[0][0].links
[0].from_socket
1533 i1t
= types
[0][0].links
[0].to_socket
1534 i2f
= types
[1][0].links
[0].from_socket
1535 i2t
= types
[1][0].links
[0].to_socket
1540 self
.report({'WARNING'}, "This node has no input connections to swap!")
1542 self
.report({'WARNING'}, "This node has no inputs to swap!")
1544 force_update(context
)
1548 class NWResetBG(Operator
, NWBase
):
1549 """Reset the zoom and position of the background image"""
1550 bl_idname
= 'node.nw_bg_reset'
1551 bl_label
= 'Reset Backdrop'
1552 bl_options
= {'REGISTER', 'UNDO'}
1555 def poll(cls
, context
):
1557 if nw_check(context
):
1558 snode
= context
.space_data
1559 valid
= snode
.tree_type
== 'CompositorNodeTree'
1562 def execute(self
, context
):
1563 context
.space_data
.backdrop_zoom
= 1
1564 context
.space_data
.backdrop_offset
[0] = 0
1565 context
.space_data
.backdrop_offset
[1] = 0
1569 class NWAddAttrNode(Operator
, NWBase
):
1570 """Add an Attribute node with this name"""
1571 bl_idname
= 'node.nw_add_attr_node'
1572 bl_label
= 'Add UV map'
1573 bl_options
= {'REGISTER', 'UNDO'}
1575 attr_name
: StringProperty()
1577 def execute(self
, context
):
1578 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1579 nodes
, links
= get_nodes_links(context
)
1580 nodes
.active
.attribute_name
= self
.attr_name
1584 class NWEmissionViewer(Operator
, NWBase
):
1585 bl_idname
= "node.nw_emission_viewer"
1586 bl_label
= "Emission Viewer"
1587 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1588 bl_options
= {'REGISTER', 'UNDO'}
1591 def poll(cls
, context
):
1592 is_cycles
= is_cycles_or_eevee(context
)
1593 if nw_check(context
):
1594 space
= context
.space_data
1595 if space
.tree_type
== 'ShaderNodeTree' and is_cycles
:
1596 if context
.active_node
:
1597 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1603 def invoke(self
, context
, event
):
1604 space
= context
.space_data
1605 shader_type
= space
.shader_type
1606 if shader_type
== 'OBJECT':
1607 if space
.id not in [light
for light
in bpy
.data
.lights
]: # cannot use bpy.data.lights directly as iterable
1608 shader_output_type
= "OUTPUT_MATERIAL"
1609 shader_output_ident
= "ShaderNodeOutputMaterial"
1610 shader_viewer_ident
= "ShaderNodeEmission"
1612 shader_output_type
= "OUTPUT_LIGHT"
1613 shader_output_ident
= "ShaderNodeOutputLight"
1614 shader_viewer_ident
= "ShaderNodeEmission"
1616 elif shader_type
== 'WORLD':
1617 shader_output_type
= "OUTPUT_WORLD"
1618 shader_output_ident
= "ShaderNodeOutputWorld"
1619 shader_viewer_ident
= "ShaderNodeBackground"
1620 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1621 mlocx
= event
.mouse_region_x
1622 mlocy
= event
.mouse_region_y
1623 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1624 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1625 nodes
, links
= get_nodes_links(context
)
1626 in_group
= context
.active_node
!= space
.node_tree
.nodes
.active
1627 active
= nodes
.active
1628 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1631 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1632 for out
in active
.outputs
:
1637 # get material_output node, store selection, deselect all
1638 materialout
= None # placeholder node
1641 if node
.type == shader_output_type
:
1644 selection
.append(node
.name
)
1647 # get right-most location
1648 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1649 max_xloc_node
= sorted_by_xloc
[-1]
1650 if max_xloc_node
.name
== 'Emission Viewer':
1651 max_xloc_node
= sorted_by_xloc
[-2]
1653 # get average y location
1656 sum_yloc
+= node
.location
.y
1658 new_locx
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1659 new_locy
= sum_yloc
/ len(nodes
)
1661 materialout
= nodes
.new(shader_output_ident
)
1662 materialout
.location
.x
= new_locx
1663 materialout
.location
.y
= new_locy
1664 materialout
.select
= False
1665 # Analyze outputs, add "Emission Viewer" if needed, make links
1668 for i
, out
in enumerate(active
.outputs
):
1670 valid_outputs
.append(i
)
1672 out_i
= valid_outputs
[0] # Start index of node's outputs
1673 for i
, valid_i
in enumerate(valid_outputs
):
1674 for out_link
in active
.outputs
[valid_i
].links
:
1675 if "Emission Viewer" in out_link
.to_node
.name
or (out_link
.to_node
== materialout
and out_link
.to_socket
== materialout
.inputs
[0]):
1676 if i
< len(valid_outputs
) - 1:
1677 out_i
= valid_outputs
[i
+ 1]
1679 out_i
= valid_outputs
[0]
1680 make_links
= [] # store sockets for new links
1682 # If output type not 'SHADER' - "Emission Viewer" needed
1683 if active
.outputs
[out_i
].type != 'SHADER':
1684 # get Emission Viewer node
1685 emission_exists
= False
1686 emission_placeholder
= nodes
[0]
1688 if "Emission Viewer" in node
.name
:
1689 emission_exists
= True
1690 emission_placeholder
= node
1691 if not emission_exists
:
1692 emission
= nodes
.new(shader_viewer_ident
)
1693 emission
.hide
= True
1694 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1695 emission
.label
= "Viewer"
1696 emission
.name
= "Emission Viewer"
1697 emission
.use_custom_color
= True
1698 emission
.color
= (0.6, 0.5, 0.4)
1699 emission
.select
= False
1701 emission
= emission_placeholder
1702 make_links
.append((active
.outputs
[out_i
], emission
.inputs
[0]))
1704 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1705 if emission
.outputs
[0].links
.__len
__() > 0:
1706 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1707 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1709 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1711 # Set brightness of viewer to compensate for Film and CM exposure
1712 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1713 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1714 emission
.inputs
[1].default_value
= intensity
1717 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1718 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[1 if active
.outputs
[out_i
].name
== "Volume" else 0]))
1720 if node
.name
== 'Emission Viewer':
1722 bpy
.ops
.node
.delete()
1723 for li_from
, li_to
in make_links
:
1724 links
.new(li_from
, li_to
)
1726 nodes
.active
= active
1728 if node
.name
in selection
:
1730 force_update(context
)
1733 return {'CANCELLED'}
1736 class NWFrameSelected(Operator
, NWBase
):
1737 bl_idname
= "node.nw_frame_selected"
1738 bl_label
= "Frame Selected"
1739 bl_description
= "Add a frame node and parent the selected nodes to it"
1740 bl_options
= {'REGISTER', 'UNDO'}
1742 label_prop
: StringProperty(
1744 description
='The visual name of the frame node',
1747 color_prop
: FloatVectorProperty(
1749 description
="The color of the frame node",
1750 default
=(0.6, 0.6, 0.6),
1751 min=0, max=1, step
=1, precision
=3,
1752 subtype
='COLOR_GAMMA', size
=3
1755 def execute(self
, context
):
1756 nodes
, links
= get_nodes_links(context
)
1759 if node
.select
== True:
1760 selected
.append(node
)
1762 bpy
.ops
.node
.add_node(type='NodeFrame')
1764 frm
.label
= self
.label_prop
1765 frm
.use_custom_color
= True
1766 frm
.color
= self
.color_prop
1768 for node
in selected
:
1774 class NWReloadImages(Operator
, NWBase
):
1775 bl_idname
= "node.nw_reload_images"
1776 bl_label
= "Reload Images"
1777 bl_description
= "Update all the image nodes to match their files on disk"
1779 def execute(self
, context
):
1780 nodes
, links
= get_nodes_links(context
)
1781 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1784 if node
.type in image_types
:
1785 if node
.type == "TEXTURE":
1786 if node
.texture
: # node has texture assigned
1787 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1788 if node
.texture
.image
: # texture has image assigned
1789 node
.texture
.image
.reload()
1797 self
.report({'INFO'}, "Reloaded images")
1798 print("Reloaded " + str(num_reloaded
) + " images")
1799 force_update(context
)
1802 self
.report({'WARNING'}, "No images found to reload in this node tree")
1803 return {'CANCELLED'}
1806 class NWSwitchNodeType(Operator
, NWBase
):
1807 """Switch type of selected nodes """
1808 bl_idname
= "node.nw_swtch_node_type"
1809 bl_label
= "Switch Node Type"
1810 bl_options
= {'REGISTER', 'UNDO'}
1812 to_type
: EnumProperty(
1813 name
="Switch to type",
1814 items
=list(shaders_input_nodes_props
) +
1815 list(shaders_output_nodes_props
) +
1816 list(shaders_shader_nodes_props
) +
1817 list(shaders_texture_nodes_props
) +
1818 list(shaders_color_nodes_props
) +
1819 list(shaders_vector_nodes_props
) +
1820 list(shaders_converter_nodes_props
) +
1821 list(shaders_layout_nodes_props
) +
1822 list(compo_input_nodes_props
) +
1823 list(compo_output_nodes_props
) +
1824 list(compo_color_nodes_props
) +
1825 list(compo_converter_nodes_props
) +
1826 list(compo_filter_nodes_props
) +
1827 list(compo_vector_nodes_props
) +
1828 list(compo_matte_nodes_props
) +
1829 list(compo_distort_nodes_props
) +
1830 list(compo_layout_nodes_props
) +
1831 list(blender_mat_input_nodes_props
) +
1832 list(blender_mat_output_nodes_props
) +
1833 list(blender_mat_color_nodes_props
) +
1834 list(blender_mat_vector_nodes_props
) +
1835 list(blender_mat_converter_nodes_props
) +
1836 list(blender_mat_layout_nodes_props
) +
1837 list(texture_input_nodes_props
) +
1838 list(texture_output_nodes_props
) +
1839 list(texture_color_nodes_props
) +
1840 list(texture_pattern_nodes_props
) +
1841 list(texture_textures_nodes_props
) +
1842 list(texture_converter_nodes_props
) +
1843 list(texture_distort_nodes_props
) +
1844 list(texture_layout_nodes_props
)
1847 def execute(self
, context
):
1848 nodes
, links
= get_nodes_links(context
)
1849 to_type
= self
.to_type
1850 # Those types of nodes will not swap.
1851 src_excludes
= ('NodeFrame')
1852 # Those attributes of nodes will be copied if possible
1853 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1854 'show_options', 'show_preview', 'show_texture',
1855 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1857 selected
= [n
for n
in nodes
if n
.select
]
1859 for node
in [n
for n
in selected
if
1860 n
.rna_type
.identifier
not in src_excludes
and
1861 n
.rna_type
.identifier
!= to_type
]:
1862 new_node
= nodes
.new(to_type
)
1863 for attr
in attrs_to_pass
:
1864 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1865 setattr(new_node
, attr
, getattr(node
, attr
))
1866 # set image datablock of dst to image of src
1867 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1869 new_node
.image
= node
.image
1871 if new_node
.type == 'SWITCH':
1872 new_node
.hide
= True
1873 # Dictionaries: src_sockets and dst_sockets:
1874 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1875 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1876 # in 'INPUTS' and 'OUTPUTS':
1877 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1879 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1881 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1882 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1885 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1886 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1888 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1889 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1890 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1891 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1892 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1893 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1894 # enumerate in inputs, then in outputs
1895 # find name, default value and links of socket
1896 for i
, socket
in enumerate(in_out
):
1897 the_name
= socket
.name
1899 # Not every socket, especially in outputs has "default_value"
1900 if hasattr(socket
, 'default_value'):
1901 dval
= socket
.default_value
1903 for lnk
in socket
.links
:
1904 socket_links
.append(lnk
)
1905 # check type of socket to fill proper keys.
1906 for the_type
in types_order_one
:
1907 if socket
.type == the_type
:
1908 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1909 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1910 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1911 # Check which of the types in inputs/outputs is considered to be "main".
1912 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1913 for type_check
in types_order_one
:
1914 if sockets
[in_out_name
][type_check
]:
1915 sockets
[in_out_name
]['MAIN'] = type_check
1919 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1920 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1923 for inout
, soctype
in (
1924 ('INPUTS', 'MAIN',),
1925 ('INPUTS', 'SHADER',),
1926 ('INPUTS', 'RGBA',),
1927 ('INPUTS', 'VECTOR',),
1928 ('INPUTS', 'VALUE',),
1929 ('OUTPUTS', 'MAIN',),
1930 ('OUTPUTS', 'SHADER',),
1931 ('OUTPUTS', 'RGBA',),
1932 ('OUTPUTS', 'VECTOR',),
1933 ('OUTPUTS', 'VALUE',),
1935 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1936 if soctype
== 'MAIN':
1937 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1938 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1940 sc
= src_sockets
[inout
][soctype
]
1941 dt
= dst_sockets
[inout
][soctype
]
1942 # start with 'dt' to determine number of possibilities.
1943 for i
, soc
in enumerate(dt
):
1944 # if src main has enough entries - match them with dst main sockets by indexes.
1946 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1947 # add 'VALUE_NAME' criterion to inputs.
1948 if inout
== 'INPUTS' and soctype
== 'VALUE':
1950 if s
[2] == soc
[2]: # if names match
1951 # append src (index, dval), dst (index, dval)
1952 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1954 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1955 # This creates better links when relinking textures.
1956 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1957 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1959 # Pass default values and RELINK:
1960 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1961 # INPUTS: Base on matches in proper order.
1962 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1964 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1965 new_node
.inputs
[dst_i
].default_value
= src_dval
1966 # Special case: switch to math
1967 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1968 new_node
.type == 'MATH' and\
1970 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1971 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1972 if node
.type == 'MIX_RGB':
1973 if node
.blend_type
in [o
[0] for o
in operations
]:
1974 new_node
.operation
= node
.blend_type
1975 # Special case: switch from math to some types
1976 if node
.type == 'MATH' and\
1977 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1980 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1981 if new_node
.type == 'MIX_RGB':
1982 if node
.operation
in [t
[0] for t
in blend_types
]:
1983 new_node
.blend_type
= node
.operation
1984 # Set Fac of MIX_RGB to 1.0
1985 new_node
.inputs
[0].default_value
= 1.0
1986 # make link only when dst matching input is not linked already.
1987 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1988 in_src_link
= node
.inputs
[src_i
].links
[0]
1989 in_dst_socket
= new_node
.inputs
[dst_i
]
1990 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1991 links
.remove(in_src_link
)
1992 # OUTPUTS: Base on matches in proper order.
1993 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1994 for out_src_link
in node
.outputs
[src_i
].links
:
1995 out_dst_socket
= new_node
.outputs
[dst_i
]
1996 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1997 # relink rest inputs if possible, no criteria
1998 for src_inp
in node
.inputs
:
1999 for dst_inp
in new_node
.inputs
:
2000 if src_inp
.links
and not dst_inp
.links
:
2001 src_link
= src_inp
.links
[0]
2002 links
.new(src_link
.from_socket
, dst_inp
)
2003 links
.remove(src_link
)
2004 # relink rest outputs if possible, base on node kind if any left.
2005 for src_o
in node
.outputs
:
2006 for out_src_link
in src_o
.links
:
2007 for dst_o
in new_node
.outputs
:
2008 if src_o
.type == dst_o
.type:
2009 links
.new(dst_o
, out_src_link
.to_socket
)
2010 # relink rest outputs no criteria if any left. Link all from first output.
2011 for src_o
in node
.outputs
:
2012 for out_src_link
in src_o
.links
:
2013 if new_node
.outputs
:
2014 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
2016 force_update(context
)
2020 class NWMergeNodes(Operator
, NWBase
):
2021 bl_idname
= "node.nw_merge_nodes"
2022 bl_label
= "Merge Nodes"
2023 bl_description
= "Merge Selected Nodes"
2024 bl_options
= {'REGISTER', 'UNDO'}
2028 description
="All possible blend types and math operations",
2029 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2031 merge_type
: EnumProperty(
2033 description
="Type of Merge to be used",
2035 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2036 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2037 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2038 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2039 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2040 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2044 def execute(self
, context
):
2045 settings
= context
.user_preferences
.addons
[__name__
].preferences
2046 merge_hide
= settings
.merge_hide
2047 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2050 do_hide_shader
= False
2051 if merge_hide
== 'ALWAYS':
2053 do_hide_shader
= True
2054 elif merge_hide
== 'NON_SHADER':
2057 tree_type
= context
.space_data
.node_tree
.type
2058 if tree_type
== 'COMPOSITING':
2059 node_type
= 'CompositorNode'
2060 elif tree_type
== 'SHADER':
2061 node_type
= 'ShaderNode'
2062 elif tree_type
== 'TEXTURE':
2063 node_type
= 'TextureNode'
2064 nodes
, links
= get_nodes_links(context
)
2066 merge_type
= self
.merge_type
2067 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2068 # 'ZCOMBINE' works only if mode == 'MIX'
2069 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2070 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2073 selected_mix
= [] # entry = [index, loc]
2074 selected_shader
= [] # entry = [index, loc]
2075 selected_math
= [] # entry = [index, loc]
2076 selected_z
= [] # entry = [index, loc]
2077 selected_alphaover
= [] # entry = [index, loc]
2079 for i
, node
in enumerate(nodes
):
2080 if node
.select
and node
.outputs
:
2081 if merge_type
== 'AUTO':
2082 for (type, types_list
, dst
) in (
2083 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2084 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2085 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2087 output_type
= node
.outputs
[0].type
2088 valid_mode
= mode
in types_list
2089 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2090 # Cheat that output type is 'RGBA',
2091 # and that 'MIX' exists in math operations list.
2092 # This way when selected_mix list is analyzed:
2093 # Node data will be appended even though it doesn't meet requirements.
2094 if output_type
!= 'SHADER' and mode
== 'MIX':
2095 output_type
= 'RGBA'
2097 if output_type
== type and valid_mode
:
2098 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2100 for (type, types_list
, dst
) in (
2101 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2102 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2103 ('MATH', [t
[0] for t
in operations
], selected_math
),
2104 ('ZCOMBINE', ('MIX', ), selected_z
),
2105 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2107 if merge_type
== type and mode
in types_list
:
2108 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2109 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2110 # use only 'Mix' nodes for merging.
2111 # For that we add selected_math list to selected_mix list and clear selected_math.
2112 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2113 selected_mix
+= selected_math
2116 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2118 count_before
= len(nodes
)
2119 # sort list by loc_x - reversed
2120 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2122 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2123 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2124 if merge_position
== 'CENTER':
2125 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)
2126 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2132 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2136 if nodes_list
== selected_shader
and not do_hide_shader
:
2138 the_range
= len(nodes_list
) - 1
2139 if len(nodes_list
) == 1:
2141 for i
in range(the_range
):
2142 if nodes_list
== selected_mix
:
2143 add_type
= node_type
+ 'MixRGB'
2144 add
= nodes
.new(add_type
)
2145 add
.blend_type
= mode
2147 add
.inputs
[0].default_value
= 1.0
2148 add
.show_preview
= False
2154 add
.width_hidden
= 100.0
2155 elif nodes_list
== selected_math
:
2156 add_type
= node_type
+ 'Math'
2157 add
= nodes
.new(add_type
)
2158 add
.operation
= mode
2164 add
.width_hidden
= 100.0
2165 elif nodes_list
== selected_shader
:
2167 add_type
= node_type
+ 'MixShader'
2168 add
= nodes
.new(add_type
)
2169 add
.hide
= do_hide_shader
2174 add
.width_hidden
= 100.0
2176 add_type
= node_type
+ 'AddShader'
2177 add
= nodes
.new(add_type
)
2178 add
.hide
= do_hide_shader
2183 add
.width_hidden
= 100.0
2184 elif nodes_list
== selected_z
:
2185 add
= nodes
.new('CompositorNodeZcombine')
2186 add
.show_preview
= False
2192 add
.width_hidden
= 100.0
2193 elif nodes_list
== selected_alphaover
:
2194 add
= nodes
.new('CompositorNodeAlphaOver')
2195 add
.show_preview
= False
2201 add
.width_hidden
= 100.0
2202 add
.location
= loc_x
, loc_y
2206 count_after
= len(nodes
)
2207 index
= count_after
- 1
2208 first_selected
= nodes
[nodes_list
[0][0]]
2209 # "last" node has been added as first, so its index is count_before.
2210 last_add
= nodes
[count_before
]
2212 # Two nodes were selected and first selected has no output links, second selected has output links.
2213 # Then add links from last add to all links 'to_socket' of out links of second selected.
2214 if len(nodes_list
) == 2:
2215 if not first_selected
.outputs
[0].links
:
2216 second_selected
= nodes
[nodes_list
[1][0]]
2217 for ss_link
in second_selected
.outputs
[0].links
:
2218 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2219 # Create list of invalid indexes.
2220 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2221 # Link only if "to_node" index not in invalid indexes list.
2222 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2223 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2224 # add links from last_add to all links 'to_socket' of out links of first selected.
2225 for fs_link
in first_selected
.outputs
[0].links
:
2226 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2227 # Create list of invalid indexes.
2228 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2229 # Link only if "to_node" index not in invalid indexes list.
2230 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2231 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2232 # add link from "first" selected and "first" add node
2233 node_to
= nodes
[count_after
- 1]
2234 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2235 if node_to
.type == 'ZCOMBINE':
2236 for fs_out
in first_selected
.outputs
:
2237 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2238 links
.new(fs_out
, node_to
.inputs
[1])
2240 # add links between added ADD nodes and between selected and ADD nodes
2241 for i
in range(count_adds
):
2242 if i
< count_adds
- 1:
2243 node_from
= nodes
[index
]
2244 node_to
= nodes
[index
- 1]
2245 node_to_input_i
= first
2246 node_to_z_i
= 1 # if z combine - link z to first z input
2247 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2248 if node_to
.type == 'ZCOMBINE':
2249 for from_out
in node_from
.outputs
:
2250 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2251 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2252 if len(nodes_list
) > 1:
2253 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2254 node_to
= nodes
[index
]
2255 node_to_input_i
= second
2256 node_to_z_i
= 3 # if z combine - link z to second z input
2257 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2258 if node_to
.type == 'ZCOMBINE':
2259 for from_out
in node_from
.outputs
:
2260 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2261 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2263 # set "last" of added nodes as active
2264 nodes
.active
= last_add
2265 for i
, x
, y
, dx
, h
in nodes_list
:
2266 nodes
[i
].select
= False
2271 class NWBatchChangeNodes(Operator
, NWBase
):
2272 bl_idname
= "node.nw_batch_change"
2273 bl_label
= "Batch Change"
2274 bl_description
= "Batch Change Blend Type and Math Operation"
2275 bl_options
= {'REGISTER', 'UNDO'}
2277 blend_type
: EnumProperty(
2279 items
=blend_types
+ navs
,
2281 operation
: EnumProperty(
2283 items
=operations
+ navs
,
2286 def execute(self
, context
):
2288 nodes
, links
= get_nodes_links(context
)
2289 blend_type
= self
.blend_type
2290 operation
= self
.operation
2291 for node
in context
.selected_nodes
:
2292 if node
.type == 'MIX_RGB':
2293 if not blend_type
in [nav
[0] for nav
in navs
]:
2294 node
.blend_type
= blend_type
2296 if blend_type
== 'NEXT':
2297 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2298 #index = blend_types.index(node.blend_type)
2299 if index
== len(blend_types
) - 1:
2300 node
.blend_type
= blend_types
[0][0]
2302 node
.blend_type
= blend_types
[index
+ 1][0]
2304 if blend_type
== 'PREV':
2305 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2307 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2309 node
.blend_type
= blend_types
[index
- 1][0]
2311 if node
.type == 'MATH':
2312 if not operation
in [nav
[0] for nav
in navs
]:
2313 node
.operation
= operation
2315 if operation
== 'NEXT':
2316 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2317 #index = operations.index(node.operation)
2318 if index
== len(operations
) - 1:
2319 node
.operation
= operations
[0][0]
2321 node
.operation
= operations
[index
+ 1][0]
2323 if operation
== 'PREV':
2324 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2325 #index = operations.index(node.operation)
2327 node
.operation
= operations
[len(operations
) - 1][0]
2329 node
.operation
= operations
[index
- 1][0]
2334 class NWChangeMixFactor(Operator
, NWBase
):
2335 bl_idname
= "node.nw_factor"
2336 bl_label
= "Change Factor"
2337 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2338 bl_options
= {'REGISTER', 'UNDO'}
2340 # option: Change factor.
2341 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2342 # Else - change factor by option value.
2343 option
: FloatProperty()
2345 def execute(self
, context
):
2346 nodes
, links
= get_nodes_links(context
)
2347 option
= self
.option
2348 selected
= [] # entry = index
2349 for si
, node
in enumerate(nodes
):
2351 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2355 fac
= nodes
[si
].inputs
[0]
2356 nodes
[si
].hide
= False
2357 if option
in {0.0, 1.0}:
2358 fac
.default_value
= option
2360 fac
.default_value
+= option
2365 class NWCopySettings(Operator
, NWBase
):
2366 bl_idname
= "node.nw_copy_settings"
2367 bl_label
= "Copy Settings"
2368 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2369 bl_options
= {'REGISTER', 'UNDO'}
2372 def poll(cls
, context
):
2374 if nw_check(context
):
2375 if context
.active_node
is not None and context
.active_node
.type is not 'FRAME':
2379 def execute(self
, context
):
2380 node_active
= context
.active_node
2381 node_selected
= context
.selected_nodes
2384 if not (len(node_selected
) > 1):
2385 self
.report({'ERROR'}, "2 nodes must be selected at least")
2386 return {'CANCELLED'}
2388 # Check if active node is in the selection
2389 selected_node_names
= [n
.name
for n
in node_selected
]
2390 if node_active
.name
not in selected_node_names
:
2391 self
.report({'ERROR'}, "No active node")
2392 return {'CANCELLED'}
2394 # Get nodes in selection by type
2395 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2397 if not (len(valid_nodes
) > 1) and node_active
:
2398 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2399 return {'CANCELLED'}
2401 if len(valid_nodes
) != len(node_selected
):
2402 # Report nodes that are not valid
2403 valid_node_names
= [n
.name
for n
in valid_nodes
]
2404 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2405 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2407 # Reference original
2409 #node_selected_names = [n.name for n in node_selected]
2414 # Deselect all nodes
2415 for i
in node_selected
:
2418 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2419 # Run through all other nodes
2420 for node
in valid_nodes
[1:]:
2422 # Check for frame node
2423 parent
= node
.parent
if node
.parent
else None
2424 node_loc
= [node
.location
.x
, node
.location
.y
]
2426 # Select original to duplicate
2429 # Duplicate selected node
2430 bpy
.ops
.node
.duplicate()
2431 new_node
= context
.selected_nodes
[0]
2434 new_node
.select
= False
2436 # Properties to copy
2437 node_tree
= node
.id_data
2438 props_to_copy
= 'bl_idname name location height width'.split(' ')
2442 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2443 for i
in (i
for i
in mappings
if i
.is_linked
):
2445 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2448 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2449 props_to_copy
.pop(0)
2451 for prop
in props_to_copy
:
2452 setattr(new_node
, prop
, props
[prop
])
2454 # Get the node tree to remove the old node
2455 nodes
= node_tree
.nodes
2457 new_node
.name
= props
['name']
2460 new_node
.parent
= parent
2461 new_node
.location
= node_loc
2463 for str_from
, str_to
in reconnections
:
2464 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2466 success_names
.append(new_node
.name
)
2469 node_tree
.nodes
.active
= orig
2470 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2474 class NWCopyLabel(Operator
, NWBase
):
2475 bl_idname
= "node.nw_copy_label"
2476 bl_label
= "Copy Label"
2477 bl_options
= {'REGISTER', 'UNDO'}
2479 option
: EnumProperty(
2481 description
="Source of name of label",
2483 ('FROM_ACTIVE', 'from active', 'from active node',),
2484 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2485 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2489 def execute(self
, context
):
2490 nodes
, links
= get_nodes_links(context
)
2491 option
= self
.option
2492 active
= nodes
.active
2493 if option
== 'FROM_ACTIVE':
2495 src_label
= active
.label
2496 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2497 node
.label
= src_label
2498 elif option
== 'FROM_NODE':
2499 selected
= [n
for n
in nodes
if n
.select
]
2500 for node
in selected
:
2501 for input in node
.inputs
:
2503 src
= input.links
[0].from_node
2504 node
.label
= src
.label
2506 elif option
== 'FROM_SOCKET':
2507 selected
= [n
for n
in nodes
if n
.select
]
2508 for node
in selected
:
2509 for input in node
.inputs
:
2511 src
= input.links
[0].from_socket
2512 node
.label
= src
.name
2518 class NWClearLabel(Operator
, NWBase
):
2519 bl_idname
= "node.nw_clear_label"
2520 bl_label
= "Clear Label"
2521 bl_options
= {'REGISTER', 'UNDO'}
2523 option
: BoolProperty()
2525 def execute(self
, context
):
2526 nodes
, links
= get_nodes_links(context
)
2527 for node
in [n
for n
in nodes
if n
.select
]:
2532 def invoke(self
, context
, event
):
2534 return self
.execute(context
)
2536 return context
.window_manager
.invoke_confirm(self
, event
)
2539 class NWModifyLabels(Operator
, NWBase
):
2540 """Modify Labels of all selected nodes"""
2541 bl_idname
= "node.nw_modify_labels"
2542 bl_label
= "Modify Labels"
2543 bl_options
= {'REGISTER', 'UNDO'}
2545 prepend
: StringProperty(
2546 name
="Add to Beginning"
2548 append
: StringProperty(
2551 replace_from
: StringProperty(
2552 name
="Text to Replace"
2554 replace_to
: StringProperty(
2558 def execute(self
, context
):
2559 nodes
, links
= get_nodes_links(context
)
2560 for node
in [n
for n
in nodes
if n
.select
]:
2561 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2565 def invoke(self
, context
, event
):
2569 return context
.window_manager
.invoke_props_dialog(self
)
2572 class NWAddTextureSetup(Operator
, NWBase
):
2573 bl_idname
= "node.nw_add_texture"
2574 bl_label
= "Texture Setup"
2575 bl_description
= "Add Texture Node Setup to Selected Shaders"
2576 bl_options
= {'REGISTER', 'UNDO'}
2578 add_mapping
: BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2581 def poll(cls
, context
):
2583 if nw_check(context
):
2584 space
= context
.space_data
2585 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2589 def execute(self
, context
):
2590 nodes
, links
= get_nodes_links(context
)
2591 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2592 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2593 selected_nodes
= [n
for n
in nodes
if n
.select
]
2594 for t_node
in selected_nodes
:
2598 for index
, i
in enumerate(t_node
.inputs
):
2604 locx
= t_node
.location
.x
2605 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2607 xoffset
= [500, 700]
2609 if t_node
.type in texture_types
+ ['MAPPING']:
2610 xoffset
= [290, 500]
2614 image_type
= 'ShaderNodeTexImage'
2616 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2617 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2618 if t_node
.type == 'BACKGROUND':
2619 image_type
= 'ShaderNodeTexEnvironment'
2622 tex
= nodes
.new(image_type
)
2623 tex
.location
= [locx
- 200, locy
+ 112]
2625 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2627 t_node
.select
= False
2628 if self
.add_mapping
or is_texture
:
2629 if t_node
.type != 'MAPPING':
2630 m
= nodes
.new('ShaderNodeMapping')
2631 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2635 coord
= nodes
.new('ShaderNodeTexCoord')
2636 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2639 links
.new(m
.outputs
[0], tex
.inputs
[0])
2640 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2643 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2644 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2646 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2650 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2651 bl_idname
= "node.nw_add_textures_for_principled"
2652 bl_label
= "Principled Texture Setup"
2653 bl_description
= "Add Texture Node Setup for Principled BSDF"
2654 bl_options
= {'REGISTER', 'UNDO'}
2656 directory
: StringProperty(
2660 description
='Folder to search in for image files'
2662 files
: CollectionProperty(
2663 type=bpy
.types
.OperatorFileListElement
,
2664 options
={'HIDDEN', 'SKIP_SAVE'}
2673 def poll(cls
, context
):
2675 if nw_check(context
):
2676 space
= context
.space_data
2677 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2681 def execute(self
, context
):
2682 # Check if everything is ok
2683 if not self
.directory
:
2684 self
.report({'INFO'}, 'No Folder Selected')
2685 return {'CANCELLED'}
2686 if not self
.files
[:]:
2687 self
.report({'INFO'}, 'No Files Selected')
2688 return {'CANCELLED'}
2690 nodes
, links
= get_nodes_links(context
)
2691 active_node
= nodes
.active
2692 if not active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled':
2693 self
.report({'INFO'}, 'Select Principled BSDF')
2694 return {'CANCELLED'}
2697 def split_into__components(fname
):
2698 # Split filename into components
2699 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2701 fname
= path
.splitext(fname
)[0]
2703 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2704 # Separate CamelCase by space
2705 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2706 # Replace common separators with SPACE
2707 seperators
= ['_', '.', '-', '__', '--', '#']
2708 for sep
in seperators
:
2709 fname
= fname
.replace(sep
, ' ')
2711 components
= fname
.split(' ')
2712 components
= [c
.lower() for c
in components
]
2715 # Filter textures names for texturetypes in filenames
2716 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2717 tags
= context
.user_preferences
.addons
[__name__
].preferences
.principled_tags
2718 normal_abbr
= tags
.normal
.split(' ')
2719 bump_abbr
= tags
.bump
.split(' ')
2720 gloss_abbr
= tags
.gloss
.split(' ')
2721 rough_abbr
= tags
.rough
.split(' ')
2723 ['Displacement', tags
.displacement
.split(' '), None],
2724 ['Base Color', tags
.base_color
.split(' '), None],
2725 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2726 ['Metallic', tags
.metallic
.split(' '), None],
2727 ['Specular', tags
.specular
.split(' '), None],
2728 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2729 ['Normal', normal_abbr
+ bump_abbr
, None],
2732 # Look through texture_types and set value as filename of first matched file
2733 def match_files_to_socket_names():
2734 for sname
in socketnames
:
2735 for file in self
.files
:
2737 filenamecomponents
= split_into__components(fname
)
2738 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
2739 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
2744 match_files_to_socket_names()
2745 # Remove socketnames without found files
2746 socketnames
= [s
for s
in socketnames
if s
[2]
2747 and path
.exists(self
.directory
+s
[2])]
2749 self
.report({'INFO'}, 'No matching images found')
2750 print('No matching images found')
2751 return {'CANCELLED'}
2754 print('\nMatched Textures:')
2758 roughness_node
= None
2759 for i
, sname
in enumerate(socketnames
):
2760 print(i
, sname
[0], sname
[2])
2762 # DISPLACEMENT NODES
2763 if sname
[0] == 'Displacement':
2764 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
2765 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2766 disp_texture
.image
= img
2767 disp_texture
.label
= 'Displacement'
2768 disp_texture
.color_space
= 'NONE'
2770 # Add displacement offset nodes
2771 disp_node
= nodes
.new(type='ShaderNodeDisplacement')
2772 disp_node
.location
= active_node
.location
+ Vector((0, -560))
2773 link
= links
.new(disp_node
.inputs
[0], disp_texture
.outputs
[0])
2775 # TODO Turn on true displacement in the material
2776 # Too complicated for now
2779 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
2781 if not output_node
[0].inputs
[2].is_linked
:
2782 link
= links
.new(output_node
[0].inputs
[2], disp_node
.outputs
[0])
2786 if not active_node
.inputs
[sname
[0]].is_linked
:
2787 # No texture node connected -> add texture node with new image
2788 texture_node
= nodes
.new(type='ShaderNodeTexImage')
2789 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2790 texture_node
.image
= img
2793 if sname
[0] == 'Normal':
2794 # Test if new texture node is normal or bump map
2795 fname_components
= split_into__components(sname
[2])
2796 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
2797 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
2799 # If Normal add normal node in between
2800 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
2801 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
2803 # If Bump add bump node in between
2804 normal_node
= nodes
.new(type='ShaderNodeBump')
2805 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
2807 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
2808 normal_node_texture
= texture_node
2810 elif sname
[0] == 'Roughness':
2811 # Test if glossy or roughness map
2812 fname_components
= split_into__components(sname
[2])
2813 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
2814 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
2817 # If Roughness nothing to to
2818 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2821 # If Gloss Map add invert node
2822 invert_node
= nodes
.new(type='ShaderNodeInvert')
2823 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
2825 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
2826 roughness_node
= texture_node
2829 # This is a simple connection Texture --> Input slot
2830 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2832 # Use non-color for all but 'Base Color' Textures
2833 if not sname
[0] in ['Base Color']:
2834 texture_node
.color_space
= 'NONE'
2837 # If already texture connected. add to node list for alignment
2838 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
2840 # This are all connected texture nodes
2841 texture_nodes
.append(texture_node
)
2842 texture_node
.label
= sname
[0]
2845 texture_nodes
.append(disp_texture
)
2848 for i
, texture_node
in enumerate(texture_nodes
):
2849 offset
= Vector((-550, (i
* -280) + 200))
2850 texture_node
.location
= active_node
.location
+ offset
2853 # Extra alignment if normal node was added
2854 normal_node
.location
= normal_node_texture
.location
+ Vector((300, 0))
2857 # Alignment of invert node if glossy map
2858 invert_node
.location
= roughness_node
.location
+ Vector((300, 0))
2860 # Add texture input + mapping
2861 mapping
= nodes
.new(type='ShaderNodeMapping')
2862 mapping
.location
= active_node
.location
+ Vector((-1050, 0))
2863 if len(texture_nodes
) > 1:
2864 # If more than one texture add reroute node in between
2865 reroute
= nodes
.new(type='NodeReroute')
2866 texture_nodes
.append(reroute
)
2867 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
2868 reroute
.location
= tex_coords
+ Vector((-50, -120))
2869 for texture_node
in texture_nodes
:
2870 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
2871 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
2873 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
2875 # Connect texture_coordiantes to mapping node
2876 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
2877 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
2878 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
2880 # Create frame around tex coords and mapping
2881 frame
= nodes
.new(type='NodeFrame')
2882 frame
.label
= 'Mapping'
2883 mapping
.parent
= frame
2884 texture_input
.parent
= frame
2887 # Create frame around texture nodes
2888 frame
= nodes
.new(type='NodeFrame')
2889 frame
.label
= 'Textures'
2890 for tnode
in texture_nodes
:
2891 tnode
.parent
= frame
2895 active_node
.select
= False
2898 force_update(context
)
2902 class NWAddReroutes(Operator
, NWBase
):
2903 """Add Reroute Nodes and link them to outputs of selected nodes"""
2904 bl_idname
= "node.nw_add_reroutes"
2905 bl_label
= "Add Reroutes"
2906 bl_description
= "Add Reroutes to Outputs"
2907 bl_options
= {'REGISTER', 'UNDO'}
2909 option
: EnumProperty(
2912 ('ALL', 'to all', 'Add to all outputs'),
2913 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2914 ('LINKED', 'to linked', 'Add only to linked outputs'),
2918 def execute(self
, context
):
2919 tree_type
= context
.space_data
.node_tree
.type
2920 option
= self
.option
2921 nodes
, links
= get_nodes_links(context
)
2922 # output valid when option is 'all' or when 'loose' output has no links
2924 post_select
= [] # nodes to be selected after execution
2925 # create reroutes and recreate links
2926 for node
in [n
for n
in nodes
if n
.select
]:
2931 # unhide 'REROUTE' nodes to avoid issues with location.y
2932 if node
.type == 'REROUTE':
2934 # When node is hidden - width_hidden not usable.
2935 # Hack needed to calculate real width
2937 bpy
.ops
.node
.select_all(action
='DESELECT')
2938 helper
= nodes
.new('NodeReroute')
2939 helper
.select
= True
2941 # resize node and helper to zero. Then check locations to calculate width
2942 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2943 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2944 # restore node location
2945 node
.location
= x
, y
2948 # only helper is selected now
2949 bpy
.ops
.node
.delete()
2950 x
= node
.location
.x
+ width
+ 20.0
2951 if node
.type != 'REROUTE':
2955 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2956 for out_i
, output
in enumerate(node
.outputs
):
2957 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2958 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2959 if node
.type != 'R_LAYERS':
2961 else: # if 'R_LAYERS' check if output represent used render pass
2962 node_scene
= node
.scene
2963 node_layer
= node
.layer
2964 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2965 if output
.name
== 'Alpha':
2968 # check entries in global 'rl_outputs' variable
2969 #for render_pass, output_name, exr_name, in_internal, in_cycles in rl_outputs:
2970 for rlo
in rl_outputs
:
2971 if output
.name
== rlo
.output_name
or output
.name
== rlo
.exr_output_name
:
2972 pass_used
= getattr(node_scene
.render
.layers
[node_layer
], rlo
.render_pass
)
2975 valid
= ((option
== 'ALL') or
2976 (option
== 'LOOSE' and not output
.links
) or
2977 (option
== 'LINKED' and output
.links
))
2978 # Add reroutes only if valid, but offset location in all cases.
2980 n
= nodes
.new('NodeReroute')
2982 for link
in output
.links
:
2983 links
.new(n
.outputs
[0], link
.to_socket
)
2984 links
.new(output
, n
.inputs
[0])
2986 post_select
.append(n
)
2990 # disselect the node so that after execution of script only newly created nodes are selected
2992 # nicer reroutes distribution along y when node.hide
2994 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2995 for reroute
in [r
for r
in nodes
if r
.select
]:
2996 reroute
.location
.y
-= y_translate
2997 for node
in post_select
:
3003 class NWLinkActiveToSelected(Operator
, NWBase
):
3004 """Link active node to selected nodes basing on various criteria"""
3005 bl_idname
= "node.nw_link_active_to_selected"
3006 bl_label
= "Link Active Node to Selected"
3007 bl_options
= {'REGISTER', 'UNDO'}
3009 replace
: BoolProperty()
3010 use_node_name
: BoolProperty()
3011 use_outputs_names
: BoolProperty()
3014 def poll(cls
, context
):
3016 if nw_check(context
):
3017 if context
.active_node
is not None:
3018 if context
.active_node
.select
:
3022 def execute(self
, context
):
3023 nodes
, links
= get_nodes_links(context
)
3024 replace
= self
.replace
3025 use_node_name
= self
.use_node_name
3026 use_outputs_names
= self
.use_outputs_names
3027 active
= nodes
.active
3028 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3029 outputs
= [] # Only usable outputs of active nodes will be stored here.
3030 for out
in active
.outputs
:
3031 if active
.type != 'R_LAYERS':
3034 # 'R_LAYERS' node type needs special handling.
3035 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3036 # Only outputs that represent used passes should be taken into account
3037 # Check if pass represented by output is used.
3038 # global 'rl_outputs' list will be used for that
3039 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3040 pass_used
= False # initial value. Will be set to True if pass is used
3041 if out
.name
== 'Alpha':
3042 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3044 elif out
.name
== out_name
:
3045 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3046 pass_used
= getattr(active
.scene
.render
.layers
[active
.layer
], render_pass
)
3050 doit
= True # Will be changed to False when links successfully added to previous output.
3053 for node
in selected
:
3054 dst_name
= node
.name
# Will be compared with src_name if needed.
3055 # When node has label - use it as dst_name
3057 dst_name
= node
.label
3058 valid
= True # Initial value. Will be changed to False if names don't match.
3059 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3061 # Set src_name to source node name or label
3062 src_name
= active
.name
3064 src_name
= active
.label
3065 elif use_outputs_names
:
3066 src_name
= (out
.name
, )
3067 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3068 if out
.name
in {out_name
, exr_name
}:
3069 src_name
= (out_name
, exr_name
)
3070 if dst_name
not in src_name
:
3073 for input in node
.inputs
:
3074 if input.type == out
.type or node
.type == 'REROUTE':
3075 if replace
or not input.is_linked
:
3076 links
.new(out
, input)
3077 if not use_node_name
and not use_outputs_names
:
3084 class NWAlignNodes(Operator
, NWBase
):
3085 '''Align the selected nodes neatly in a row/column'''
3086 bl_idname
= "node.nw_align_nodes"
3087 bl_label
= "Align Nodes"
3088 bl_options
= {'REGISTER', 'UNDO'}
3089 margin
: IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3091 def execute(self
, context
):
3092 nodes
, links
= get_nodes_links(context
)
3093 margin
= self
.margin
3097 if node
.select
and node
.type != 'FRAME':
3098 selection
.append(node
)
3100 # If no nodes are selected, align all nodes
3104 elif nodes
.active
in selection
:
3105 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3107 # Check if nodes should be laid out horizontally or vertically
3108 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3109 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3110 x_range
= max(x_locs
) - min(x_locs
)
3111 y_range
= max(y_locs
) - min(y_locs
)
3112 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3113 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3114 horizontal
= x_range
> y_range
3116 # Sort selection by location of node mid-point
3118 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3120 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3124 for node
in selection
:
3125 current_margin
= margin
3126 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3129 node
.location
.x
= current_pos
3130 current_pos
+= current_margin
+ node
.dimensions
.x
3131 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3133 node
.location
.y
= current_pos
3134 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3135 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3137 # If active node is selected, center nodes around it
3138 if active_loc
is not None:
3139 active_loc_diff
= active_loc
- nodes
.active
.location
3140 for node
in selection
:
3141 node
.location
+= active_loc_diff
3142 else: # Position nodes centered around where they used to be
3143 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
])
3144 new_mid
= (max(locs
) + min(locs
)) / 2
3145 for node
in selection
:
3147 node
.location
.x
+= (mid_x
- new_mid
)
3149 node
.location
.y
+= (mid_y
- new_mid
)
3154 class NWSelectParentChildren(Operator
, NWBase
):
3155 bl_idname
= "node.nw_select_parent_child"
3156 bl_label
= "Select Parent or Children"
3157 bl_options
= {'REGISTER', 'UNDO'}
3159 option
: EnumProperty(
3162 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3163 ('CHILD', 'Select Children', 'Select members of selected frame'),
3167 def execute(self
, context
):
3168 nodes
, links
= get_nodes_links(context
)
3169 option
= self
.option
3170 selected
= [node
for node
in nodes
if node
.select
]
3171 if option
== 'PARENT':
3172 for sel
in selected
:
3175 parent
.select
= True
3176 else: # option == 'CHILD'
3177 for sel
in selected
:
3178 children
= [node
for node
in nodes
if node
.parent
== sel
]
3179 for kid
in children
:
3185 class NWDetachOutputs(Operator
, NWBase
):
3186 """Detach outputs of selected node leaving inputs linked"""
3187 bl_idname
= "node.nw_detach_outputs"
3188 bl_label
= "Detach Outputs"
3189 bl_options
= {'REGISTER', 'UNDO'}
3191 def execute(self
, context
):
3192 nodes
, links
= get_nodes_links(context
)
3193 selected
= context
.selected_nodes
3194 bpy
.ops
.node
.duplicate_move_keep_inputs()
3195 new_nodes
= context
.selected_nodes
3196 bpy
.ops
.node
.select_all(action
="DESELECT")
3197 for node
in selected
:
3199 bpy
.ops
.node
.delete_reconnect()
3200 for new_node
in new_nodes
:
3201 new_node
.select
= True
3202 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3207 class NWLinkToOutputNode(Operator
, NWBase
):
3208 """Link to Composite node or Material Output node"""
3209 bl_idname
= "node.nw_link_out"
3210 bl_label
= "Connect to Output"
3211 bl_options
= {'REGISTER', 'UNDO'}
3214 def poll(cls
, context
):
3216 if nw_check(context
):
3217 if context
.active_node
is not None:
3218 for out
in context
.active_node
.outputs
:
3224 def execute(self
, context
):
3225 nodes
, links
= get_nodes_links(context
)
3226 active
= nodes
.active
3229 tree_type
= context
.space_data
.tree_type
3230 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3231 output_types_compo
= ['COMPOSITE']
3232 output_types_blender_mat
= ['OUTPUT']
3233 output_types_textures
= ['OUTPUT']
3234 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3236 if node
.type in output_types
:
3240 bpy
.ops
.node
.select_all(action
="DESELECT")
3241 if tree_type
== 'ShaderNodeTree':
3242 if is_cycles_or_eevee(context
):
3243 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3245 output_node
= nodes
.new('ShaderNodeOutput')
3246 elif tree_type
== 'CompositorNodeTree':
3247 output_node
= nodes
.new('CompositorNodeComposite')
3248 elif tree_type
== 'TextureNodeTree':
3249 output_node
= nodes
.new('TextureNodeOutput')
3250 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3251 output_node
.location
.y
= active
.location
.y
3252 if (output_node
and active
.outputs
):
3253 for i
, output
in enumerate(active
.outputs
):
3257 for i
, output
in enumerate(active
.outputs
):
3258 if output
.type == output_node
.inputs
[0].type and not output
.hide
:
3263 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3264 if active
.outputs
[output_index
].name
== 'Volume':
3266 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3268 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3270 force_update(context
) # viewport render does not update
3275 class NWMakeLink(Operator
, NWBase
):
3276 """Make a link from one socket to another"""
3277 bl_idname
= 'node.nw_make_link'
3278 bl_label
= 'Make Link'
3279 bl_options
= {'REGISTER', 'UNDO'}
3280 from_socket
: IntProperty()
3281 to_socket
: IntProperty()
3283 def execute(self
, context
):
3284 nodes
, links
= get_nodes_links(context
)
3286 n1
= nodes
[context
.scene
.NWLazySource
]
3287 n2
= nodes
[context
.scene
.NWLazyTarget
]
3289 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3291 force_update(context
)
3296 class NWCallInputsMenu(Operator
, NWBase
):
3297 """Link from this output"""
3298 bl_idname
= 'node.nw_call_inputs_menu'
3299 bl_label
= 'Make Link'
3300 bl_options
= {'REGISTER', 'UNDO'}
3301 from_socket
: IntProperty()
3303 def execute(self
, context
):
3304 nodes
, links
= get_nodes_links(context
)
3306 context
.scene
.NWSourceSocket
= self
.from_socket
3308 n1
= nodes
[context
.scene
.NWLazySource
]
3309 n2
= nodes
[context
.scene
.NWLazyTarget
]
3310 if len(n2
.inputs
) > 1:
3311 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3312 elif len(n2
.inputs
) == 1:
3313 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3317 class NWAddSequence(Operator
, ImportHelper
):
3318 """Add an Image Sequence"""
3319 bl_idname
= 'node.nw_add_sequence'
3320 bl_label
= 'Import Image Sequence'
3321 bl_options
= {'REGISTER', 'UNDO'}
3323 directory
: StringProperty(
3326 filename
: StringProperty(
3329 files
: CollectionProperty(
3330 type=bpy
.types
.OperatorFileListElement
,
3331 options
={'HIDDEN', 'SKIP_SAVE'}
3334 def execute(self
, context
):
3335 nodes
, links
= get_nodes_links(context
)
3336 directory
= self
.directory
3337 filename
= self
.filename
3339 tree
= context
.space_data
.node_tree
3342 # print ("\nDIR:", directory)
3343 # print ("FN:", filename)
3344 # print ("Fs:", list(f.name for f in files), '\n')
3346 if tree
.type == 'SHADER':
3347 node_type
= "ShaderNodeTexImage"
3348 elif tree
.type == 'COMPOSITING':
3349 node_type
= "CompositorNodeImage"
3351 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3352 return {'CANCELLED'}
3354 if not files
[0].name
and not filename
:
3355 self
.report({'ERROR'}, "No file chosen")
3356 return {'CANCELLED'}
3357 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3358 # User has selected multiple files without an active one, or the active one is non-existant
3359 filename
= files
[0].name
3361 if not path
.exists(directory
+filename
):
3362 self
.report({'ERROR'}, filename
+" does not exist!")
3363 return {'CANCELLED'}
3365 without_ext
= '.'.join(filename
.split('.')[:-1])
3367 # if last digit isn't a number, it's not a sequence
3368 if not without_ext
[-1].isdigit():
3369 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3370 return {'CANCELLED'}
3373 extension
= filename
.split('.')[-1]
3374 reverse
= without_ext
[::-1] # reverse string
3377 for char
in reverse
:
3383 without_num
= without_ext
[:count_numbers
*-1]
3385 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3387 num_frames
= len(files
)
3389 nodes_list
= [node
for node
in nodes
]
3391 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3392 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3396 yloc
+= node_mid_pt(node
, 'y')
3397 yloc
= yloc
/len(nodes
)
3402 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3404 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3406 node
.label
= name_with_hashes
3408 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3409 img
.source
= 'SEQUENCE'
3410 img
.name
= name_with_hashes
3412 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3413 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
3414 image_user
.frame_duration
= num_frames
3419 class NWAddMultipleImages(Operator
, ImportHelper
):
3420 """Add multiple images at once"""
3421 bl_idname
= 'node.nw_add_multiple_images'
3422 bl_label
= 'Open Selected Images'
3423 bl_options
= {'REGISTER', 'UNDO'}
3424 directory
: StringProperty(
3427 files
: CollectionProperty(
3428 type=bpy
.types
.OperatorFileListElement
,
3429 options
={'HIDDEN', 'SKIP_SAVE'}
3432 def execute(self
, context
):
3433 nodes
, links
= get_nodes_links(context
)
3435 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3437 if context
.space_data
.node_tree
.type == 'SHADER':
3438 node_type
= "ShaderNodeTexImage"
3439 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3440 node_type
= "CompositorNodeImage"
3442 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3443 return {'CANCELLED'}
3446 for f
in self
.files
:
3449 node
= nodes
.new(node_type
)
3450 new_nodes
.append(node
)
3453 node
.width_hidden
= 100
3454 node
.location
.x
= xloc
3455 node
.location
.y
= yloc
3458 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3461 # shift new nodes up to center of tree
3462 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3464 if node
in new_nodes
:
3466 node
.location
.y
+= (list_size
/2)
3472 class NWViewerFocus(bpy
.types
.Operator
):
3473 """Set the viewer tile center to the mouse position"""
3474 bl_idname
= "node.nw_viewer_focus"
3475 bl_label
= "Viewer Focus"
3477 x
: bpy
.props
.IntProperty()
3478 y
: bpy
.props
.IntProperty()
3481 def poll(cls
, context
):
3482 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3484 def execute(self
, context
):
3487 def invoke(self
, context
, event
):
3488 render
= context
.scene
.render
3489 space
= context
.space_data
3490 percent
= render
.resolution_percentage
*0.01
3492 nodes
, links
= get_nodes_links(context
)
3493 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3496 mlocx
= event
.mouse_region_x
3497 mlocy
= event
.mouse_region_y
3498 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3500 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3501 region_x
= context
.region
.width
3502 region_y
= context
.region
.height
3504 region_center_x
= context
.region
.width
/ 2
3505 region_center_y
= context
.region
.height
/ 2
3507 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3508 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3510 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_x
3511 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_y
3513 margin_x
= region_center_x
- backdrop_center_x
3514 margin_y
= region_center_y
- backdrop_center_y
3516 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3517 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3519 for node
in viewers
:
3520 node
.center_x
= abs_mouse_x
3521 node
.center_y
= abs_mouse_y
3523 return {'PASS_THROUGH'}
3525 return self
.execute(context
)
3528 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3529 """Save the current viewer node to an image file"""
3530 bl_idname
= "node.nw_save_viewer"
3531 bl_label
= "Save This Image"
3532 filepath
: StringProperty(subtype
="FILE_PATH")
3533 filename_ext
: EnumProperty(
3535 description
="Choose the file format to save to",
3536 items
=(('.bmp', "PNG", ""),
3537 ('.rgb', 'IRIS', ""),
3538 ('.png', 'PNG', ""),
3539 ('.jpg', 'JPEG', ""),
3540 ('.jp2', 'JPEG2000', ""),
3541 ('.tga', 'TARGA', ""),
3542 ('.cin', 'CINEON', ""),
3543 ('.dpx', 'DPX', ""),
3544 ('.exr', 'OPEN_EXR', ""),
3545 ('.hdr', 'HDR', ""),
3546 ('.tif', 'TIFF', "")),
3551 def poll(cls
, context
):
3553 if nw_check(context
):
3554 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3555 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3556 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3560 def execute(self
, context
):
3577 basename
, ext
= path
.splitext(fp
)
3578 old_render_format
= context
.scene
.render
.image_settings
.file_format
3579 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3580 context
.area
.type = "IMAGE_EDITOR"
3581 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3582 context
.area
.spaces
[0].image
.save_render(fp
)
3583 context
.area
.type = "NODE_EDITOR"
3584 context
.scene
.render
.image_settings
.file_format
= old_render_format
3588 class NWResetNodes(bpy
.types
.Operator
):
3589 """Reset Nodes in Selection"""
3590 bl_idname
= "node.nw_reset_nodes"
3591 bl_label
= "Reset Nodes"
3592 bl_options
= {'REGISTER', 'UNDO'}
3595 def poll(cls
, context
):
3596 space
= context
.space_data
3597 return space
.type == 'NODE_EDITOR'
3599 def execute(self
, context
):
3600 node_active
= context
.active_node
3601 node_selected
= context
.selected_nodes
3602 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3604 # Check if one node is selected at least
3605 if not (len(node_selected
) > 0):
3606 self
.report({'ERROR'}, "1 node must be selected at least")
3607 return {'CANCELLED'}
3609 active_node_name
= node_active
.name
if node_active
.select
else None
3610 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3612 # Create output lists
3613 selected_node_names
= [n
.name
for n
in node_selected
]
3616 # Reset all valid children in a frame
3617 node_active_is_frame
= False
3618 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3619 node_tree
= node_active
.id_data
3620 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3622 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3623 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3624 node_active_is_frame
= True
3626 # Check if valid nodes in selection
3627 if not (len(valid_nodes
) > 0):
3628 # Check for frames only
3629 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3630 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3631 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3633 self
.report({'ERROR'}, "No valid node(s) in selection")
3634 return {'CANCELLED'}
3636 # Report nodes that are not valid
3637 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3638 valid_node_names
= [n
.name
for n
in valid_nodes
]
3639 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3640 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3642 # Deselect all nodes
3643 for i
in node_selected
:
3646 # Run through all valid nodes
3647 for node
in valid_nodes
:
3649 parent
= node
.parent
if node
.parent
else None
3650 node_loc
= [node
.location
.x
, node
.location
.y
]
3652 node_tree
= node
.id_data
3653 props_to_copy
= 'bl_idname name location height width'.split(' ')
3656 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3657 for i
in (i
for i
in mappings
if i
.is_linked
):
3659 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3661 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3663 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3664 props_to_copy
.pop(0)
3666 for prop
in props_to_copy
:
3667 setattr(new_node
, prop
, props
[prop
])
3669 nodes
= node_tree
.nodes
3671 new_node
.name
= props
['name']
3674 new_node
.parent
= parent
3675 new_node
.location
= node_loc
3677 for str_from
, str_to
in reconnections
:
3678 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3680 new_node
.select
= False
3681 success_names
.append(new_node
.name
)
3683 # Reselect all nodes
3684 if selected_node_names
and node_active_is_frame
is False:
3685 for i
in selected_node_names
:
3686 node_tree
.nodes
[i
].select
= True
3688 if active_node_name
is not None:
3689 node_tree
.nodes
[active_node_name
].select
= True
3690 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3692 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3700 def drawlayout(context
, layout
, mode
='non-panel'):
3701 tree_type
= context
.space_data
.tree_type
3703 col
= layout
.column(align
=True)
3704 col
.menu(NWMergeNodesMenu
.bl_idname
)
3707 col
= layout
.column(align
=True)
3708 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3711 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3712 col
= layout
.column(align
=True)
3713 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3714 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3717 col
= layout
.column(align
=True)
3718 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3719 col
.operator(NWSwapLinks
.bl_idname
)
3720 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3723 col
= layout
.column(align
=True)
3724 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3725 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3728 col
= layout
.column(align
=True)
3730 row
= col
.row(align
=True)
3731 row
.operator(NWClearLabel
.bl_idname
).option
= True
3732 row
.operator(NWModifyLabels
.bl_idname
)
3734 col
.operator(NWClearLabel
.bl_idname
).option
= True
3735 col
.operator(NWModifyLabels
.bl_idname
)
3736 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3738 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
3741 col
= layout
.column(align
=True)
3742 if tree_type
== 'CompositorNodeTree':
3743 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
3744 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
3747 col
= layout
.column(align
=True)
3748 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3751 col
= layout
.column(align
=True)
3752 col
.operator(NWAlignNodes
.bl_idname
, icon
='CENTER_ONLY')
3755 col
= layout
.column(align
=True)
3756 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
3760 class NodeWranglerPanel(Panel
, NWBase
):
3761 bl_idname
= "NODE_PT_nw_node_wrangler"
3762 bl_space_type
= 'NODE_EDITOR'
3763 bl_label
= "Node Wrangler"
3764 bl_region_type
= "TOOLS"
3765 bl_category
= "Node Wrangler"
3767 prepend
: StringProperty(
3770 append
: StringProperty()
3771 remove
: StringProperty()
3773 def draw(self
, context
):
3774 self
.layout
.label(text
="(Quick access: Ctrl+Space)")
3775 drawlayout(context
, self
.layout
, mode
='panel')
3781 class NodeWranglerMenu(Menu
, NWBase
):
3782 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
3783 bl_label
= "Node Wrangler"
3785 def draw(self
, context
):
3786 drawlayout(context
, self
.layout
)
3789 class NWMergeNodesMenu(Menu
, NWBase
):
3790 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
3791 bl_label
= "Merge Selected Nodes"
3793 def draw(self
, context
):
3794 type = context
.space_data
.tree_type
3795 layout
= self
.layout
3796 if type == 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3797 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
3798 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
3799 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
3800 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
3802 props
.merge_type
= 'ZCOMBINE'
3803 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
3805 props
.merge_type
= 'ALPHAOVER'
3808 class NWMergeShadersMenu(Menu
, NWBase
):
3809 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
3810 bl_label
= "Merge Selected Nodes using Shaders"
3812 def draw(self
, context
):
3813 layout
= self
.layout
3814 for type in ('MIX', 'ADD'):
3815 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
3817 props
.merge_type
= 'SHADER'
3820 class NWMergeMixMenu(Menu
, NWBase
):
3821 bl_idname
= "NODE_MT_nw_merge_mix_menu"
3822 bl_label
= "Merge Selected Nodes using Mix"
3824 def draw(self
, context
):
3825 layout
= self
.layout
3826 for type, name
, description
in blend_types
:
3827 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3829 props
.merge_type
= 'MIX'
3832 class NWConnectionListOutputs(Menu
, NWBase
):
3833 bl_idname
= "NODE_MT_nw_connection_list_out"
3836 def draw(self
, context
):
3837 layout
= self
.layout
3838 nodes
, links
= get_nodes_links(context
)
3840 n1
= nodes
[context
.scene
.NWLazySource
]
3842 if n1
.type == "R_LAYERS":
3844 for o
in n1
.outputs
:
3845 if o
.enabled
: # Check which passes the render layer has enabled
3846 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3850 for o
in n1
.outputs
:
3851 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3855 class NWConnectionListInputs(Menu
, NWBase
):
3856 bl_idname
= "NODE_MT_nw_connection_list_in"
3859 def draw(self
, context
):
3860 layout
= self
.layout
3861 nodes
, links
= get_nodes_links(context
)
3863 n2
= nodes
[context
.scene
.NWLazyTarget
]
3867 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3868 op
.from_socket
= context
.scene
.NWSourceSocket
3869 op
.to_socket
= index
3873 class NWMergeMathMenu(Menu
, NWBase
):
3874 bl_idname
= "NODE_MT_nw_merge_math_menu"
3875 bl_label
= "Merge Selected Nodes using Math"
3877 def draw(self
, context
):
3878 layout
= self
.layout
3879 for type, name
, description
in operations
:
3880 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3882 props
.merge_type
= 'MATH'
3885 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3886 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3887 bl_label
= "Batch Change Selected Nodes"
3889 def draw(self
, context
):
3890 layout
= self
.layout
3891 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3892 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3895 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3896 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3897 bl_label
= "Batch Change Blend Type"
3899 def draw(self
, context
):
3900 layout
= self
.layout
3901 for type, name
, description
in blend_types
:
3902 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3903 props
.blend_type
= type
3904 props
.operation
= 'CURRENT'
3907 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3908 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3909 bl_label
= "Batch Change Math Operation"
3911 def draw(self
, context
):
3912 layout
= self
.layout
3913 for type, name
, description
in operations
:
3914 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3915 props
.blend_type
= 'CURRENT'
3916 props
.operation
= type
3919 class NWCopyToSelectedMenu(Menu
, NWBase
):
3920 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3921 bl_label
= "Copy to Selected"
3923 def draw(self
, context
):
3924 layout
= self
.layout
3925 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3926 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3929 class NWCopyLabelMenu(Menu
, NWBase
):
3930 bl_idname
= "NODE_MT_nw_copy_label_menu"
3931 bl_label
= "Copy Label"
3933 def draw(self
, context
):
3934 layout
= self
.layout
3935 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3936 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3937 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3940 class NWAddReroutesMenu(Menu
, NWBase
):
3941 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3942 bl_label
= "Add Reroutes"
3943 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3945 def draw(self
, context
):
3946 layout
= self
.layout
3947 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3948 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3949 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3952 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3953 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3954 bl_label
= "Link Active to Selected"
3956 def draw(self
, context
):
3957 layout
= self
.layout
3958 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3959 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3960 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3963 class NWLinkStandardMenu(Menu
, NWBase
):
3964 bl_idname
= "NODE_MT_nw_link_standard_menu"
3965 bl_label
= "To All Selected"
3967 def draw(self
, context
):
3968 layout
= self
.layout
3969 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3970 props
.replace
= False
3971 props
.use_node_name
= False
3972 props
.use_outputs_names
= False
3973 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3974 props
.replace
= True
3975 props
.use_node_name
= False
3976 props
.use_outputs_names
= False
3979 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3980 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3981 bl_label
= "Use Node Name/Label"
3983 def draw(self
, context
):
3984 layout
= self
.layout
3985 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3986 props
.replace
= False
3987 props
.use_node_name
= True
3988 props
.use_outputs_names
= False
3989 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3990 props
.replace
= True
3991 props
.use_node_name
= True
3992 props
.use_outputs_names
= False
3995 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3996 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
3997 bl_label
= "Use Outputs Names"
3999 def draw(self
, context
):
4000 layout
= self
.layout
4001 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
4002 props
.replace
= False
4003 props
.use_node_name
= False
4004 props
.use_outputs_names
= True
4005 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4006 props
.replace
= True
4007 props
.use_node_name
= False
4008 props
.use_outputs_names
= True
4011 class NWVertColMenu(bpy
.types
.Menu
):
4012 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
4013 bl_label
= "Vertex Colors"
4016 def poll(cls
, context
):
4018 if nw_check(context
):
4019 snode
= context
.space_data
4020 valid
= snode
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
)
4023 def draw(self
, context
):
4025 nodes
, links
= get_nodes_links(context
)
4026 mat
= context
.object.active_material
4029 for obj
in bpy
.data
.objects
:
4030 for slot
in obj
.material_slots
:
4031 if slot
.material
== mat
:
4035 if obj
.data
.vertex_colors
:
4036 for vcol
in obj
.data
.vertex_colors
:
4037 vcols
.append(vcol
.name
)
4038 vcols
= list(set(vcols
)) # get a unique list
4042 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4044 l
.label("No Vertex Color layers on objects with this material")
4047 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4048 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4049 bl_label
= "Switch Type to..."
4051 def draw(self
, context
):
4052 layout
= self
.layout
4053 tree
= context
.space_data
.node_tree
4054 if tree
.type == 'SHADER':
4055 if is_cycles_or_eevee(context
):
4056 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4057 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4058 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4059 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4060 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4061 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4062 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4063 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4065 layout
.menu(NWSwitchMatInputSubmenu
.bl_idname
)
4066 layout
.menu(NWSwitchMatOutputSubmenu
.bl_idname
)
4067 layout
.menu(NWSwitchMatColorSubmenu
.bl_idname
)
4068 layout
.menu(NWSwitchMatVectorSubmenu
.bl_idname
)
4069 layout
.menu(NWSwitchMatConverterSubmenu
.bl_idname
)
4070 layout
.menu(NWSwitchMatLayoutSubmenu
.bl_idname
)
4071 if tree
.type == 'COMPOSITING':
4072 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4073 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4074 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4075 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4076 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4077 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4078 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4079 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4080 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4081 if tree
.type == 'TEXTURE':
4082 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4083 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4084 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4085 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4086 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4087 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4088 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4089 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4092 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4093 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4096 def draw(self
, context
):
4097 layout
= self
.layout
4098 for ident
, node_type
, rna_name
in sorted(shaders_input_nodes_props
, key
=lambda k
: k
[2]):
4099 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4100 props
.to_type
= ident
4103 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4104 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4107 def draw(self
, context
):
4108 layout
= self
.layout
4109 for ident
, node_type
, rna_name
in sorted(shaders_output_nodes_props
, key
=lambda k
: k
[2]):
4110 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4111 props
.to_type
= ident
4114 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4115 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4118 def draw(self
, context
):
4119 layout
= self
.layout
4120 for ident
, node_type
, rna_name
in sorted(shaders_shader_nodes_props
, key
=lambda k
: k
[2]):
4121 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4122 props
.to_type
= ident
4125 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4126 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4127 bl_label
= "Texture"
4129 def draw(self
, context
):
4130 layout
= self
.layout
4131 for ident
, node_type
, rna_name
in sorted(shaders_texture_nodes_props
, key
=lambda k
: k
[2]):
4132 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4133 props
.to_type
= ident
4136 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4137 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4140 def draw(self
, context
):
4141 layout
= self
.layout
4142 for ident
, node_type
, rna_name
in sorted(shaders_color_nodes_props
, key
=lambda k
: k
[2]):
4143 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4144 props
.to_type
= ident
4147 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4148 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4151 def draw(self
, context
):
4152 layout
= self
.layout
4153 for ident
, node_type
, rna_name
in sorted(shaders_vector_nodes_props
, key
=lambda k
: k
[2]):
4154 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4155 props
.to_type
= ident
4158 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4159 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4160 bl_label
= "Converter"
4162 def draw(self
, context
):
4163 layout
= self
.layout
4164 for ident
, node_type
, rna_name
in sorted(shaders_converter_nodes_props
, key
=lambda k
: k
[2]):
4165 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4166 props
.to_type
= ident
4169 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4170 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4173 def draw(self
, context
):
4174 layout
= self
.layout
4175 for ident
, node_type
, rna_name
in sorted(shaders_layout_nodes_props
, key
=lambda k
: k
[2]):
4176 if node_type
!= 'FRAME':
4177 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4178 props
.to_type
= ident
4181 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4182 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4185 def draw(self
, context
):
4186 layout
= self
.layout
4187 for ident
, node_type
, rna_name
in sorted(compo_input_nodes_props
, key
=lambda k
: k
[2]):
4188 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4189 props
.to_type
= ident
4192 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4193 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4196 def draw(self
, context
):
4197 layout
= self
.layout
4198 for ident
, node_type
, rna_name
in sorted(compo_output_nodes_props
, key
=lambda k
: k
[2]):
4199 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4200 props
.to_type
= ident
4203 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4204 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4207 def draw(self
, context
):
4208 layout
= self
.layout
4209 for ident
, node_type
, rna_name
in sorted(compo_color_nodes_props
, key
=lambda k
: k
[2]):
4210 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4211 props
.to_type
= ident
4214 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4215 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4216 bl_label
= "Converter"
4218 def draw(self
, context
):
4219 layout
= self
.layout
4220 for ident
, node_type
, rna_name
in sorted(compo_converter_nodes_props
, key
=lambda k
: k
[2]):
4221 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4222 props
.to_type
= ident
4225 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4226 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4229 def draw(self
, context
):
4230 layout
= self
.layout
4231 for ident
, node_type
, rna_name
in sorted(compo_filter_nodes_props
, key
=lambda k
: k
[2]):
4232 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4233 props
.to_type
= ident
4236 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4237 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4240 def draw(self
, context
):
4241 layout
= self
.layout
4242 for ident
, node_type
, rna_name
in sorted(compo_vector_nodes_props
, key
=lambda k
: k
[2]):
4243 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4244 props
.to_type
= ident
4247 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4248 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4251 def draw(self
, context
):
4252 layout
= self
.layout
4253 for ident
, node_type
, rna_name
in sorted(compo_matte_nodes_props
, key
=lambda k
: k
[2]):
4254 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4255 props
.to_type
= ident
4258 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4259 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4260 bl_label
= "Distort"
4262 def draw(self
, context
):
4263 layout
= self
.layout
4264 for ident
, node_type
, rna_name
in sorted(compo_distort_nodes_props
, key
=lambda k
: k
[2]):
4265 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4266 props
.to_type
= ident
4269 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4270 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4273 def draw(self
, context
):
4274 layout
= self
.layout
4275 for ident
, node_type
, rna_name
in sorted(compo_layout_nodes_props
, key
=lambda k
: k
[2]):
4276 if node_type
!= 'FRAME':
4277 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4278 props
.to_type
= ident
4281 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4282 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4285 def draw(self
, context
):
4286 layout
= self
.layout
4287 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4288 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4289 props
.to_type
= ident
4292 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4293 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4296 def draw(self
, context
):
4297 layout
= self
.layout
4298 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4299 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4300 props
.to_type
= ident
4303 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4304 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4307 def draw(self
, context
):
4308 layout
= self
.layout
4309 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4310 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4311 props
.to_type
= ident
4314 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4315 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4318 def draw(self
, context
):
4319 layout
= self
.layout
4320 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4321 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4322 props
.to_type
= ident
4325 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4326 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4327 bl_label
= "Converter"
4329 def draw(self
, context
):
4330 layout
= self
.layout
4331 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4332 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4333 props
.to_type
= ident
4336 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4337 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4340 def draw(self
, context
):
4341 layout
= self
.layout
4342 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4343 if node_type
!= 'FRAME':
4344 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4345 props
.to_type
= ident
4348 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4349 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4352 def draw(self
, context
):
4353 layout
= self
.layout
4354 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4355 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4356 props
.to_type
= ident
4359 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4360 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4363 def draw(self
, context
):
4364 layout
= self
.layout
4365 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4366 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4367 props
.to_type
= ident
4370 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4371 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4374 def draw(self
, context
):
4375 layout
= self
.layout
4376 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4377 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4378 props
.to_type
= ident
4381 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4382 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4383 bl_label
= "Pattern"
4385 def draw(self
, context
):
4386 layout
= self
.layout
4387 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4388 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4389 props
.to_type
= ident
4392 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4393 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4394 bl_label
= "Textures"
4396 def draw(self
, context
):
4397 layout
= self
.layout
4398 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4399 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4400 props
.to_type
= ident
4403 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4404 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4405 bl_label
= "Converter"
4407 def draw(self
, context
):
4408 layout
= self
.layout
4409 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4410 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4411 props
.to_type
= ident
4414 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4415 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4416 bl_label
= "Distort"
4418 def draw(self
, context
):
4419 layout
= self
.layout
4420 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4421 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4422 props
.to_type
= ident
4425 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4426 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4429 def draw(self
, context
):
4430 layout
= self
.layout
4431 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4432 if node_type
!= 'FRAME':
4433 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4434 props
.to_type
= ident
4438 # APPENDAGES TO EXISTING UI
4442 def select_parent_children_buttons(self
, context
):
4443 layout
= self
.layout
4444 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4445 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4448 def attr_nodes_menu_func(self
, context
):
4449 col
= self
.layout
.column(align
=True)
4450 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4454 def multipleimages_menu_func(self
, context
):
4455 col
= self
.layout
.column(align
=True)
4456 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4457 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4461 def bgreset_menu_func(self
, context
):
4462 self
.layout
.operator(NWResetBG
.bl_idname
)
4465 def save_viewer_menu_func(self
, context
):
4466 if nw_check(context
):
4467 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4468 if context
.scene
.node_tree
.nodes
.active
:
4469 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4470 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4473 def reset_nodes_button(self
, context
):
4474 node_active
= context
.active_node
4475 node_selected
= context
.selected_nodes
4476 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4478 # Check if active node is in the selection and respective type
4479 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4480 row
= self
.layout
.row()
4481 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4482 self
.layout
.separator()
4484 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4485 row
= self
.layout
.row()
4486 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4487 self
.layout
.separator()
4491 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4495 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4496 # props entry: (property name, property value)
4499 # NWMergeNodes with Ctrl (AUTO).
4500 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4501 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4502 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4503 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4504 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4505 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4506 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4507 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4508 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4509 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4510 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4511 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4512 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4513 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4514 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4515 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4516 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4517 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4518 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4519 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4520 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4521 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4522 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4523 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4524 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4525 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4526 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4527 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4528 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4529 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4530 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4531 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4532 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4533 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4534 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4535 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4536 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4537 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4538 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4539 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4540 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4541 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4542 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4543 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4544 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4545 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4546 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4547 # NWMergeNodes with Ctrl Shift (MATH)
4548 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4549 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4550 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4551 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4552 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4553 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4554 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4555 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4556 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4557 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4558 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4559 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4560 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4561 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4562 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4563 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4564 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4565 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4566 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4567 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4568 # BATCH CHANGE NODES
4569 # NWBatchChangeNodes with Alt
4570 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4571 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4572 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4573 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4574 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4575 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4576 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4577 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4578 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4579 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4580 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4581 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4582 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4583 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4584 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4585 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4586 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4587 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4588 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4589 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4590 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4591 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4592 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4593 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4594 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4595 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4596 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4597 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4598 # LINK ACTIVE TO SELECTED
4599 # Don't use names, don't replace links (K)
4600 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4601 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4602 # Don't use names, replace links (Shift K)
4603 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4604 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4605 # Use node name, don't replace links (')
4606 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4607 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4608 # Use node name, replace links (Shift ')
4609 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4610 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4611 # Don't use names, don't replace links (;)
4612 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4613 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4614 # Don't use names, replace links (')
4615 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4616 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4618 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4619 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4620 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4621 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4622 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4623 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4624 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4625 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4626 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4627 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4628 # CLEAR LABEL (Alt L)
4629 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4630 # MODIFY LABEL (Alt Shift L)
4631 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4632 # Copy Label from active to selected
4633 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4634 # DETACH OUTPUTS (Alt Shift D)
4635 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4636 # LINK TO OUTPUT NODE (O)
4637 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4638 # SELECT PARENT/CHILDREN
4640 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4642 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4644 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4645 # Add Principled BSDF Texture Setup
4646 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4648 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4650 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4652 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4654 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4656 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4658 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4660 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
4662 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, False, False, None, "Lazy Connect"),
4663 # Lazy Connect with Menu
4664 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4665 # Viewer Tile Center
4666 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4668 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4669 # Reset Nodes (Back Space)
4670 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4672 ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wranger menu"),
4673 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4674 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4675 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4676 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4677 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4682 NWPrincipledPreferences
,
4702 NWAddPrincipledSetup
,
4704 NWLinkActiveToSelected
,
4706 NWSelectParentChildren
,
4712 NWAddMultipleImages
,
4721 NWConnectionListOutputs
,
4722 NWConnectionListInputs
,
4724 NWBatchChangeNodesMenu
,
4725 NWBatchChangeBlendTypeMenu
,
4726 NWBatchChangeOperationMenu
,
4727 NWCopyToSelectedMenu
,
4730 NWLinkActiveToSelectedMenu
,
4732 NWLinkUseNodeNameMenu
,
4733 NWLinkUseOutputsNamesMenu
,
4735 NWSwitchNodeTypeMenu
,
4736 NWSwitchShadersInputSubmenu
,
4737 NWSwitchShadersOutputSubmenu
,
4738 NWSwitchShadersShaderSubmenu
,
4739 NWSwitchShadersTextureSubmenu
,
4740 NWSwitchShadersColorSubmenu
,
4741 NWSwitchShadersVectorSubmenu
,
4742 NWSwitchShadersConverterSubmenu
,
4743 NWSwitchShadersLayoutSubmenu
,
4744 NWSwitchCompoInputSubmenu
,
4745 NWSwitchCompoOutputSubmenu
,
4746 NWSwitchCompoColorSubmenu
,
4747 NWSwitchCompoConverterSubmenu
,
4748 NWSwitchCompoFilterSubmenu
,
4749 NWSwitchCompoVectorSubmenu
,
4750 NWSwitchCompoMatteSubmenu
,
4751 NWSwitchCompoDistortSubmenu
,
4752 NWSwitchCompoLayoutSubmenu
,
4753 NWSwitchMatInputSubmenu
,
4754 NWSwitchMatOutputSubmenu
,
4755 NWSwitchMatColorSubmenu
,
4756 NWSwitchMatVectorSubmenu
,
4757 NWSwitchMatConverterSubmenu
,
4758 NWSwitchMatLayoutSubmenu
,
4759 NWSwitchTexInputSubmenu
,
4760 NWSwitchTexOutputSubmenu
,
4761 NWSwitchTexColorSubmenu
,
4762 NWSwitchTexPatternSubmenu
,
4763 NWSwitchTexTexturesSubmenu
,
4764 NWSwitchTexConverterSubmenu
,
4765 NWSwitchTexDistortSubmenu
,
4766 NWSwitchTexLayoutSubmenu
,
4770 from bpy
.utils
import register_class
4773 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4774 name
="Busy Drawing!",
4776 description
="An internal property used to store only the first mouse position")
4777 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4778 name
="Lazy Source!",
4780 description
="An internal property used to store the first node in a Lazy Connect operation")
4781 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
4782 name
="Lazy Target!",
4784 description
="An internal property used to store the last node in a Lazy Connect operation")
4785 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
4786 name
="Source Socket!",
4788 description
="An internal property used to store the source socket in a Lazy Connect operation")
4794 addon_keymaps
.clear()
4795 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
4797 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
4798 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
4799 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
4801 for prop
, value
in props
:
4802 setattr(kmi
.properties
, prop
, value
)
4803 addon_keymaps
.append((km
, kmi
))
4806 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
4807 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4808 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
4809 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
4810 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4811 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4812 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
4813 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
4817 from bpy
.utils
import unregister_class
4820 del bpy
.types
.Scene
.NWBusyDrawing
4821 del bpy
.types
.Scene
.NWLazySource
4822 del bpy
.types
.Scene
.NWLazyTarget
4823 del bpy
.types
.Scene
.NWSourceSocket
4826 for km
, kmi
in addon_keymaps
:
4827 km
.keymap_items
.remove(kmi
)
4828 addon_keymaps
.clear()
4831 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
4832 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4833 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
4834 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
4835 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4836 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4837 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
4838 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
4841 unregister_class(cls
)
4843 if __name__
== "__main__":