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 Shift-W",
25 "description": "Various tools to enhance and speed up node-based workflow",
27 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
28 "node/node_wrangler.html",
34 from bpy
.types
import Operator
, Panel
, Menu
35 from bpy
.props
import (
44 from bpy_extras
.io_utils
import ImportHelper
, ExportHelper
45 from gpu_extras
.batch
import batch_for_shader
46 from mathutils
import Vector
47 from math
import cos
, sin
, pi
, hypot
51 from itertools
import chain
53 from collections
import namedtuple
57 # list of outputs of Input Render Layer
58 # with attributes determinig if pass is used,
59 # and MultiLayer EXR outputs names and corresponding render engines
61 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_eevee, in_cycles)
62 RL_entry
= namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_eevee', 'in_cycles'])
64 RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
65 RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
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', False, True),
70 RL_entry('use_pass_environment', 'Environment', 'Env', False, 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', False, False),
75 RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', False, True),
76 RL_entry('use_pass_mist', 'Mist', 'Mist', True, True),
77 RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
78 RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', False, True),
79 RL_entry('use_pass_shadow', 'Shadow', 'Shadow', False, True),
80 RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', True, True),
81 RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', True, True),
82 RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
83 RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
84 RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
85 RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
86 RL_entry('use_pass_uv', 'UV', 'UV', True, True),
87 RL_entry('use_pass_vector', 'Speed', 'Vector', False, True),
88 RL_entry('use_pass_z', 'Z', 'Depth', True, True),
92 # (rna_type.identifier, type, rna_type.name)
93 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
94 shaders_input_nodes_props
= (
95 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
96 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
97 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
98 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
99 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
100 ('ShaderNodeRGB', 'RGB', 'RGB'),
101 ('ShaderNodeValue', 'VALUE', 'Value'),
102 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
103 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
104 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
105 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
106 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
107 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
108 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
109 ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
111 # (rna_type.identifier, type, rna_type.name)
112 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
113 shaders_output_nodes_props
= (
114 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
115 ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
116 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
118 # (rna_type.identifier, type, rna_type.name)
119 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
120 shaders_shader_nodes_props
= (
121 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
122 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
123 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
124 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
125 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
126 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
127 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
128 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
129 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
130 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
131 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
132 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
133 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
134 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
135 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
136 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
137 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
138 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
139 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
140 ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
142 # (rna_type.identifier, type, rna_type.name)
143 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
144 shaders_texture_nodes_props
= (
145 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
146 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
147 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
148 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
149 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
150 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
151 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
152 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
153 ('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
154 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
155 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
156 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
158 # (rna_type.identifier, type, rna_type.name)
159 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
160 shaders_color_nodes_props
= (
161 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
162 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
163 ('ShaderNodeInvert', 'INVERT', 'Invert'),
164 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
165 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
166 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
167 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
169 # (rna_type.identifier, type, rna_type.name)
170 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
171 shaders_vector_nodes_props
= (
172 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
173 ('ShaderNodeBump', 'BUMP', 'Bump'),
174 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
175 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
176 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
177 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
179 # (rna_type.identifier, type, rna_type.name)
180 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
181 shaders_converter_nodes_props
= (
182 ('ShaderNodeMath', 'MATH', 'Math'),
183 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
184 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
185 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
186 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
187 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
188 ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
189 ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
190 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
191 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
192 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
193 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
195 # (rna_type.identifier, type, rna_type.name)
196 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
197 shaders_layout_nodes_props
= (
198 ('NodeFrame', 'FRAME', 'Frame'),
199 ('NodeReroute', 'REROUTE', 'Reroute'),
203 # (rna_type.identifier, type, rna_type.name)
204 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
205 compo_input_nodes_props
= (
206 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
207 ('CompositorNodeImage', 'IMAGE', 'Image'),
208 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
209 ('CompositorNodeMask', 'MASK', 'Mask'),
210 ('CompositorNodeRGB', 'RGB', 'RGB'),
211 ('CompositorNodeValue', 'VALUE', 'Value'),
212 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
213 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
214 ('CompositorNodeTime', 'TIME', 'Time'),
215 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
217 # (rna_type.identifier, type, rna_type.name)
218 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
219 compo_output_nodes_props
= (
220 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
221 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
222 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
223 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
224 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
226 # (rna_type.identifier, type, rna_type.name)
227 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
228 compo_color_nodes_props
= (
229 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
230 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
231 ('CompositorNodeInvert', 'INVERT', 'Invert'),
232 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
233 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
234 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
235 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
236 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
237 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
238 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
239 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
240 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
242 # (rna_type.identifier, type, rna_type.name)
243 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
244 compo_converter_nodes_props
= (
245 ('CompositorNodeMath', 'MATH', 'Math'),
246 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
247 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
248 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
249 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
250 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
251 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
252 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
253 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
254 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
255 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
256 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
257 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
258 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
260 # (rna_type.identifier, type, rna_type.name)
261 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
262 compo_filter_nodes_props
= (
263 ('CompositorNodeBlur', 'BLUR', 'Blur'),
264 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
265 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
266 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
267 ('CompositorNodeFilter', 'FILTER', 'Filter'),
268 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
269 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
270 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
271 ('CompositorNodeGlare', 'GLARE', 'Glare'),
272 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
273 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
274 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
275 ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
277 # (rna_type.identifier, type, rna_type.name)
278 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
279 compo_vector_nodes_props
= (
280 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
281 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
282 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
283 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
284 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
286 # (rna_type.identifier, type, rna_type.name)
287 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
288 compo_matte_nodes_props
= (
289 ('CompositorNodeKeying', 'KEYING', 'Keying'),
290 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
291 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
292 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
293 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
294 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
295 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
296 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
297 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
298 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
299 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
300 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
302 # (rna_type.identifier, type, rna_type.name)
303 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
304 compo_distort_nodes_props
= (
305 ('CompositorNodeScale', 'SCALE', 'Scale'),
306 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
307 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
308 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
309 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
310 ('CompositorNodeFlip', 'FLIP', 'Flip'),
311 ('CompositorNodeCrop', 'CROP', 'Crop'),
312 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
313 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
314 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
315 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
316 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
317 ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
319 # (rna_type.identifier, type, rna_type.name)
320 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
321 compo_layout_nodes_props
= (
322 ('NodeFrame', 'FRAME', 'Frame'),
323 ('NodeReroute', 'REROUTE', 'Reroute'),
324 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
326 # Blender Render material nodes
327 # (rna_type.identifier, type, rna_type.name)
328 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
329 blender_mat_input_nodes_props
= (
330 ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
331 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
332 ('ShaderNodeLightData', 'LIGHT', 'Light Data'),
333 ('ShaderNodeValue', 'VALUE', 'Value'),
334 ('ShaderNodeRGB', 'RGB', 'RGB'),
335 ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
336 ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
337 ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
340 # (rna_type.identifier, type, rna_type.name)
341 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
342 blender_mat_output_nodes_props
= (
343 ('ShaderNodeOutput', 'OUTPUT', 'Output'),
346 # (rna_type.identifier, type, rna_type.name)
347 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
348 blender_mat_color_nodes_props
= (
349 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
350 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
351 ('ShaderNodeInvert', 'INVERT', 'Invert'),
352 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
355 # (rna_type.identifier, type, rna_type.name)
356 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
357 blender_mat_vector_nodes_props
= (
358 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
359 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
360 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
363 # (rna_type.identifier, type, rna_type.name)
364 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
365 blender_mat_converter_nodes_props
= (
366 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
367 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
368 ('ShaderNodeMath', 'MATH', 'Math'),
369 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
370 ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
371 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
372 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
373 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
374 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
377 # (rna_type.identifier, type, rna_type.name)
378 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
379 blender_mat_layout_nodes_props
= (
380 ('NodeReroute', 'REROUTE', 'Reroute'),
384 # (rna_type.identifier, type, rna_type.name)
385 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
386 texture_input_nodes_props
= (
387 ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
388 ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
389 ('TextureNodeTexture', 'TEXTURE', 'Texture'),
390 ('TextureNodeImage', 'IMAGE', 'Image'),
393 # (rna_type.identifier, type, rna_type.name)
394 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
395 texture_output_nodes_props
= (
396 ('TextureNodeOutput', 'OUTPUT', 'Output'),
397 ('TextureNodeViewer', 'VIEWER', 'Viewer'),
400 # (rna_type.identifier, type, rna_type.name)
401 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
402 texture_color_nodes_props
= (
403 ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
404 ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
405 ('TextureNodeInvert', 'INVERT', 'Invert'),
406 ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
407 ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
408 ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
411 # (rna_type.identifier, type, rna_type.name)
412 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
413 texture_pattern_nodes_props
= (
414 ('TextureNodeChecker', 'CHECKER', 'Checker'),
415 ('TextureNodeBricks', 'BRICKS', 'Bricks'),
418 # (rna_type.identifier, type, rna_type.name)
419 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
420 texture_textures_nodes_props
= (
421 ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
422 ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
423 ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
424 ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
425 ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
426 ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
427 ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
428 ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
429 ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
430 ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
433 # (rna_type.identifier, type, rna_type.name)
434 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
435 texture_converter_nodes_props
= (
436 ('TextureNodeMath', 'MATH', 'Math'),
437 ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
438 ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
439 ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
440 ('TextureNodeDistance', 'DISTANCE', 'Distance'),
443 # (rna_type.identifier, type, rna_type.name)
444 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
445 texture_distort_nodes_props
= (
446 ('TextureNodeScale', 'SCALE', 'Scale'),
447 ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
448 ('TextureNodeRotate', 'ROTATE', 'Rotate'),
449 ('TextureNodeAt', 'AT', 'At'),
452 # (rna_type.identifier, type, rna_type.name)
453 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
454 texture_layout_nodes_props
= (
455 ('NodeReroute', 'REROUTE', 'Reroute'),
458 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
459 # used list, not tuple for easy merging with other lists.
461 ('MIX', 'Mix', 'Mix Mode'),
462 ('ADD', 'Add', 'Add Mode'),
463 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
464 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
465 ('SCREEN', 'Screen', 'Screen Mode'),
466 ('DIVIDE', 'Divide', 'Divide Mode'),
467 ('DIFFERENCE', 'Difference', 'Difference Mode'),
468 ('DARKEN', 'Darken', 'Darken Mode'),
469 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
470 ('OVERLAY', 'Overlay', 'Overlay Mode'),
471 ('DODGE', 'Dodge', 'Dodge Mode'),
472 ('BURN', 'Burn', 'Burn Mode'),
473 ('HUE', 'Hue', 'Hue Mode'),
474 ('SATURATION', 'Saturation', 'Saturation Mode'),
475 ('VALUE', 'Value', 'Value Mode'),
476 ('COLOR', 'Color', 'Color Mode'),
477 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
478 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
481 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
482 # used list, not tuple for easy merging with other lists.
484 ('ADD', 'Add', 'Add Mode'),
485 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
486 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
487 ('DIVIDE', 'Divide', 'Divide Mode'),
488 ('SINE', 'Sine', 'Sine Mode'),
489 ('COSINE', 'Cosine', 'Cosine Mode'),
490 ('TANGENT', 'Tangent', 'Tangent Mode'),
491 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
492 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
493 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
494 ('POWER', 'Power', 'Power Mode'),
495 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
496 ('MINIMUM', 'Minimum', 'Minimum Mode'),
497 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
498 ('ROUND', 'Round', 'Round Mode'),
499 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
500 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
501 ('MODULO', 'Modulo', 'Modulo Mode'),
502 ('ABSOLUTE', 'Absolute', 'Absolute Mode'),
505 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
506 # used list, not tuple for easy merging with other lists.
508 ('CURRENT', 'Current', 'Leave at current state'),
509 ('NEXT', 'Next', 'Next blend type/operation'),
510 ('PREV', 'Prev', 'Previous blend type/operation'),
515 (1.0, 1.0, 1.0, 0.7),
516 (1.0, 0.0, 0.0, 0.7),
520 (0.0, 0.0, 0.0, 1.0),
521 (0.38, 0.77, 0.38, 1.0),
522 (0.38, 0.77, 0.38, 1.0)
525 (0.0, 0.0, 0.0, 1.0),
526 (0.77, 0.77, 0.16, 1.0),
527 (0.77, 0.77, 0.16, 1.0)
530 (0.0, 0.0, 0.0, 1.0),
531 (0.38, 0.38, 0.77, 1.0),
532 (0.38, 0.38, 0.77, 1.0)
535 (0.0, 0.0, 0.0, 1.0),
536 (0.63, 0.63, 0.63, 1.0),
537 (0.63, 0.63, 0.63, 1.0)
540 (1.0, 1.0, 1.0, 0.7),
541 (0.0, 0.0, 0.0, 0.7),
547 def is_cycles_or_eevee(context
):
548 return context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}
550 def is_visible_socket(socket
):
551 return not socket
.hide
and socket
.enabled
553 def nice_hotkey_name(punc
):
554 # convert the ugly string name into the actual character
556 ('LEFTMOUSE', "LMB"),
557 ('MIDDLEMOUSE', "MMB"),
558 ('RIGHTMOUSE', "RMB"),
559 ('WHEELUPMOUSE', "Wheel Up"),
560 ('WHEELDOWNMOUSE', "Wheel Down"),
561 ('WHEELINMOUSE', "Wheel In"),
562 ('WHEELOUTMOUSE', "Wheel Out"),
575 ('LINE_FEED', "Enter"),
582 ('BACK_SLASH', "\\"),
584 ('NUMPAD_1', "Numpad 1"),
585 ('NUMPAD_2', "Numpad 2"),
586 ('NUMPAD_3', "Numpad 3"),
587 ('NUMPAD_4', "Numpad 4"),
588 ('NUMPAD_5', "Numpad 5"),
589 ('NUMPAD_6', "Numpad 6"),
590 ('NUMPAD_7', "Numpad 7"),
591 ('NUMPAD_8', "Numpad 8"),
592 ('NUMPAD_9', "Numpad 9"),
593 ('NUMPAD_0', "Numpad 0"),
594 ('NUMPAD_PERIOD', "Numpad ."),
595 ('NUMPAD_SLASH', "Numpad /"),
596 ('NUMPAD_ASTERIX', "Numpad *"),
597 ('NUMPAD_MINUS', "Numpad -"),
598 ('NUMPAD_ENTER', "Numpad Enter"),
599 ('NUMPAD_PLUS', "Numpad +"),
602 for (ugly
, nice
) in pairs
:
607 nice_punc
= punc
.replace("_", " ").title()
611 def force_update(context
):
612 context
.space_data
.node_tree
.update_tag()
616 prefs
= bpy
.context
.preferences
.system
617 return prefs
.dpi
* prefs
.pixel_size
/ 72
620 def node_mid_pt(node
, axis
):
622 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
624 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
630 def autolink(node1
, node2
, links
):
633 for outp
in node1
.outputs
:
634 for inp
in node2
.inputs
:
635 if not inp
.is_linked
and inp
.name
== outp
.name
:
640 for outp
in node1
.outputs
:
641 for inp
in node2
.inputs
:
642 if not inp
.is_linked
and inp
.type == outp
.type:
647 # force some connection even if the type doesn't match
648 for outp
in node1
.outputs
:
649 for inp
in node2
.inputs
:
650 if not inp
.is_linked
:
655 # even if no sockets are open, force one of matching type
656 for outp
in node1
.outputs
:
657 for inp
in node2
.inputs
:
658 if inp
.type == outp
.type:
664 for outp
in node1
.outputs
:
665 for inp
in node2
.inputs
:
670 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
674 def node_at_pos(nodes
, context
, event
):
675 nodes_near_mouse
= []
676 nodes_under_mouse
= []
679 store_mouse_cursor(context
, event
)
680 x
, y
= context
.space_data
.cursor_location
684 # Make a list of each corner (and middle of border) for each node.
685 # Will be sorted to find nearest point and thus nearest node
686 node_points_with_dist
= []
689 if node
.type != 'FRAME': # no point trying to link to a frame node
690 locx
= node
.location
.x
691 locy
= node
.location
.y
692 dimx
= node
.dimensions
.x
/dpifac()
693 dimy
= node
.dimensions
.y
/dpifac()
695 locx
+= node
.parent
.location
.x
696 locy
+= node
.parent
.location
.y
697 if node
.parent
.parent
:
698 locx
+= node
.parent
.parent
.location
.x
699 locy
+= node
.parent
.parent
.location
.y
700 if node
.parent
.parent
.parent
:
701 locx
+= node
.parent
.parent
.parent
.location
.x
702 locy
+= node
.parent
.parent
.parent
.location
.y
703 if node
.parent
.parent
.parent
.parent
:
704 # Support three levels or parenting
705 # There's got to be a better way to do this...
708 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- locy
)]) # Top Left
709 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- locy
)]) # Top Right
710 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- dimy
))]) # Bottom Left
711 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- dimy
))]) # Bottom Right
713 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- locy
)]) # Mid Top
714 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- (locy
- dimy
))]) # Mid Bottom
715 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- (dimy
/ 2)))]) # Mid Left
716 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- (dimy
/ 2)))]) # Mid Right
718 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
721 if node
.type != 'FRAME' and skipnode
== False:
722 locx
= node
.location
.x
723 locy
= node
.location
.y
724 dimx
= node
.dimensions
.x
/dpifac()
725 dimy
= node
.dimensions
.y
/dpifac()
727 locx
+= node
.parent
.location
.x
728 locy
+= node
.parent
.location
.y
729 if (locx
<= x
<= locx
+ dimx
) and \
730 (locy
- dimy
<= y
<= locy
):
731 nodes_under_mouse
.append(node
)
733 if len(nodes_under_mouse
) == 1:
734 if nodes_under_mouse
[0] != nearest_node
:
735 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
737 target_node
= nearest_node
# else use the nearest node
739 target_node
= nearest_node
743 def store_mouse_cursor(context
, event
):
744 space
= context
.space_data
745 v2d
= context
.region
.view2d
746 tree
= space
.edit_tree
748 # convert mouse position to the View2D for later node placement
749 if context
.region
.type == 'WINDOW':
750 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
752 space
.cursor_location
= tree
.view_center
754 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=(1.0, 1.0, 1.0, 0.7)):
755 shader
= gpu
.shader
.from_builtin('2D_SMOOTH_COLOR')
757 vertices
= ((x1
, y1
), (x2
, y2
))
758 vertex_colors
= ((colour
[0]+(1.0-colour
[0])/4,
759 colour
[1]+(1.0-colour
[1])/4,
760 colour
[2]+(1.0-colour
[2])/4,
761 colour
[3]+(1.0-colour
[3])/4),
764 batch
= batch_for_shader(shader
, 'LINE_STRIP', {"pos": vertices
, "color": vertex_colors
})
765 bgl
.glLineWidth(size
* dpifac())
771 def draw_circle_2d_filled(shader
, mx
, my
, radius
, colour
=(1.0, 1.0, 1.0, 0.7)):
772 radius
= radius
* dpifac()
774 vertices
= [(radius
* cos(i
* 2 * pi
/ sides
) + mx
,
775 radius
* sin(i
* 2 * pi
/ sides
) + my
)
776 for i
in range(sides
+ 1)]
778 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
780 shader
.uniform_float("color", colour
)
783 def draw_rounded_node_border(shader
, node
, radius
=8, colour
=(1.0, 1.0, 1.0, 0.7)):
784 area_width
= bpy
.context
.area
.width
- (16*dpifac()) - 1
785 bottom_bar
= (16*dpifac()) + 1
787 radius
= radius
*dpifac()
789 nlocx
= (node
.location
.x
+1)*dpifac()
790 nlocy
= (node
.location
.y
+1)*dpifac()
791 ndimx
= node
.dimensions
.x
792 ndimy
= node
.dimensions
.y
793 # This is a stupid way to do this... TODO use while loop
795 nlocx
+= node
.parent
.location
.x
796 nlocy
+= node
.parent
.location
.y
797 if node
.parent
.parent
:
798 nlocx
+= node
.parent
.parent
.location
.x
799 nlocy
+= node
.parent
.parent
.location
.y
800 if node
.parent
.parent
.parent
:
801 nlocx
+= node
.parent
.parent
.parent
.location
.x
802 nlocy
+= node
.parent
.parent
.parent
.location
.y
807 if node
.type == 'REROUTE':
815 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
817 for i
in range(sides
+1):
819 if my
> bottom_bar
and mx
< area_width
:
820 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
821 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
822 vertices
.append((cosine
,sine
))
823 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
825 shader
.uniform_float("color", colour
)
829 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
831 for i
in range(sides
+1):
833 if my
> bottom_bar
and mx
< area_width
:
834 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
835 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
836 vertices
.append((cosine
,sine
))
837 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
839 shader
.uniform_float("color", colour
)
843 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
845 for i
in range(sides
+1):
847 if my
> bottom_bar
and mx
< area_width
:
848 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
849 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
850 vertices
.append((cosine
,sine
))
851 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
853 shader
.uniform_float("color", colour
)
856 # Bottom right corner
857 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
859 for i
in range(sides
+1):
861 if my
> bottom_bar
and mx
< area_width
:
862 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
863 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
864 vertices
.append((cosine
,sine
))
865 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
867 shader
.uniform_float("color", colour
)
870 # prepare drawing all edges in one batch
876 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
877 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
878 if m1x
< area_width
and m2x
< area_width
:
879 vertices
.extend([(m2x
-radius
,m2y
), (m2x
,m2y
),
880 (m1x
,m1y
), (m1x
-radius
,m1y
)])
881 indices
.extend([(id_last
, id_last
+1, id_last
+3),
882 (id_last
+3, id_last
+1, id_last
+2)])
886 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
887 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
888 m1x
= min(m1x
, area_width
)
889 m2x
= min(m2x
, area_width
)
890 if m1y
> bottom_bar
and m2y
> bottom_bar
:
891 vertices
.extend([(m1x
,m1y
), (m2x
,m1y
),
892 (m2x
,m1y
+radius
), (m1x
,m1y
+radius
)])
893 indices
.extend([(id_last
, id_last
+1, id_last
+3),
894 (id_last
+3, id_last
+1, id_last
+2)])
898 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
899 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
900 m1y
= max(m1y
, bottom_bar
)
901 m2y
= max(m2y
, bottom_bar
)
902 if m1x
< area_width
and m2x
< area_width
:
903 vertices
.extend([(m1x
,m2y
), (m1x
+radius
,m2y
),
904 (m1x
+radius
,m1y
), (m1x
,m1y
)])
905 indices
.extend([(id_last
, id_last
+1, id_last
+3),
906 (id_last
+3, id_last
+1, id_last
+2)])
910 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
, clip
=False)
911 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
, clip
=False)
912 m1x
= min(m1x
, area_width
)
913 m2x
= min(m2x
, area_width
)
914 if m1y
> bottom_bar
and m2y
> bottom_bar
:
915 vertices
.extend([(m1x
,m2y
), (m2x
,m2y
),
916 (m2x
,m1y
-radius
), (m1x
,m1y
-radius
)])
917 indices
.extend([(id_last
, id_last
+1, id_last
+3),
918 (id_last
+3, id_last
+1, id_last
+2)])
920 # now draw all edges in one batch
921 if len(vertices
) != 0:
922 batch
= batch_for_shader(shader
, 'TRIS', {"pos": vertices
}, indices
=indices
)
924 shader
.uniform_float("color", colour
)
927 def draw_callback_nodeoutline(self
, context
, mode
):
931 bgl
.glEnable(bgl
.GL_BLEND
)
932 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
933 bgl
.glHint(bgl
.GL_LINE_SMOOTH_HINT
, bgl
.GL_NICEST
)
935 nodes
, links
= get_nodes_links(context
)
937 shader
= gpu
.shader
.from_builtin('2D_UNIFORM_COLOR')
940 col_outer
= (1.0, 0.2, 0.2, 0.4)
941 col_inner
= (0.0, 0.0, 0.0, 0.5)
942 col_circle_inner
= (0.3, 0.05, 0.05, 1.0)
943 elif mode
== "LINKMENU":
944 col_outer
= (0.4, 0.6, 1.0, 0.4)
945 col_inner
= (0.0, 0.0, 0.0, 0.5)
946 col_circle_inner
= (0.08, 0.15, .3, 1.0)
948 col_outer
= (0.2, 1.0, 0.2, 0.4)
949 col_inner
= (0.0, 0.0, 0.0, 0.5)
950 col_circle_inner
= (0.05, 0.3, 0.05, 1.0)
952 m1x
= self
.mouse_path
[0][0]
953 m1y
= self
.mouse_path
[0][1]
954 m2x
= self
.mouse_path
[-1][0]
955 m2y
= self
.mouse_path
[-1][1]
957 n1
= nodes
[context
.scene
.NWLazySource
]
958 n2
= nodes
[context
.scene
.NWLazyTarget
]
961 col_outer
= (0.4, 0.4, 0.4, 0.4)
962 col_inner
= (0.0, 0.0, 0.0, 0.5)
963 col_circle_inner
= (0.2, 0.2, 0.2, 1.0)
965 draw_rounded_node_border(shader
, n1
, radius
=6, colour
=col_outer
) # outline
966 draw_rounded_node_border(shader
, n1
, radius
=5, colour
=col_inner
) # inner
967 draw_rounded_node_border(shader
, n2
, radius
=6, colour
=col_outer
) # outline
968 draw_rounded_node_border(shader
, n2
, radius
=5, colour
=col_inner
) # inner
970 draw_line(m1x
, m1y
, m2x
, m2y
, 5, col_outer
) # line outline
971 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
974 draw_circle_2d_filled(shader
, m1x
, m1y
, 7, col_outer
)
975 draw_circle_2d_filled(shader
, m2x
, m2y
, 7, col_outer
)
978 draw_circle_2d_filled(shader
, m1x
, m1y
, 5, col_circle_inner
)
979 draw_circle_2d_filled(shader
, m2x
, m2y
, 5, col_circle_inner
)
981 bgl
.glDisable(bgl
.GL_BLEND
)
982 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
984 def get_nodes_links(context
):
985 tree
= context
.space_data
.node_tree
987 # Get nodes from currently edited tree.
988 # If user is editing a group, space_data.node_tree is still the base level (outside group).
989 # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
990 # the same as context.active_node, the user is in a group.
991 # Check recursively until we find the real active node_tree:
992 if tree
.nodes
.active
:
993 while tree
.nodes
.active
!= context
.active_node
:
994 tree
= tree
.nodes
.active
.node_tree
996 return tree
.nodes
, tree
.links
999 class NWPrincipledPreferences(bpy
.types
.PropertyGroup
):
1000 base_color
: StringProperty(
1002 default
='diffuse diff albedo base col color',
1003 description
='Naming Components for Base Color maps')
1004 sss_color
: StringProperty(
1005 name
='Subsurface Color',
1006 default
='sss subsurface',
1007 description
='Naming Components for Subsurface Color maps')
1008 metallic
: StringProperty(
1010 default
='metallic metalness metal mtl',
1011 description
='Naming Components for metallness maps')
1012 specular
: StringProperty(
1014 default
='specularity specular spec spc',
1015 description
='Naming Components for Specular maps')
1016 normal
: StringProperty(
1018 default
='normal nor nrm nrml norm',
1019 description
='Naming Components for Normal maps')
1020 bump
: StringProperty(
1023 description
='Naming Components for bump maps')
1024 rough
: StringProperty(
1026 default
='roughness rough rgh',
1027 description
='Naming Components for roughness maps')
1028 gloss
: StringProperty(
1030 default
='gloss glossy glossiness',
1031 description
='Naming Components for glossy maps')
1032 displacement
: StringProperty(
1033 name
='Displacement',
1034 default
='displacement displace disp dsp height heightmap',
1035 description
='Naming Components for displacement maps')
1038 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1039 bl_idname
= __name__
1041 merge_hide
: EnumProperty(
1042 name
="Hide Mix nodes",
1044 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1045 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1046 ("NEVER", "Never", "Never collapse the new merge nodes")
1048 default
='NON_SHADER',
1049 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded")
1050 merge_position
: EnumProperty(
1051 name
="Mix Node Position",
1053 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1054 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1057 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
1059 show_hotkey_list
: BoolProperty(
1060 name
="Show Hotkey List",
1062 description
="Expand this box into a list of all the hotkeys for functions in this addon"
1064 hotkey_list_filter
: StringProperty(
1065 name
=" Filter by Name",
1067 description
="Show only hotkeys that have this text in their name"
1069 show_principled_lists
: BoolProperty(
1070 name
="Show Principled naming tags",
1072 description
="Expand this box into a list of all naming tags for principled texture setup"
1074 principled_tags
: bpy
.props
.PointerProperty(type=NWPrincipledPreferences
)
1076 def draw(self
, context
):
1077 layout
= self
.layout
1078 col
= layout
.column()
1079 col
.prop(self
, "merge_position")
1080 col
.prop(self
, "merge_hide")
1083 col
= box
.column(align
=True)
1084 col
.prop(self
, "show_principled_lists", text
='Edit tags for auto texture detection in Principled BSDF setup', toggle
=True)
1085 if self
.show_principled_lists
:
1086 tags
= self
.principled_tags
1088 col
.prop(tags
, "base_color")
1089 col
.prop(tags
, "sss_color")
1090 col
.prop(tags
, "metallic")
1091 col
.prop(tags
, "specular")
1092 col
.prop(tags
, "rough")
1093 col
.prop(tags
, "gloss")
1094 col
.prop(tags
, "normal")
1095 col
.prop(tags
, "bump")
1096 col
.prop(tags
, "displacement")
1099 col
= box
.column(align
=True)
1100 hotkey_button_name
= "Show Hotkey List"
1101 if self
.show_hotkey_list
:
1102 hotkey_button_name
= "Hide Hotkey List"
1103 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
1104 if self
.show_hotkey_list
:
1105 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
1107 for hotkey
in kmi_defs
:
1109 hotkey_name
= hotkey
[7]
1111 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
1112 row
= col
.row(align
=True)
1113 row
.label(text
=hotkey_name
)
1114 keystr
= nice_hotkey_name(hotkey
[1])
1116 keystr
= "Shift " + keystr
1118 keystr
= "Alt " + keystr
1120 keystr
= "Ctrl " + keystr
1121 row
.label(text
=keystr
)
1125 def nw_check(context
):
1126 space
= context
.space_data
1127 valid_trees
= ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
1130 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and space
.tree_type
in valid_trees
:
1137 def poll(cls
, context
):
1138 return nw_check(context
)
1142 class NWLazyMix(Operator
, NWBase
):
1143 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1144 bl_idname
= "node.nw_lazy_mix"
1145 bl_label
= "Mix Nodes"
1146 bl_options
= {'REGISTER', 'UNDO'}
1148 def modal(self
, context
, event
):
1149 context
.area
.tag_redraw()
1150 nodes
, links
= get_nodes_links(context
)
1153 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1156 if not context
.scene
.NWBusyDrawing
:
1157 node1
= node_at_pos(nodes
, context
, event
)
1159 context
.scene
.NWBusyDrawing
= node1
.name
1161 if context
.scene
.NWBusyDrawing
!= 'STOP':
1162 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1164 context
.scene
.NWLazySource
= node1
.name
1165 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1167 if event
.type == 'MOUSEMOVE':
1168 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1170 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1171 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1172 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1175 node2
= node_at_pos(nodes
, context
, event
)
1177 context
.scene
.NWBusyDrawing
= node2
.name
1189 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1191 context
.scene
.NWBusyDrawing
= ""
1194 elif event
.type == 'ESC':
1196 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1197 return {'CANCELLED'}
1199 return {'RUNNING_MODAL'}
1201 def invoke(self
, context
, event
):
1202 if context
.area
.type == 'NODE_EDITOR':
1203 # the arguments we pass the the callback
1204 args
= (self
, context
, 'MIX')
1205 # Add the region OpenGL drawing callback
1206 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1207 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1209 self
.mouse_path
= []
1211 context
.window_manager
.modal_handler_add(self
)
1212 return {'RUNNING_MODAL'}
1214 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1215 return {'CANCELLED'}
1218 class NWLazyConnect(Operator
, NWBase
):
1219 """Connect two nodes without clicking a specific socket (automatically determined"""
1220 bl_idname
= "node.nw_lazy_connect"
1221 bl_label
= "Lazy Connect"
1222 bl_options
= {'REGISTER', 'UNDO'}
1223 with_menu
: BoolProperty()
1225 def modal(self
, context
, event
):
1226 context
.area
.tag_redraw()
1227 nodes
, links
= get_nodes_links(context
)
1230 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1233 if not context
.scene
.NWBusyDrawing
:
1234 node1
= node_at_pos(nodes
, context
, event
)
1236 context
.scene
.NWBusyDrawing
= node1
.name
1238 if context
.scene
.NWBusyDrawing
!= 'STOP':
1239 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1241 context
.scene
.NWLazySource
= node1
.name
1242 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1244 if event
.type == 'MOUSEMOVE':
1245 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1247 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1248 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1249 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1252 node2
= node_at_pos(nodes
, context
, event
)
1254 context
.scene
.NWBusyDrawing
= node2
.name
1259 link_success
= False
1265 if node
.select
== True:
1267 original_sel
.append(node
)
1269 original_unsel
.append(node
)
1273 #link_success = autolink(node1, node2, links)
1275 if len(node1
.outputs
) > 1 and node2
.inputs
:
1276 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1277 elif len(node1
.outputs
) == 1:
1278 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1280 link_success
= autolink(node1
, node2
, links
)
1282 for node
in original_sel
:
1284 for node
in original_unsel
:
1288 force_update(context
)
1289 context
.scene
.NWBusyDrawing
= ""
1292 elif event
.type == 'ESC':
1293 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1294 return {'CANCELLED'}
1296 return {'RUNNING_MODAL'}
1298 def invoke(self
, context
, event
):
1299 if context
.area
.type == 'NODE_EDITOR':
1300 nodes
, links
= get_nodes_links(context
)
1301 node
= node_at_pos(nodes
, context
, event
)
1303 context
.scene
.NWBusyDrawing
= node
.name
1305 # the arguments we pass the the callback
1309 args
= (self
, context
, mode
)
1310 # Add the region OpenGL drawing callback
1311 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1312 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1314 self
.mouse_path
= []
1316 context
.window_manager
.modal_handler_add(self
)
1317 return {'RUNNING_MODAL'}
1319 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1320 return {'CANCELLED'}
1323 class NWDeleteUnused(Operator
, NWBase
):
1324 """Delete all nodes whose output is not used"""
1325 bl_idname
= 'node.nw_del_unused'
1326 bl_label
= 'Delete Unused Nodes'
1327 bl_options
= {'REGISTER', 'UNDO'}
1329 delete_muted
: BoolProperty(name
="Delete Muted", description
="Delete (but reconnect, like Ctrl-X) all muted nodes", default
=True)
1330 delete_frames
: BoolProperty(name
="Delete Empty Frames", description
="Delete all frames that have no nodes inside them", default
=True)
1332 def is_unused_node(self
, node
):
1333 end_types
= ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1334 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
1335 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
1336 if node
.type in end_types
:
1339 for output
in node
.outputs
:
1345 def poll(cls
, context
):
1347 if nw_check(context
):
1348 if context
.space_data
.node_tree
.nodes
:
1352 def execute(self
, context
):
1353 nodes
, links
= get_nodes_links(context
)
1358 if node
.select
== True:
1359 selection
.append(node
.name
)
1365 temp_deleted_nodes
= []
1366 del_unused_iterations
= len(nodes
)
1367 for it
in range(0, del_unused_iterations
):
1368 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1370 if self
.is_unused_node(node
):
1372 deleted_nodes
.append(node
.name
)
1373 bpy
.ops
.node
.delete()
1375 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1378 if self
.delete_frames
:
1386 frames_in_use
.append(node
.parent
)
1388 if node
.type == 'FRAME' and node
not in frames_in_use
:
1391 repeat
= True # repeat for nested frames
1393 if node
not in frames_in_use
:
1395 deleted_nodes
.append(node
.name
)
1396 bpy
.ops
.node
.delete()
1398 if self
.delete_muted
:
1402 deleted_nodes
.append(node
.name
)
1403 bpy
.ops
.node
.delete_reconnect()
1405 # get unique list of deleted nodes (iterations would count the same node more than once)
1406 deleted_nodes
= list(set(deleted_nodes
))
1407 for n
in deleted_nodes
:
1408 self
.report({'INFO'}, "Node " + n
+ " deleted")
1409 num_deleted
= len(deleted_nodes
)
1414 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1416 self
.report({'INFO'}, "Nothing deleted")
1419 nodes
, links
= get_nodes_links(context
)
1421 if node
.name
in selection
:
1425 def invoke(self
, context
, event
):
1426 return context
.window_manager
.invoke_confirm(self
, event
)
1429 class NWSwapLinks(Operator
, NWBase
):
1430 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1431 bl_idname
= 'node.nw_swap_links'
1432 bl_label
= 'Swap Links'
1433 bl_options
= {'REGISTER', 'UNDO'}
1436 def poll(cls
, context
):
1438 if nw_check(context
):
1439 if context
.selected_nodes
:
1440 valid
= len(context
.selected_nodes
) <= 2
1443 def execute(self
, context
):
1444 nodes
, links
= get_nodes_links(context
)
1445 selected_nodes
= context
.selected_nodes
1446 n1
= selected_nodes
[0]
1449 if len(selected_nodes
) == 2:
1450 n2
= selected_nodes
[1]
1451 if n1
.outputs
and n2
.outputs
:
1456 for output
in n1
.outputs
:
1458 for link
in output
.links
:
1459 n1_outputs
.append([out_index
, link
.to_socket
])
1464 for output
in n2
.outputs
:
1466 for link
in output
.links
:
1467 n2_outputs
.append([out_index
, link
.to_socket
])
1471 for connection
in n1_outputs
:
1473 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1475 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1476 for connection
in n2_outputs
:
1478 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1480 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1482 if n1
.outputs
or n2
.outputs
:
1483 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1485 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1488 elif len(selected_nodes
) == 1:
1492 for i1
in n1
.inputs
:
1495 for i2
in n1
.inputs
:
1496 if i1
.type == i2
.type and i2
.is_linked
:
1498 types
.append ([i1
, similar_types
, i
])
1500 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1505 for i2
in n1
.inputs
:
1506 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1508 i1f
= pair
[0].links
[0].from_socket
1509 i1t
= pair
[0].links
[0].to_socket
1510 i2f
= pair
[1].links
[0].from_socket
1511 i2t
= pair
[1].links
[0].to_socket
1516 fs
= t
[0].links
[0].from_socket
1518 links
.remove(t
[0].links
[0])
1519 if i
+1 == len(n1
.inputs
):
1522 while n1
.inputs
[i
].is_linked
:
1524 links
.new(fs
, n1
.inputs
[i
])
1525 elif len(types
) == 2:
1526 i1f
= types
[0][0].links
[0].from_socket
1527 i1t
= types
[0][0].links
[0].to_socket
1528 i2f
= types
[1][0].links
[0].from_socket
1529 i2t
= types
[1][0].links
[0].to_socket
1534 self
.report({'WARNING'}, "This node has no input connections to swap!")
1536 self
.report({'WARNING'}, "This node has no inputs to swap!")
1538 force_update(context
)
1542 class NWResetBG(Operator
, NWBase
):
1543 """Reset the zoom and position of the background image"""
1544 bl_idname
= 'node.nw_bg_reset'
1545 bl_label
= 'Reset Backdrop'
1546 bl_options
= {'REGISTER', 'UNDO'}
1549 def poll(cls
, context
):
1551 if nw_check(context
):
1552 snode
= context
.space_data
1553 valid
= snode
.tree_type
== 'CompositorNodeTree'
1556 def execute(self
, context
):
1557 context
.space_data
.backdrop_zoom
= 1
1558 context
.space_data
.backdrop_offset
[0] = 0
1559 context
.space_data
.backdrop_offset
[1] = 0
1563 class NWAddAttrNode(Operator
, NWBase
):
1564 """Add an Attribute node with this name"""
1565 bl_idname
= 'node.nw_add_attr_node'
1566 bl_label
= 'Add UV map'
1567 bl_options
= {'REGISTER', 'UNDO'}
1569 attr_name
: StringProperty()
1571 def execute(self
, context
):
1572 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1573 nodes
, links
= get_nodes_links(context
)
1574 nodes
.active
.attribute_name
= self
.attr_name
1578 class NWEmissionViewer(Operator
, NWBase
):
1579 bl_idname
= "node.nw_emission_viewer"
1580 bl_label
= "Emission Viewer"
1581 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1582 bl_options
= {'REGISTER', 'UNDO'}
1585 def poll(cls
, context
):
1586 is_cycles
= is_cycles_or_eevee(context
)
1587 if nw_check(context
):
1588 space
= context
.space_data
1589 if space
.tree_type
== 'ShaderNodeTree' and is_cycles
:
1590 if context
.active_node
:
1591 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1597 def invoke(self
, context
, event
):
1598 space
= context
.space_data
1599 shader_type
= space
.shader_type
1600 if shader_type
== 'OBJECT':
1601 if space
.id not in [light
for light
in bpy
.data
.lights
]: # cannot use bpy.data.lights directly as iterable
1602 shader_output_type
= "OUTPUT_MATERIAL"
1603 shader_output_ident
= "ShaderNodeOutputMaterial"
1604 shader_viewer_ident
= "ShaderNodeEmission"
1606 shader_output_type
= "OUTPUT_LIGHT"
1607 shader_output_ident
= "ShaderNodeOutputLight"
1608 shader_viewer_ident
= "ShaderNodeEmission"
1610 elif shader_type
== 'WORLD':
1611 shader_output_type
= "OUTPUT_WORLD"
1612 shader_output_ident
= "ShaderNodeOutputWorld"
1613 shader_viewer_ident
= "ShaderNodeBackground"
1614 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1615 mlocx
= event
.mouse_region_x
1616 mlocy
= event
.mouse_region_y
1617 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1618 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1619 nodes
, links
= get_nodes_links(context
)
1620 in_group
= context
.active_node
!= space
.node_tree
.nodes
.active
1621 active
= nodes
.active
1622 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1625 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1626 for out
in active
.outputs
:
1627 if is_visible_socket(out
):
1631 # get material_output node, store selection, deselect all
1632 materialout
= None # placeholder node
1635 if node
.type == shader_output_type
:
1638 selection
.append(node
.name
)
1641 # get right-most location
1642 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1643 max_xloc_node
= sorted_by_xloc
[-1]
1644 if max_xloc_node
.name
== 'Emission Viewer':
1645 max_xloc_node
= sorted_by_xloc
[-2]
1647 # get average y location
1650 sum_yloc
+= node
.location
.y
1652 new_locx
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1653 new_locy
= sum_yloc
/ len(nodes
)
1655 materialout
= nodes
.new(shader_output_ident
)
1656 materialout
.location
.x
= new_locx
1657 materialout
.location
.y
= new_locy
1658 materialout
.select
= False
1659 # Analyze outputs, add "Emission Viewer" if needed, make links
1662 for i
, out
in enumerate(active
.outputs
):
1663 if is_visible_socket(out
):
1664 valid_outputs
.append(i
)
1666 out_i
= valid_outputs
[0] # Start index of node's outputs
1667 for i
, valid_i
in enumerate(valid_outputs
):
1668 for out_link
in active
.outputs
[valid_i
].links
:
1669 if "Emission Viewer" in out_link
.to_node
.name
or (out_link
.to_node
== materialout
and out_link
.to_socket
== materialout
.inputs
[0]):
1670 if i
< len(valid_outputs
) - 1:
1671 out_i
= valid_outputs
[i
+ 1]
1673 out_i
= valid_outputs
[0]
1674 make_links
= [] # store sockets for new links
1676 # If output type not 'SHADER' - "Emission Viewer" needed
1677 if active
.outputs
[out_i
].type != 'SHADER':
1678 # get Emission Viewer node
1679 emission_exists
= False
1680 emission_placeholder
= nodes
[0]
1682 if "Emission Viewer" in node
.name
:
1683 emission_exists
= True
1684 emission_placeholder
= node
1685 if not emission_exists
:
1686 emission
= nodes
.new(shader_viewer_ident
)
1687 emission
.hide
= True
1688 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1689 emission
.label
= "Viewer"
1690 emission
.name
= "Emission Viewer"
1691 emission
.use_custom_color
= True
1692 emission
.color
= (0.6, 0.5, 0.4)
1693 emission
.select
= False
1695 emission
= emission_placeholder
1696 make_links
.append((active
.outputs
[out_i
], emission
.inputs
[0]))
1698 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1699 if emission
.outputs
[0].links
.__len
__() > 0:
1700 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1701 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1703 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1705 # Set brightness of viewer to compensate for Film and CM exposure
1706 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1707 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1708 emission
.inputs
[1].default_value
= intensity
1711 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1712 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[1 if active
.outputs
[out_i
].name
== "Volume" else 0]))
1714 if node
.name
== 'Emission Viewer':
1716 bpy
.ops
.node
.delete()
1717 for li_from
, li_to
in make_links
:
1718 links
.new(li_from
, li_to
)
1720 nodes
.active
= active
1722 if node
.name
in selection
:
1724 force_update(context
)
1727 return {'CANCELLED'}
1730 class NWFrameSelected(Operator
, NWBase
):
1731 bl_idname
= "node.nw_frame_selected"
1732 bl_label
= "Frame Selected"
1733 bl_description
= "Add a frame node and parent the selected nodes to it"
1734 bl_options
= {'REGISTER', 'UNDO'}
1736 label_prop
: StringProperty(
1738 description
='The visual name of the frame node',
1741 color_prop
: FloatVectorProperty(
1743 description
="The color of the frame node",
1744 default
=(0.6, 0.6, 0.6),
1745 min=0, max=1, step
=1, precision
=3,
1746 subtype
='COLOR_GAMMA', size
=3
1749 def execute(self
, context
):
1750 nodes
, links
= get_nodes_links(context
)
1753 if node
.select
== True:
1754 selected
.append(node
)
1756 bpy
.ops
.node
.add_node(type='NodeFrame')
1758 frm
.label
= self
.label_prop
1759 frm
.use_custom_color
= True
1760 frm
.color
= self
.color_prop
1762 for node
in selected
:
1768 class NWReloadImages(Operator
, NWBase
):
1769 bl_idname
= "node.nw_reload_images"
1770 bl_label
= "Reload Images"
1771 bl_description
= "Update all the image nodes to match their files on disk"
1773 def execute(self
, context
):
1774 nodes
, links
= get_nodes_links(context
)
1775 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1778 if node
.type in image_types
:
1779 if node
.type == "TEXTURE":
1780 if node
.texture
: # node has texture assigned
1781 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1782 if node
.texture
.image
: # texture has image assigned
1783 node
.texture
.image
.reload()
1791 self
.report({'INFO'}, "Reloaded images")
1792 print("Reloaded " + str(num_reloaded
) + " images")
1793 force_update(context
)
1796 self
.report({'WARNING'}, "No images found to reload in this node tree")
1797 return {'CANCELLED'}
1800 class NWSwitchNodeType(Operator
, NWBase
):
1801 """Switch type of selected nodes """
1802 bl_idname
= "node.nw_swtch_node_type"
1803 bl_label
= "Switch Node Type"
1804 bl_options
= {'REGISTER', 'UNDO'}
1806 to_type
: EnumProperty(
1807 name
="Switch to type",
1808 items
=list(shaders_input_nodes_props
) +
1809 list(shaders_output_nodes_props
) +
1810 list(shaders_shader_nodes_props
) +
1811 list(shaders_texture_nodes_props
) +
1812 list(shaders_color_nodes_props
) +
1813 list(shaders_vector_nodes_props
) +
1814 list(shaders_converter_nodes_props
) +
1815 list(shaders_layout_nodes_props
) +
1816 list(compo_input_nodes_props
) +
1817 list(compo_output_nodes_props
) +
1818 list(compo_color_nodes_props
) +
1819 list(compo_converter_nodes_props
) +
1820 list(compo_filter_nodes_props
) +
1821 list(compo_vector_nodes_props
) +
1822 list(compo_matte_nodes_props
) +
1823 list(compo_distort_nodes_props
) +
1824 list(compo_layout_nodes_props
) +
1825 list(blender_mat_input_nodes_props
) +
1826 list(blender_mat_output_nodes_props
) +
1827 list(blender_mat_color_nodes_props
) +
1828 list(blender_mat_vector_nodes_props
) +
1829 list(blender_mat_converter_nodes_props
) +
1830 list(blender_mat_layout_nodes_props
) +
1831 list(texture_input_nodes_props
) +
1832 list(texture_output_nodes_props
) +
1833 list(texture_color_nodes_props
) +
1834 list(texture_pattern_nodes_props
) +
1835 list(texture_textures_nodes_props
) +
1836 list(texture_converter_nodes_props
) +
1837 list(texture_distort_nodes_props
) +
1838 list(texture_layout_nodes_props
)
1841 def execute(self
, context
):
1842 nodes
, links
= get_nodes_links(context
)
1843 to_type
= self
.to_type
1844 # Those types of nodes will not swap.
1845 src_excludes
= ('NodeFrame')
1846 # Those attributes of nodes will be copied if possible
1847 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1848 'show_options', 'show_preview', 'show_texture',
1849 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1851 selected
= [n
for n
in nodes
if n
.select
]
1853 for node
in [n
for n
in selected
if
1854 n
.rna_type
.identifier
not in src_excludes
and
1855 n
.rna_type
.identifier
!= to_type
]:
1856 new_node
= nodes
.new(to_type
)
1857 for attr
in attrs_to_pass
:
1858 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1859 setattr(new_node
, attr
, getattr(node
, attr
))
1860 # set image datablock of dst to image of src
1861 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1863 new_node
.image
= node
.image
1865 if new_node
.type == 'SWITCH':
1866 new_node
.hide
= True
1867 # Dictionaries: src_sockets and dst_sockets:
1868 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1869 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1870 # in 'INPUTS' and 'OUTPUTS':
1871 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1873 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1875 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1876 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1879 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1880 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1882 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1883 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1884 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1885 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1886 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1887 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1888 # enumerate in inputs, then in outputs
1889 # find name, default value and links of socket
1890 for i
, socket
in enumerate(in_out
):
1891 the_name
= socket
.name
1893 # Not every socket, especially in outputs has "default_value"
1894 if hasattr(socket
, 'default_value'):
1895 dval
= socket
.default_value
1897 for lnk
in socket
.links
:
1898 socket_links
.append(lnk
)
1899 # check type of socket to fill proper keys.
1900 for the_type
in types_order_one
:
1901 if socket
.type == the_type
:
1902 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1903 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1904 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1905 # Check which of the types in inputs/outputs is considered to be "main".
1906 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1907 for type_check
in types_order_one
:
1908 if sockets
[in_out_name
][type_check
]:
1909 sockets
[in_out_name
]['MAIN'] = type_check
1913 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1914 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1917 for inout
, soctype
in (
1918 ('INPUTS', 'MAIN',),
1919 ('INPUTS', 'SHADER',),
1920 ('INPUTS', 'RGBA',),
1921 ('INPUTS', 'VECTOR',),
1922 ('INPUTS', 'VALUE',),
1923 ('OUTPUTS', 'MAIN',),
1924 ('OUTPUTS', 'SHADER',),
1925 ('OUTPUTS', 'RGBA',),
1926 ('OUTPUTS', 'VECTOR',),
1927 ('OUTPUTS', 'VALUE',),
1929 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1930 if soctype
== 'MAIN':
1931 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1932 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1934 sc
= src_sockets
[inout
][soctype
]
1935 dt
= dst_sockets
[inout
][soctype
]
1936 # start with 'dt' to determine number of possibilities.
1937 for i
, soc
in enumerate(dt
):
1938 # if src main has enough entries - match them with dst main sockets by indexes.
1940 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1941 # add 'VALUE_NAME' criterion to inputs.
1942 if inout
== 'INPUTS' and soctype
== 'VALUE':
1944 if s
[2] == soc
[2]: # if names match
1945 # append src (index, dval), dst (index, dval)
1946 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1948 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1949 # This creates better links when relinking textures.
1950 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1951 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1953 # Pass default values and RELINK:
1954 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1955 # INPUTS: Base on matches in proper order.
1956 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1958 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1959 new_node
.inputs
[dst_i
].default_value
= src_dval
1960 # Special case: switch to math
1961 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1962 new_node
.type == 'MATH' and\
1964 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1965 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1966 if node
.type == 'MIX_RGB':
1967 if node
.blend_type
in [o
[0] for o
in operations
]:
1968 new_node
.operation
= node
.blend_type
1969 # Special case: switch from math to some types
1970 if node
.type == 'MATH' and\
1971 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1974 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1975 if new_node
.type == 'MIX_RGB':
1976 if node
.operation
in [t
[0] for t
in blend_types
]:
1977 new_node
.blend_type
= node
.operation
1978 # Set Fac of MIX_RGB to 1.0
1979 new_node
.inputs
[0].default_value
= 1.0
1980 # make link only when dst matching input is not linked already.
1981 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1982 in_src_link
= node
.inputs
[src_i
].links
[0]
1983 in_dst_socket
= new_node
.inputs
[dst_i
]
1984 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1985 links
.remove(in_src_link
)
1986 # OUTPUTS: Base on matches in proper order.
1987 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1988 for out_src_link
in node
.outputs
[src_i
].links
:
1989 out_dst_socket
= new_node
.outputs
[dst_i
]
1990 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1991 # relink rest inputs if possible, no criteria
1992 for src_inp
in node
.inputs
:
1993 for dst_inp
in new_node
.inputs
:
1994 if src_inp
.links
and not dst_inp
.links
:
1995 src_link
= src_inp
.links
[0]
1996 links
.new(src_link
.from_socket
, dst_inp
)
1997 links
.remove(src_link
)
1998 # relink rest outputs if possible, base on node kind if any left.
1999 for src_o
in node
.outputs
:
2000 for out_src_link
in src_o
.links
:
2001 for dst_o
in new_node
.outputs
:
2002 if src_o
.type == dst_o
.type:
2003 links
.new(dst_o
, out_src_link
.to_socket
)
2004 # relink rest outputs no criteria if any left. Link all from first output.
2005 for src_o
in node
.outputs
:
2006 for out_src_link
in src_o
.links
:
2007 if new_node
.outputs
:
2008 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
2010 force_update(context
)
2014 class NWMergeNodes(Operator
, NWBase
):
2015 bl_idname
= "node.nw_merge_nodes"
2016 bl_label
= "Merge Nodes"
2017 bl_description
= "Merge Selected Nodes"
2018 bl_options
= {'REGISTER', 'UNDO'}
2022 description
="All possible blend types and math operations",
2023 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2025 merge_type
: EnumProperty(
2027 description
="Type of Merge to be used",
2029 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2030 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2031 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2032 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2033 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2034 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2038 def execute(self
, context
):
2039 settings
= context
.preferences
.addons
[__name__
].preferences
2040 merge_hide
= settings
.merge_hide
2041 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2044 do_hide_shader
= False
2045 if merge_hide
== 'ALWAYS':
2047 do_hide_shader
= True
2048 elif merge_hide
== 'NON_SHADER':
2051 tree_type
= context
.space_data
.node_tree
.type
2052 if tree_type
== 'COMPOSITING':
2053 node_type
= 'CompositorNode'
2054 elif tree_type
== 'SHADER':
2055 node_type
= 'ShaderNode'
2056 elif tree_type
== 'TEXTURE':
2057 node_type
= 'TextureNode'
2058 nodes
, links
= get_nodes_links(context
)
2060 merge_type
= self
.merge_type
2061 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2062 # 'ZCOMBINE' works only if mode == 'MIX'
2063 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2064 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2067 selected_mix
= [] # entry = [index, loc]
2068 selected_shader
= [] # entry = [index, loc]
2069 selected_math
= [] # entry = [index, loc]
2070 selected_z
= [] # entry = [index, loc]
2071 selected_alphaover
= [] # entry = [index, loc]
2073 for i
, node
in enumerate(nodes
):
2074 if node
.select
and node
.outputs
:
2075 if merge_type
== 'AUTO':
2076 for (type, types_list
, dst
) in (
2077 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2078 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2079 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2081 output_type
= node
.outputs
[0].type
2082 valid_mode
= mode
in types_list
2083 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2084 # Cheat that output type is 'RGBA',
2085 # and that 'MIX' exists in math operations list.
2086 # This way when selected_mix list is analyzed:
2087 # Node data will be appended even though it doesn't meet requirements.
2088 if output_type
!= 'SHADER' and mode
== 'MIX':
2089 output_type
= 'RGBA'
2091 if output_type
== type and valid_mode
:
2092 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2094 for (type, types_list
, dst
) in (
2095 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2096 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2097 ('MATH', [t
[0] for t
in operations
], selected_math
),
2098 ('ZCOMBINE', ('MIX', ), selected_z
),
2099 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2101 if merge_type
== type and mode
in types_list
:
2102 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2103 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2104 # use only 'Mix' nodes for merging.
2105 # For that we add selected_math list to selected_mix list and clear selected_math.
2106 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2107 selected_mix
+= selected_math
2110 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2112 count_before
= len(nodes
)
2113 # sort list by loc_x - reversed
2114 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2116 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2117 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2118 if merge_position
== 'CENTER':
2119 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)
2120 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2126 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2130 if nodes_list
== selected_shader
and not do_hide_shader
:
2132 the_range
= len(nodes_list
) - 1
2133 if len(nodes_list
) == 1:
2135 for i
in range(the_range
):
2136 if nodes_list
== selected_mix
:
2137 add_type
= node_type
+ 'MixRGB'
2138 add
= nodes
.new(add_type
)
2139 add
.blend_type
= mode
2141 add
.inputs
[0].default_value
= 1.0
2142 add
.show_preview
= False
2148 add
.width_hidden
= 100.0
2149 elif nodes_list
== selected_math
:
2150 add_type
= node_type
+ 'Math'
2151 add
= nodes
.new(add_type
)
2152 add
.operation
= mode
2158 add
.width_hidden
= 100.0
2159 elif nodes_list
== selected_shader
:
2161 add_type
= node_type
+ 'MixShader'
2162 add
= nodes
.new(add_type
)
2163 add
.hide
= do_hide_shader
2168 add
.width_hidden
= 100.0
2170 add_type
= node_type
+ 'AddShader'
2171 add
= nodes
.new(add_type
)
2172 add
.hide
= do_hide_shader
2177 add
.width_hidden
= 100.0
2178 elif nodes_list
== selected_z
:
2179 add
= nodes
.new('CompositorNodeZcombine')
2180 add
.show_preview
= False
2186 add
.width_hidden
= 100.0
2187 elif nodes_list
== selected_alphaover
:
2188 add
= nodes
.new('CompositorNodeAlphaOver')
2189 add
.show_preview
= False
2195 add
.width_hidden
= 100.0
2196 add
.location
= loc_x
, loc_y
2200 count_after
= len(nodes
)
2201 index
= count_after
- 1
2202 first_selected
= nodes
[nodes_list
[0][0]]
2203 # "last" node has been added as first, so its index is count_before.
2204 last_add
= nodes
[count_before
]
2206 # Two nodes were selected and first selected has no output links, second selected has output links.
2207 # Then add links from last add to all links 'to_socket' of out links of second selected.
2208 if len(nodes_list
) == 2:
2209 if not first_selected
.outputs
[0].links
:
2210 second_selected
= nodes
[nodes_list
[1][0]]
2211 for ss_link
in second_selected
.outputs
[0].links
:
2212 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2213 # Create list of invalid indexes.
2214 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2215 # Link only if "to_node" index not in invalid indexes list.
2216 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2217 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2218 # add links from last_add to all links 'to_socket' of out links of first selected.
2219 for fs_link
in first_selected
.outputs
[0].links
:
2220 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2221 # Create list of invalid indexes.
2222 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2223 # Link only if "to_node" index not in invalid indexes list.
2224 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2225 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2226 # add link from "first" selected and "first" add node
2227 node_to
= nodes
[count_after
- 1]
2228 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2229 if node_to
.type == 'ZCOMBINE':
2230 for fs_out
in first_selected
.outputs
:
2231 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2232 links
.new(fs_out
, node_to
.inputs
[1])
2234 # add links between added ADD nodes and between selected and ADD nodes
2235 for i
in range(count_adds
):
2236 if i
< count_adds
- 1:
2237 node_from
= nodes
[index
]
2238 node_to
= nodes
[index
- 1]
2239 node_to_input_i
= first
2240 node_to_z_i
= 1 # if z combine - link z to first z input
2241 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2242 if node_to
.type == 'ZCOMBINE':
2243 for from_out
in node_from
.outputs
:
2244 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2245 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2246 if len(nodes_list
) > 1:
2247 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2248 node_to
= nodes
[index
]
2249 node_to_input_i
= second
2250 node_to_z_i
= 3 # if z combine - link z to second z input
2251 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2252 if node_to
.type == 'ZCOMBINE':
2253 for from_out
in node_from
.outputs
:
2254 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2255 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2257 # set "last" of added nodes as active
2258 nodes
.active
= last_add
2259 for i
, x
, y
, dx
, h
in nodes_list
:
2260 nodes
[i
].select
= False
2265 class NWBatchChangeNodes(Operator
, NWBase
):
2266 bl_idname
= "node.nw_batch_change"
2267 bl_label
= "Batch Change"
2268 bl_description
= "Batch Change Blend Type and Math Operation"
2269 bl_options
= {'REGISTER', 'UNDO'}
2271 blend_type
: EnumProperty(
2273 items
=blend_types
+ navs
,
2275 operation
: EnumProperty(
2277 items
=operations
+ navs
,
2280 def execute(self
, context
):
2282 nodes
, links
= get_nodes_links(context
)
2283 blend_type
= self
.blend_type
2284 operation
= self
.operation
2285 for node
in context
.selected_nodes
:
2286 if node
.type == 'MIX_RGB':
2287 if not blend_type
in [nav
[0] for nav
in navs
]:
2288 node
.blend_type
= blend_type
2290 if blend_type
== 'NEXT':
2291 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2292 #index = blend_types.index(node.blend_type)
2293 if index
== len(blend_types
) - 1:
2294 node
.blend_type
= blend_types
[0][0]
2296 node
.blend_type
= blend_types
[index
+ 1][0]
2298 if blend_type
== 'PREV':
2299 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2301 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2303 node
.blend_type
= blend_types
[index
- 1][0]
2305 if node
.type == 'MATH':
2306 if not operation
in [nav
[0] for nav
in navs
]:
2307 node
.operation
= operation
2309 if operation
== 'NEXT':
2310 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2311 #index = operations.index(node.operation)
2312 if index
== len(operations
) - 1:
2313 node
.operation
= operations
[0][0]
2315 node
.operation
= operations
[index
+ 1][0]
2317 if operation
== 'PREV':
2318 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2319 #index = operations.index(node.operation)
2321 node
.operation
= operations
[len(operations
) - 1][0]
2323 node
.operation
= operations
[index
- 1][0]
2328 class NWChangeMixFactor(Operator
, NWBase
):
2329 bl_idname
= "node.nw_factor"
2330 bl_label
= "Change Factor"
2331 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2332 bl_options
= {'REGISTER', 'UNDO'}
2334 # option: Change factor.
2335 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2336 # Else - change factor by option value.
2337 option
: FloatProperty()
2339 def execute(self
, context
):
2340 nodes
, links
= get_nodes_links(context
)
2341 option
= self
.option
2342 selected
= [] # entry = index
2343 for si
, node
in enumerate(nodes
):
2345 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2349 fac
= nodes
[si
].inputs
[0]
2350 nodes
[si
].hide
= False
2351 if option
in {0.0, 1.0}:
2352 fac
.default_value
= option
2354 fac
.default_value
+= option
2359 class NWCopySettings(Operator
, NWBase
):
2360 bl_idname
= "node.nw_copy_settings"
2361 bl_label
= "Copy Settings"
2362 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2363 bl_options
= {'REGISTER', 'UNDO'}
2366 def poll(cls
, context
):
2368 if nw_check(context
):
2370 context
.active_node
is not None and
2371 context
.active_node
.type != 'FRAME'
2376 def execute(self
, context
):
2377 node_active
= context
.active_node
2378 node_selected
= context
.selected_nodes
2381 if not (len(node_selected
) > 1):
2382 self
.report({'ERROR'}, "2 nodes must be selected at least")
2383 return {'CANCELLED'}
2385 # Check if active node is in the selection
2386 selected_node_names
= [n
.name
for n
in node_selected
]
2387 if node_active
.name
not in selected_node_names
:
2388 self
.report({'ERROR'}, "No active node")
2389 return {'CANCELLED'}
2391 # Get nodes in selection by type
2392 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2394 if not (len(valid_nodes
) > 1) and node_active
:
2395 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2396 return {'CANCELLED'}
2398 if len(valid_nodes
) != len(node_selected
):
2399 # Report nodes that are not valid
2400 valid_node_names
= [n
.name
for n
in valid_nodes
]
2401 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2402 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2404 # Reference original
2406 #node_selected_names = [n.name for n in node_selected]
2411 # Deselect all nodes
2412 for i
in node_selected
:
2415 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2416 # Run through all other nodes
2417 for node
in valid_nodes
[1:]:
2419 # Check for frame node
2420 parent
= node
.parent
if node
.parent
else None
2421 node_loc
= [node
.location
.x
, node
.location
.y
]
2423 # Select original to duplicate
2426 # Duplicate selected node
2427 bpy
.ops
.node
.duplicate()
2428 new_node
= context
.selected_nodes
[0]
2431 new_node
.select
= False
2433 # Properties to copy
2434 node_tree
= node
.id_data
2435 props_to_copy
= 'bl_idname name location height width'.split(' ')
2439 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2440 for i
in (i
for i
in mappings
if i
.is_linked
):
2442 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2445 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2446 props_to_copy
.pop(0)
2448 for prop
in props_to_copy
:
2449 setattr(new_node
, prop
, props
[prop
])
2451 # Get the node tree to remove the old node
2452 nodes
= node_tree
.nodes
2454 new_node
.name
= props
['name']
2457 new_node
.parent
= parent
2458 new_node
.location
= node_loc
2460 for str_from
, str_to
in reconnections
:
2461 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2463 success_names
.append(new_node
.name
)
2466 node_tree
.nodes
.active
= orig
2467 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2471 class NWCopyLabel(Operator
, NWBase
):
2472 bl_idname
= "node.nw_copy_label"
2473 bl_label
= "Copy Label"
2474 bl_options
= {'REGISTER', 'UNDO'}
2476 option
: EnumProperty(
2478 description
="Source of name of label",
2480 ('FROM_ACTIVE', 'from active', 'from active node',),
2481 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2482 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2486 def execute(self
, context
):
2487 nodes
, links
= get_nodes_links(context
)
2488 option
= self
.option
2489 active
= nodes
.active
2490 if option
== 'FROM_ACTIVE':
2492 src_label
= active
.label
2493 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2494 node
.label
= src_label
2495 elif option
== 'FROM_NODE':
2496 selected
= [n
for n
in nodes
if n
.select
]
2497 for node
in selected
:
2498 for input in node
.inputs
:
2500 src
= input.links
[0].from_node
2501 node
.label
= src
.label
2503 elif option
== 'FROM_SOCKET':
2504 selected
= [n
for n
in nodes
if n
.select
]
2505 for node
in selected
:
2506 for input in node
.inputs
:
2508 src
= input.links
[0].from_socket
2509 node
.label
= src
.name
2515 class NWClearLabel(Operator
, NWBase
):
2516 bl_idname
= "node.nw_clear_label"
2517 bl_label
= "Clear Label"
2518 bl_options
= {'REGISTER', 'UNDO'}
2520 option
: BoolProperty()
2522 def execute(self
, context
):
2523 nodes
, links
= get_nodes_links(context
)
2524 for node
in [n
for n
in nodes
if n
.select
]:
2529 def invoke(self
, context
, event
):
2531 return self
.execute(context
)
2533 return context
.window_manager
.invoke_confirm(self
, event
)
2536 class NWModifyLabels(Operator
, NWBase
):
2537 """Modify Labels of all selected nodes"""
2538 bl_idname
= "node.nw_modify_labels"
2539 bl_label
= "Modify Labels"
2540 bl_options
= {'REGISTER', 'UNDO'}
2542 prepend
: StringProperty(
2543 name
="Add to Beginning"
2545 append
: StringProperty(
2548 replace_from
: StringProperty(
2549 name
="Text to Replace"
2551 replace_to
: StringProperty(
2555 def execute(self
, context
):
2556 nodes
, links
= get_nodes_links(context
)
2557 for node
in [n
for n
in nodes
if n
.select
]:
2558 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2562 def invoke(self
, context
, event
):
2566 return context
.window_manager
.invoke_props_dialog(self
)
2569 class NWAddTextureSetup(Operator
, NWBase
):
2570 bl_idname
= "node.nw_add_texture"
2571 bl_label
= "Texture Setup"
2572 bl_description
= "Add Texture Node Setup to Selected Shaders"
2573 bl_options
= {'REGISTER', 'UNDO'}
2575 add_mapping
: BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2578 def poll(cls
, context
):
2580 if nw_check(context
):
2581 space
= context
.space_data
2582 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2586 def execute(self
, context
):
2587 nodes
, links
= get_nodes_links(context
)
2588 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2589 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2590 selected_nodes
= [n
for n
in nodes
if n
.select
]
2591 for t_node
in selected_nodes
:
2595 for index
, i
in enumerate(t_node
.inputs
):
2601 locx
= t_node
.location
.x
2602 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2604 xoffset
= [500, 700]
2606 if t_node
.type in texture_types
+ ['MAPPING']:
2607 xoffset
= [290, 500]
2611 image_type
= 'ShaderNodeTexImage'
2613 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2614 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2615 if t_node
.type == 'BACKGROUND':
2616 image_type
= 'ShaderNodeTexEnvironment'
2619 tex
= nodes
.new(image_type
)
2620 tex
.location
= [locx
- 200, locy
+ 112]
2622 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2624 t_node
.select
= False
2625 if self
.add_mapping
or is_texture
:
2626 if t_node
.type != 'MAPPING':
2627 m
= nodes
.new('ShaderNodeMapping')
2628 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2632 coord
= nodes
.new('ShaderNodeTexCoord')
2633 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2636 links
.new(m
.outputs
[0], tex
.inputs
[0])
2637 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2640 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2641 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2643 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2647 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2648 bl_idname
= "node.nw_add_textures_for_principled"
2649 bl_label
= "Principled Texture Setup"
2650 bl_description
= "Add Texture Node Setup for Principled BSDF"
2651 bl_options
= {'REGISTER', 'UNDO'}
2653 directory
: StringProperty(
2657 description
='Folder to search in for image files'
2659 files
: CollectionProperty(
2660 type=bpy
.types
.OperatorFileListElement
,
2661 options
={'HIDDEN', 'SKIP_SAVE'}
2670 def poll(cls
, context
):
2672 if nw_check(context
):
2673 space
= context
.space_data
2674 if space
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
2678 def execute(self
, context
):
2679 # Check if everything is ok
2680 if not self
.directory
:
2681 self
.report({'INFO'}, 'No Folder Selected')
2682 return {'CANCELLED'}
2683 if not self
.files
[:]:
2684 self
.report({'INFO'}, 'No Files Selected')
2685 return {'CANCELLED'}
2687 nodes
, links
= get_nodes_links(context
)
2688 active_node
= nodes
.active
2689 if not active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled':
2690 self
.report({'INFO'}, 'Select Principled BSDF')
2691 return {'CANCELLED'}
2694 def split_into__components(fname
):
2695 # Split filename into components
2696 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2698 fname
= path
.splitext(fname
)[0]
2700 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2701 # Separate CamelCase by space
2702 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2703 # Replace common separators with SPACE
2704 seperators
= ['_', '.', '-', '__', '--', '#']
2705 for sep
in seperators
:
2706 fname
= fname
.replace(sep
, ' ')
2708 components
= fname
.split(' ')
2709 components
= [c
.lower() for c
in components
]
2712 # Filter textures names for texturetypes in filenames
2713 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2714 tags
= context
.preferences
.addons
[__name__
].preferences
.principled_tags
2715 normal_abbr
= tags
.normal
.split(' ')
2716 bump_abbr
= tags
.bump
.split(' ')
2717 gloss_abbr
= tags
.gloss
.split(' ')
2718 rough_abbr
= tags
.rough
.split(' ')
2720 ['Displacement', tags
.displacement
.split(' '), None],
2721 ['Base Color', tags
.base_color
.split(' '), None],
2722 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2723 ['Metallic', tags
.metallic
.split(' '), None],
2724 ['Specular', tags
.specular
.split(' '), None],
2725 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2726 ['Normal', normal_abbr
+ bump_abbr
, None],
2729 # Look through texture_types and set value as filename of first matched file
2730 def match_files_to_socket_names():
2731 for sname
in socketnames
:
2732 for file in self
.files
:
2734 filenamecomponents
= split_into__components(fname
)
2735 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
2736 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
2741 match_files_to_socket_names()
2742 # Remove socketnames without found files
2743 socketnames
= [s
for s
in socketnames
if s
[2]
2744 and path
.exists(self
.directory
+s
[2])]
2746 self
.report({'INFO'}, 'No matching images found')
2747 print('No matching images found')
2748 return {'CANCELLED'}
2751 print('\nMatched Textures:')
2755 roughness_node
= None
2756 for i
, sname
in enumerate(socketnames
):
2757 print(i
, sname
[0], sname
[2])
2759 # DISPLACEMENT NODES
2760 if sname
[0] == 'Displacement':
2761 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
2762 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2763 disp_texture
.image
= img
2764 disp_texture
.label
= 'Displacement'
2765 if disp_texture
.image
:
2766 disp_texture
.image
.colorspace_settings
.is_data
= True
2768 # Add displacement offset nodes
2769 disp_node
= nodes
.new(type='ShaderNodeDisplacement')
2770 disp_node
.location
= active_node
.location
+ Vector((0, -560))
2771 link
= links
.new(disp_node
.inputs
[0], disp_texture
.outputs
[0])
2773 # TODO Turn on true displacement in the material
2774 # Too complicated for now
2777 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
2779 if not output_node
[0].inputs
[2].is_linked
:
2780 link
= links
.new(output_node
[0].inputs
[2], disp_node
.outputs
[0])
2784 if not active_node
.inputs
[sname
[0]].is_linked
:
2785 # No texture node connected -> add texture node with new image
2786 texture_node
= nodes
.new(type='ShaderNodeTexImage')
2787 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2788 texture_node
.image
= img
2791 if sname
[0] == 'Normal':
2792 # Test if new texture node is normal or bump map
2793 fname_components
= split_into__components(sname
[2])
2794 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
2795 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
2797 # If Normal add normal node in between
2798 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
2799 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
2801 # If Bump add bump node in between
2802 normal_node
= nodes
.new(type='ShaderNodeBump')
2803 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
2805 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
2806 normal_node_texture
= texture_node
2808 elif sname
[0] == 'Roughness':
2809 # Test if glossy or roughness map
2810 fname_components
= split_into__components(sname
[2])
2811 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
2812 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
2815 # If Roughness nothing to to
2816 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2819 # If Gloss Map add invert node
2820 invert_node
= nodes
.new(type='ShaderNodeInvert')
2821 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
2823 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
2824 roughness_node
= texture_node
2827 # This is a simple connection Texture --> Input slot
2828 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2830 # Use non-color for all but 'Base Color' Textures
2831 if not sname
[0] in ['Base Color'] and texture_node
.image
:
2832 texture_node
.image
.colorspace_settings
.is_data
= True
2835 # If already texture connected. add to node list for alignment
2836 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
2838 # This are all connected texture nodes
2839 texture_nodes
.append(texture_node
)
2840 texture_node
.label
= sname
[0]
2843 texture_nodes
.append(disp_texture
)
2846 for i
, texture_node
in enumerate(texture_nodes
):
2847 offset
= Vector((-550, (i
* -280) + 200))
2848 texture_node
.location
= active_node
.location
+ offset
2851 # Extra alignment if normal node was added
2852 normal_node
.location
= normal_node_texture
.location
+ Vector((300, 0))
2855 # Alignment of invert node if glossy map
2856 invert_node
.location
= roughness_node
.location
+ Vector((300, 0))
2858 # Add texture input + mapping
2859 mapping
= nodes
.new(type='ShaderNodeMapping')
2860 mapping
.location
= active_node
.location
+ Vector((-1050, 0))
2861 if len(texture_nodes
) > 1:
2862 # If more than one texture add reroute node in between
2863 reroute
= nodes
.new(type='NodeReroute')
2864 texture_nodes
.append(reroute
)
2865 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
2866 reroute
.location
= tex_coords
+ Vector((-50, -120))
2867 for texture_node
in texture_nodes
:
2868 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
2869 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
2871 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
2873 # Connect texture_coordiantes to mapping node
2874 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
2875 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
2876 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
2878 # Create frame around tex coords and mapping
2879 frame
= nodes
.new(type='NodeFrame')
2880 frame
.label
= 'Mapping'
2881 mapping
.parent
= frame
2882 texture_input
.parent
= frame
2885 # Create frame around texture nodes
2886 frame
= nodes
.new(type='NodeFrame')
2887 frame
.label
= 'Textures'
2888 for tnode
in texture_nodes
:
2889 tnode
.parent
= frame
2893 active_node
.select
= False
2896 force_update(context
)
2900 class NWAddReroutes(Operator
, NWBase
):
2901 """Add Reroute Nodes and link them to outputs of selected nodes"""
2902 bl_idname
= "node.nw_add_reroutes"
2903 bl_label
= "Add Reroutes"
2904 bl_description
= "Add Reroutes to Outputs"
2905 bl_options
= {'REGISTER', 'UNDO'}
2907 option
: EnumProperty(
2910 ('ALL', 'to all', 'Add to all outputs'),
2911 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2912 ('LINKED', 'to linked', 'Add only to linked outputs'),
2916 def execute(self
, context
):
2917 tree_type
= context
.space_data
.node_tree
.type
2918 option
= self
.option
2919 nodes
, links
= get_nodes_links(context
)
2920 # output valid when option is 'all' or when 'loose' output has no links
2922 post_select
= [] # nodes to be selected after execution
2923 # create reroutes and recreate links
2924 for node
in [n
for n
in nodes
if n
.select
]:
2929 # unhide 'REROUTE' nodes to avoid issues with location.y
2930 if node
.type == 'REROUTE':
2932 # When node is hidden - width_hidden not usable.
2933 # Hack needed to calculate real width
2935 bpy
.ops
.node
.select_all(action
='DESELECT')
2936 helper
= nodes
.new('NodeReroute')
2937 helper
.select
= True
2939 # resize node and helper to zero. Then check locations to calculate width
2940 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2941 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2942 # restore node location
2943 node
.location
= x
, y
2946 # only helper is selected now
2947 bpy
.ops
.node
.delete()
2948 x
= node
.location
.x
+ width
+ 20.0
2949 if node
.type != 'REROUTE':
2953 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2954 for out_i
, output
in enumerate(node
.outputs
):
2955 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2956 # if node != 'R_LAYERS' - "pass_used" not needed, so set it to True
2957 if node
.type != 'R_LAYERS':
2959 else: # if 'R_LAYERS' check if output represent used render pass
2960 node_scene
= node
.scene
2961 node_layer
= node
.layer
2962 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2963 if output
.name
== 'Alpha':
2966 # check entries in global 'rl_outputs' variable
2967 for rlo
in rl_outputs
:
2968 if output
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
2969 pass_used
= getattr(node_scene
.view_layers
[node_layer
], rlo
.render_pass
)
2972 valid
= ((option
== 'ALL') or
2973 (option
== 'LOOSE' and not output
.links
) or
2974 (option
== 'LINKED' and output
.links
))
2975 # Add reroutes only if valid, but offset location in all cases.
2977 n
= nodes
.new('NodeReroute')
2979 for link
in output
.links
:
2980 links
.new(n
.outputs
[0], link
.to_socket
)
2981 links
.new(output
, n
.inputs
[0])
2983 post_select
.append(n
)
2987 # disselect the node so that after execution of script only newly created nodes are selected
2989 # nicer reroutes distribution along y when node.hide
2991 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2992 for reroute
in [r
for r
in nodes
if r
.select
]:
2993 reroute
.location
.y
-= y_translate
2994 for node
in post_select
:
3000 class NWLinkActiveToSelected(Operator
, NWBase
):
3001 """Link active node to selected nodes basing on various criteria"""
3002 bl_idname
= "node.nw_link_active_to_selected"
3003 bl_label
= "Link Active Node to Selected"
3004 bl_options
= {'REGISTER', 'UNDO'}
3006 replace
: BoolProperty()
3007 use_node_name
: BoolProperty()
3008 use_outputs_names
: BoolProperty()
3011 def poll(cls
, context
):
3013 if nw_check(context
):
3014 if context
.active_node
is not None:
3015 if context
.active_node
.select
:
3019 def execute(self
, context
):
3020 nodes
, links
= get_nodes_links(context
)
3021 replace
= self
.replace
3022 use_node_name
= self
.use_node_name
3023 use_outputs_names
= self
.use_outputs_names
3024 active
= nodes
.active
3025 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3026 outputs
= [] # Only usable outputs of active nodes will be stored here.
3027 for out
in active
.outputs
:
3028 if active
.type != 'R_LAYERS':
3031 # 'R_LAYERS' node type needs special handling.
3032 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3033 # Only outputs that represent used passes should be taken into account
3034 # Check if pass represented by output is used.
3035 # global 'rl_outputs' list will be used for that
3036 for rlo
in rl_outputs
:
3037 pass_used
= False # initial value. Will be set to True if pass is used
3038 if out
.name
== 'Alpha':
3039 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3041 elif out
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
3042 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3043 pass_used
= getattr(active
.scene
.view_layers
[active
.layer
], rlo
.render_pass
)
3047 doit
= True # Will be changed to False when links successfully added to previous output.
3050 for node
in selected
:
3051 dst_name
= node
.name
# Will be compared with src_name if needed.
3052 # When node has label - use it as dst_name
3054 dst_name
= node
.label
3055 valid
= True # Initial value. Will be changed to False if names don't match.
3056 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3058 # Set src_name to source node name or label
3059 src_name
= active
.name
3061 src_name
= active
.label
3062 elif use_outputs_names
:
3063 src_name
= (out
.name
, )
3064 for rlo
in rl_outputs
:
3065 if out
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
3066 src_name
= (rlo
.output_name
, rlo
.exr_output_name
)
3067 if dst_name
not in src_name
:
3070 for input in node
.inputs
:
3071 if input.type == out
.type or node
.type == 'REROUTE':
3072 if replace
or not input.is_linked
:
3073 links
.new(out
, input)
3074 if not use_node_name
and not use_outputs_names
:
3081 class NWAlignNodes(Operator
, NWBase
):
3082 '''Align the selected nodes neatly in a row/column'''
3083 bl_idname
= "node.nw_align_nodes"
3084 bl_label
= "Align Nodes"
3085 bl_options
= {'REGISTER', 'UNDO'}
3086 margin
: IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3088 def execute(self
, context
):
3089 nodes
, links
= get_nodes_links(context
)
3090 margin
= self
.margin
3094 if node
.select
and node
.type != 'FRAME':
3095 selection
.append(node
)
3097 # If no nodes are selected, align all nodes
3101 elif nodes
.active
in selection
:
3102 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3104 # Check if nodes should be laid out horizontally or vertically
3105 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3106 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3107 x_range
= max(x_locs
) - min(x_locs
)
3108 y_range
= max(y_locs
) - min(y_locs
)
3109 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3110 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3111 horizontal
= x_range
> y_range
3113 # Sort selection by location of node mid-point
3115 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3117 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3121 for node
in selection
:
3122 current_margin
= margin
3123 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3126 node
.location
.x
= current_pos
3127 current_pos
+= current_margin
+ node
.dimensions
.x
3128 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3130 node
.location
.y
= current_pos
3131 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3132 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3134 # If active node is selected, center nodes around it
3135 if active_loc
is not None:
3136 active_loc_diff
= active_loc
- nodes
.active
.location
3137 for node
in selection
:
3138 node
.location
+= active_loc_diff
3139 else: # Position nodes centered around where they used to be
3140 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
])
3141 new_mid
= (max(locs
) + min(locs
)) / 2
3142 for node
in selection
:
3144 node
.location
.x
+= (mid_x
- new_mid
)
3146 node
.location
.y
+= (mid_y
- new_mid
)
3151 class NWSelectParentChildren(Operator
, NWBase
):
3152 bl_idname
= "node.nw_select_parent_child"
3153 bl_label
= "Select Parent or Children"
3154 bl_options
= {'REGISTER', 'UNDO'}
3156 option
: EnumProperty(
3159 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3160 ('CHILD', 'Select Children', 'Select members of selected frame'),
3164 def execute(self
, context
):
3165 nodes
, links
= get_nodes_links(context
)
3166 option
= self
.option
3167 selected
= [node
for node
in nodes
if node
.select
]
3168 if option
== 'PARENT':
3169 for sel
in selected
:
3172 parent
.select
= True
3173 else: # option == 'CHILD'
3174 for sel
in selected
:
3175 children
= [node
for node
in nodes
if node
.parent
== sel
]
3176 for kid
in children
:
3182 class NWDetachOutputs(Operator
, NWBase
):
3183 """Detach outputs of selected node leaving inputs linked"""
3184 bl_idname
= "node.nw_detach_outputs"
3185 bl_label
= "Detach Outputs"
3186 bl_options
= {'REGISTER', 'UNDO'}
3188 def execute(self
, context
):
3189 nodes
, links
= get_nodes_links(context
)
3190 selected
= context
.selected_nodes
3191 bpy
.ops
.node
.duplicate_move_keep_inputs()
3192 new_nodes
= context
.selected_nodes
3193 bpy
.ops
.node
.select_all(action
="DESELECT")
3194 for node
in selected
:
3196 bpy
.ops
.node
.delete_reconnect()
3197 for new_node
in new_nodes
:
3198 new_node
.select
= True
3199 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3204 class NWLinkToOutputNode(Operator
, NWBase
):
3205 """Link to Composite node or Material Output node"""
3206 bl_idname
= "node.nw_link_out"
3207 bl_label
= "Connect to Output"
3208 bl_options
= {'REGISTER', 'UNDO'}
3211 def poll(cls
, context
):
3213 if nw_check(context
):
3214 if context
.active_node
is not None:
3215 for out
in context
.active_node
.outputs
:
3216 if is_visible_socket(out
):
3221 def execute(self
, context
):
3222 nodes
, links
= get_nodes_links(context
)
3223 active
= nodes
.active
3226 tree_type
= context
.space_data
.tree_type
3227 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3228 output_types_compo
= ['COMPOSITE']
3229 output_types_blender_mat
= ['OUTPUT']
3230 output_types_textures
= ['OUTPUT']
3231 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3233 if node
.type in output_types
:
3237 bpy
.ops
.node
.select_all(action
="DESELECT")
3238 if tree_type
== 'ShaderNodeTree':
3239 if is_cycles_or_eevee(context
):
3240 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3242 output_node
= nodes
.new('ShaderNodeOutput')
3243 elif tree_type
== 'CompositorNodeTree':
3244 output_node
= nodes
.new('CompositorNodeComposite')
3245 elif tree_type
== 'TextureNodeTree':
3246 output_node
= nodes
.new('TextureNodeOutput')
3247 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3248 output_node
.location
.y
= active
.location
.y
3249 if (output_node
and active
.outputs
):
3250 for i
, output
in enumerate(active
.outputs
):
3251 if is_visible_socket(output
):
3254 for i
, output
in enumerate(active
.outputs
):
3255 if output
.type == output_node
.inputs
[0].type and is_visible_socket(output
):
3260 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3261 if active
.outputs
[output_index
].name
== 'Volume':
3263 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3265 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3267 force_update(context
) # viewport render does not update
3272 class NWMakeLink(Operator
, NWBase
):
3273 """Make a link from one socket to another"""
3274 bl_idname
= 'node.nw_make_link'
3275 bl_label
= 'Make Link'
3276 bl_options
= {'REGISTER', 'UNDO'}
3277 from_socket
: IntProperty()
3278 to_socket
: IntProperty()
3280 def execute(self
, context
):
3281 nodes
, links
= get_nodes_links(context
)
3283 n1
= nodes
[context
.scene
.NWLazySource
]
3284 n2
= nodes
[context
.scene
.NWLazyTarget
]
3286 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3288 force_update(context
)
3293 class NWCallInputsMenu(Operator
, NWBase
):
3294 """Link from this output"""
3295 bl_idname
= 'node.nw_call_inputs_menu'
3296 bl_label
= 'Make Link'
3297 bl_options
= {'REGISTER', 'UNDO'}
3298 from_socket
: IntProperty()
3300 def execute(self
, context
):
3301 nodes
, links
= get_nodes_links(context
)
3303 context
.scene
.NWSourceSocket
= self
.from_socket
3305 n1
= nodes
[context
.scene
.NWLazySource
]
3306 n2
= nodes
[context
.scene
.NWLazyTarget
]
3307 if len(n2
.inputs
) > 1:
3308 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3309 elif len(n2
.inputs
) == 1:
3310 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3314 class NWAddSequence(Operator
, NWBase
, ImportHelper
):
3315 """Add an Image Sequence"""
3316 bl_idname
= 'node.nw_add_sequence'
3317 bl_label
= 'Import Image Sequence'
3318 bl_options
= {'REGISTER', 'UNDO'}
3320 directory
: StringProperty(
3323 filename
: StringProperty(
3326 files
: CollectionProperty(
3327 type=bpy
.types
.OperatorFileListElement
,
3328 options
={'HIDDEN', 'SKIP_SAVE'}
3331 def execute(self
, context
):
3332 nodes
, links
= get_nodes_links(context
)
3333 directory
= self
.directory
3334 filename
= self
.filename
3336 tree
= context
.space_data
.node_tree
3339 # print ("\nDIR:", directory)
3340 # print ("FN:", filename)
3341 # print ("Fs:", list(f.name for f in files), '\n')
3343 if tree
.type == 'SHADER':
3344 node_type
= "ShaderNodeTexImage"
3345 elif tree
.type == 'COMPOSITING':
3346 node_type
= "CompositorNodeImage"
3348 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3349 return {'CANCELLED'}
3351 if not files
[0].name
and not filename
:
3352 self
.report({'ERROR'}, "No file chosen")
3353 return {'CANCELLED'}
3354 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3355 # User has selected multiple files without an active one, or the active one is non-existant
3356 filename
= files
[0].name
3358 if not path
.exists(directory
+filename
):
3359 self
.report({'ERROR'}, filename
+" does not exist!")
3360 return {'CANCELLED'}
3362 without_ext
= '.'.join(filename
.split('.')[:-1])
3364 # if last digit isn't a number, it's not a sequence
3365 if not without_ext
[-1].isdigit():
3366 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3367 return {'CANCELLED'}
3370 extension
= filename
.split('.')[-1]
3371 reverse
= without_ext
[::-1] # reverse string
3374 for char
in reverse
:
3380 without_num
= without_ext
[:count_numbers
*-1]
3382 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3384 num_frames
= len(files
)
3386 nodes_list
= [node
for node
in nodes
]
3388 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3389 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3393 yloc
+= node_mid_pt(node
, 'y')
3394 yloc
= yloc
/len(nodes
)
3399 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3401 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3403 node
.label
= name_with_hashes
3405 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3406 img
.source
= 'SEQUENCE'
3407 img
.name
= name_with_hashes
3409 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3410 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
3411 image_user
.frame_duration
= num_frames
3416 class NWAddMultipleImages(Operator
, NWBase
, ImportHelper
):
3417 """Add multiple images at once"""
3418 bl_idname
= 'node.nw_add_multiple_images'
3419 bl_label
= 'Open Selected Images'
3420 bl_options
= {'REGISTER', 'UNDO'}
3421 directory
: StringProperty(
3424 files
: CollectionProperty(
3425 type=bpy
.types
.OperatorFileListElement
,
3426 options
={'HIDDEN', 'SKIP_SAVE'}
3429 def execute(self
, context
):
3430 nodes
, links
= get_nodes_links(context
)
3432 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3434 if context
.space_data
.node_tree
.type == 'SHADER':
3435 node_type
= "ShaderNodeTexImage"
3436 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3437 node_type
= "CompositorNodeImage"
3439 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3440 return {'CANCELLED'}
3443 for f
in self
.files
:
3446 node
= nodes
.new(node_type
)
3447 new_nodes
.append(node
)
3450 node
.width_hidden
= 100
3451 node
.location
.x
= xloc
3452 node
.location
.y
= yloc
3455 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3458 # shift new nodes up to center of tree
3459 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3461 if node
in new_nodes
:
3463 node
.location
.y
+= (list_size
/2)
3469 class NWViewerFocus(bpy
.types
.Operator
):
3470 """Set the viewer tile center to the mouse position"""
3471 bl_idname
= "node.nw_viewer_focus"
3472 bl_label
= "Viewer Focus"
3474 x
: bpy
.props
.IntProperty()
3475 y
: bpy
.props
.IntProperty()
3478 def poll(cls
, context
):
3479 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3481 def execute(self
, context
):
3484 def invoke(self
, context
, event
):
3485 render
= context
.scene
.render
3486 space
= context
.space_data
3487 percent
= render
.resolution_percentage
*0.01
3489 nodes
, links
= get_nodes_links(context
)
3490 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3493 mlocx
= event
.mouse_region_x
3494 mlocy
= event
.mouse_region_y
3495 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3497 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3498 region_x
= context
.region
.width
3499 region_y
= context
.region
.height
3501 region_center_x
= context
.region
.width
/ 2
3502 region_center_y
= context
.region
.height
/ 2
3504 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3505 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3507 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_offset
[0]
3508 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_offset
[1]
3510 margin_x
= region_center_x
- backdrop_center_x
3511 margin_y
= region_center_y
- backdrop_center_y
3513 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3514 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3516 for node
in viewers
:
3517 node
.center_x
= abs_mouse_x
3518 node
.center_y
= abs_mouse_y
3520 return {'PASS_THROUGH'}
3522 return self
.execute(context
)
3525 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3526 """Save the current viewer node to an image file"""
3527 bl_idname
= "node.nw_save_viewer"
3528 bl_label
= "Save This Image"
3529 filepath
: StringProperty(subtype
="FILE_PATH")
3530 filename_ext
: EnumProperty(
3532 description
="Choose the file format to save to",
3533 items
=(('.bmp', "PNG", ""),
3534 ('.rgb', 'IRIS', ""),
3535 ('.png', 'PNG', ""),
3536 ('.jpg', 'JPEG', ""),
3537 ('.jp2', 'JPEG2000', ""),
3538 ('.tga', 'TARGA', ""),
3539 ('.cin', 'CINEON', ""),
3540 ('.dpx', 'DPX', ""),
3541 ('.exr', 'OPEN_EXR', ""),
3542 ('.hdr', 'HDR', ""),
3543 ('.tif', 'TIFF', "")),
3548 def poll(cls
, context
):
3550 if nw_check(context
):
3551 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3552 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3553 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3557 def execute(self
, context
):
3574 basename
, ext
= path
.splitext(fp
)
3575 old_render_format
= context
.scene
.render
.image_settings
.file_format
3576 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3577 context
.area
.type = "IMAGE_EDITOR"
3578 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3579 context
.area
.spaces
[0].image
.save_render(fp
)
3580 context
.area
.type = "NODE_EDITOR"
3581 context
.scene
.render
.image_settings
.file_format
= old_render_format
3585 class NWResetNodes(bpy
.types
.Operator
):
3586 """Reset Nodes in Selection"""
3587 bl_idname
= "node.nw_reset_nodes"
3588 bl_label
= "Reset Nodes"
3589 bl_options
= {'REGISTER', 'UNDO'}
3592 def poll(cls
, context
):
3593 space
= context
.space_data
3594 return space
.type == 'NODE_EDITOR'
3596 def execute(self
, context
):
3597 node_active
= context
.active_node
3598 node_selected
= context
.selected_nodes
3599 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3601 # Check if one node is selected at least
3602 if not (len(node_selected
) > 0):
3603 self
.report({'ERROR'}, "1 node must be selected at least")
3604 return {'CANCELLED'}
3606 active_node_name
= node_active
.name
if node_active
.select
else None
3607 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3609 # Create output lists
3610 selected_node_names
= [n
.name
for n
in node_selected
]
3613 # Reset all valid children in a frame
3614 node_active_is_frame
= False
3615 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3616 node_tree
= node_active
.id_data
3617 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3619 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3620 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3621 node_active_is_frame
= True
3623 # Check if valid nodes in selection
3624 if not (len(valid_nodes
) > 0):
3625 # Check for frames only
3626 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3627 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3628 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3630 self
.report({'ERROR'}, "No valid node(s) in selection")
3631 return {'CANCELLED'}
3633 # Report nodes that are not valid
3634 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3635 valid_node_names
= [n
.name
for n
in valid_nodes
]
3636 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3637 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3639 # Deselect all nodes
3640 for i
in node_selected
:
3643 # Run through all valid nodes
3644 for node
in valid_nodes
:
3646 parent
= node
.parent
if node
.parent
else None
3647 node_loc
= [node
.location
.x
, node
.location
.y
]
3649 node_tree
= node
.id_data
3650 props_to_copy
= 'bl_idname name location height width'.split(' ')
3653 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3654 for i
in (i
for i
in mappings
if i
.is_linked
):
3656 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3658 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3660 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3661 props_to_copy
.pop(0)
3663 for prop
in props_to_copy
:
3664 setattr(new_node
, prop
, props
[prop
])
3666 nodes
= node_tree
.nodes
3668 new_node
.name
= props
['name']
3671 new_node
.parent
= parent
3672 new_node
.location
= node_loc
3674 for str_from
, str_to
in reconnections
:
3675 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3677 new_node
.select
= False
3678 success_names
.append(new_node
.name
)
3680 # Reselect all nodes
3681 if selected_node_names
and node_active_is_frame
is False:
3682 for i
in selected_node_names
:
3683 node_tree
.nodes
[i
].select
= True
3685 if active_node_name
is not None:
3686 node_tree
.nodes
[active_node_name
].select
= True
3687 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3689 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3697 def drawlayout(context
, layout
, mode
='non-panel'):
3698 tree_type
= context
.space_data
.tree_type
3700 col
= layout
.column(align
=True)
3701 col
.menu(NWMergeNodesMenu
.bl_idname
)
3704 col
= layout
.column(align
=True)
3705 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3708 if tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3709 col
= layout
.column(align
=True)
3710 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3711 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3714 col
= layout
.column(align
=True)
3715 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3716 col
.operator(NWSwapLinks
.bl_idname
)
3717 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3720 col
= layout
.column(align
=True)
3721 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3722 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3725 col
= layout
.column(align
=True)
3727 row
= col
.row(align
=True)
3728 row
.operator(NWClearLabel
.bl_idname
).option
= True
3729 row
.operator(NWModifyLabels
.bl_idname
)
3731 col
.operator(NWClearLabel
.bl_idname
).option
= True
3732 col
.operator(NWModifyLabels
.bl_idname
)
3733 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3735 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
3738 col
= layout
.column(align
=True)
3739 if tree_type
== 'CompositorNodeTree':
3740 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
3741 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
3744 col
= layout
.column(align
=True)
3745 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3748 col
= layout
.column(align
=True)
3749 col
.operator(NWAlignNodes
.bl_idname
, icon
='CENTER_ONLY')
3752 col
= layout
.column(align
=True)
3753 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
3757 class NodeWranglerPanel(Panel
, NWBase
):
3758 bl_idname
= "NODE_PT_nw_node_wrangler"
3759 bl_space_type
= 'NODE_EDITOR'
3760 bl_label
= "Node Wrangler"
3761 bl_region_type
= "UI"
3762 bl_category
= "Node Wrangler"
3764 prepend
: StringProperty(
3767 append
: StringProperty()
3768 remove
: StringProperty()
3770 def draw(self
, context
):
3771 self
.layout
.label(text
="(Quick access: Shift+W)")
3772 drawlayout(context
, self
.layout
, mode
='panel')
3778 class NodeWranglerMenu(Menu
, NWBase
):
3779 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
3780 bl_label
= "Node Wrangler"
3782 def draw(self
, context
):
3783 drawlayout(context
, self
.layout
)
3786 class NWMergeNodesMenu(Menu
, NWBase
):
3787 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
3788 bl_label
= "Merge Selected Nodes"
3790 def draw(self
, context
):
3791 type = context
.space_data
.tree_type
3792 layout
= self
.layout
3793 if type == 'ShaderNodeTree' and is_cycles_or_eevee(context
):
3794 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
3795 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
3796 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
3797 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
3799 props
.merge_type
= 'ZCOMBINE'
3800 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
3802 props
.merge_type
= 'ALPHAOVER'
3805 class NWMergeShadersMenu(Menu
, NWBase
):
3806 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
3807 bl_label
= "Merge Selected Nodes using Shaders"
3809 def draw(self
, context
):
3810 layout
= self
.layout
3811 for type in ('MIX', 'ADD'):
3812 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
3814 props
.merge_type
= 'SHADER'
3817 class NWMergeMixMenu(Menu
, NWBase
):
3818 bl_idname
= "NODE_MT_nw_merge_mix_menu"
3819 bl_label
= "Merge Selected Nodes using Mix"
3821 def draw(self
, context
):
3822 layout
= self
.layout
3823 for type, name
, description
in blend_types
:
3824 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3826 props
.merge_type
= 'MIX'
3829 class NWConnectionListOutputs(Menu
, NWBase
):
3830 bl_idname
= "NODE_MT_nw_connection_list_out"
3833 def draw(self
, context
):
3834 layout
= self
.layout
3835 nodes
, links
= get_nodes_links(context
)
3837 n1
= nodes
[context
.scene
.NWLazySource
]
3839 if n1
.type == "R_LAYERS":
3841 for o
in n1
.outputs
:
3842 if o
.enabled
: # Check which passes the render layer has enabled
3843 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3847 for o
in n1
.outputs
:
3848 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3852 class NWConnectionListInputs(Menu
, NWBase
):
3853 bl_idname
= "NODE_MT_nw_connection_list_in"
3856 def draw(self
, context
):
3857 layout
= self
.layout
3858 nodes
, links
= get_nodes_links(context
)
3860 n2
= nodes
[context
.scene
.NWLazyTarget
]
3864 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3865 op
.from_socket
= context
.scene
.NWSourceSocket
3866 op
.to_socket
= index
3870 class NWMergeMathMenu(Menu
, NWBase
):
3871 bl_idname
= "NODE_MT_nw_merge_math_menu"
3872 bl_label
= "Merge Selected Nodes using Math"
3874 def draw(self
, context
):
3875 layout
= self
.layout
3876 for type, name
, description
in operations
:
3877 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3879 props
.merge_type
= 'MATH'
3882 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3883 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3884 bl_label
= "Batch Change Selected Nodes"
3886 def draw(self
, context
):
3887 layout
= self
.layout
3888 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3889 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3892 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3893 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3894 bl_label
= "Batch Change Blend Type"
3896 def draw(self
, context
):
3897 layout
= self
.layout
3898 for type, name
, description
in blend_types
:
3899 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3900 props
.blend_type
= type
3901 props
.operation
= 'CURRENT'
3904 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3905 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3906 bl_label
= "Batch Change Math Operation"
3908 def draw(self
, context
):
3909 layout
= self
.layout
3910 for type, name
, description
in operations
:
3911 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3912 props
.blend_type
= 'CURRENT'
3913 props
.operation
= type
3916 class NWCopyToSelectedMenu(Menu
, NWBase
):
3917 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3918 bl_label
= "Copy to Selected"
3920 def draw(self
, context
):
3921 layout
= self
.layout
3922 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3923 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3926 class NWCopyLabelMenu(Menu
, NWBase
):
3927 bl_idname
= "NODE_MT_nw_copy_label_menu"
3928 bl_label
= "Copy Label"
3930 def draw(self
, context
):
3931 layout
= self
.layout
3932 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3933 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3934 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3937 class NWAddReroutesMenu(Menu
, NWBase
):
3938 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3939 bl_label
= "Add Reroutes"
3940 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3942 def draw(self
, context
):
3943 layout
= self
.layout
3944 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3945 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3946 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3949 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3950 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3951 bl_label
= "Link Active to Selected"
3953 def draw(self
, context
):
3954 layout
= self
.layout
3955 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3956 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3957 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3960 class NWLinkStandardMenu(Menu
, NWBase
):
3961 bl_idname
= "NODE_MT_nw_link_standard_menu"
3962 bl_label
= "To All Selected"
3964 def draw(self
, context
):
3965 layout
= self
.layout
3966 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3967 props
.replace
= False
3968 props
.use_node_name
= False
3969 props
.use_outputs_names
= False
3970 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3971 props
.replace
= True
3972 props
.use_node_name
= False
3973 props
.use_outputs_names
= False
3976 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3977 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3978 bl_label
= "Use Node Name/Label"
3980 def draw(self
, context
):
3981 layout
= self
.layout
3982 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3983 props
.replace
= False
3984 props
.use_node_name
= True
3985 props
.use_outputs_names
= False
3986 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3987 props
.replace
= True
3988 props
.use_node_name
= True
3989 props
.use_outputs_names
= False
3992 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3993 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
3994 bl_label
= "Use Outputs Names"
3996 def draw(self
, context
):
3997 layout
= self
.layout
3998 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3999 props
.replace
= False
4000 props
.use_node_name
= False
4001 props
.use_outputs_names
= True
4002 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4003 props
.replace
= True
4004 props
.use_node_name
= False
4005 props
.use_outputs_names
= True
4008 class NWVertColMenu(bpy
.types
.Menu
):
4009 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
4010 bl_label
= "Vertex Colors"
4013 def poll(cls
, context
):
4015 if nw_check(context
):
4016 snode
= context
.space_data
4017 valid
= snode
.tree_type
== 'ShaderNodeTree' and is_cycles_or_eevee(context
)
4020 def draw(self
, context
):
4022 nodes
, links
= get_nodes_links(context
)
4023 mat
= context
.object.active_material
4026 for obj
in bpy
.data
.objects
:
4027 for slot
in obj
.material_slots
:
4028 if slot
.material
== mat
:
4032 if obj
.data
.vertex_colors
:
4033 for vcol
in obj
.data
.vertex_colors
:
4034 vcols
.append(vcol
.name
)
4035 vcols
= list(set(vcols
)) # get a unique list
4039 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4041 l
.label(text
="No Vertex Color layers on objects with this material")
4044 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4045 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4046 bl_label
= "Switch Type to..."
4048 def draw(self
, context
):
4049 layout
= self
.layout
4050 tree
= context
.space_data
.node_tree
4051 if tree
.type == 'SHADER':
4052 if is_cycles_or_eevee(context
):
4053 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4054 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4055 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4056 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4057 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4058 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4059 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4060 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4062 layout
.menu(NWSwitchMatInputSubmenu
.bl_idname
)
4063 layout
.menu(NWSwitchMatOutputSubmenu
.bl_idname
)
4064 layout
.menu(NWSwitchMatColorSubmenu
.bl_idname
)
4065 layout
.menu(NWSwitchMatVectorSubmenu
.bl_idname
)
4066 layout
.menu(NWSwitchMatConverterSubmenu
.bl_idname
)
4067 layout
.menu(NWSwitchMatLayoutSubmenu
.bl_idname
)
4068 if tree
.type == 'COMPOSITING':
4069 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4070 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4071 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4072 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4073 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4074 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4075 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4076 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4077 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4078 if tree
.type == 'TEXTURE':
4079 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4080 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4081 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4082 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4083 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4084 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4085 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4086 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4089 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4090 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4093 def draw(self
, context
):
4094 layout
= self
.layout
4095 for ident
, node_type
, rna_name
in sorted(shaders_input_nodes_props
, key
=lambda k
: k
[2]):
4096 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4097 props
.to_type
= ident
4100 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4101 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4104 def draw(self
, context
):
4105 layout
= self
.layout
4106 for ident
, node_type
, rna_name
in sorted(shaders_output_nodes_props
, key
=lambda k
: k
[2]):
4107 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4108 props
.to_type
= ident
4111 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4112 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4115 def draw(self
, context
):
4116 layout
= self
.layout
4117 for ident
, node_type
, rna_name
in sorted(shaders_shader_nodes_props
, key
=lambda k
: k
[2]):
4118 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4119 props
.to_type
= ident
4122 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4123 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4124 bl_label
= "Texture"
4126 def draw(self
, context
):
4127 layout
= self
.layout
4128 for ident
, node_type
, rna_name
in sorted(shaders_texture_nodes_props
, key
=lambda k
: k
[2]):
4129 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4130 props
.to_type
= ident
4133 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4134 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4137 def draw(self
, context
):
4138 layout
= self
.layout
4139 for ident
, node_type
, rna_name
in sorted(shaders_color_nodes_props
, key
=lambda k
: k
[2]):
4140 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4141 props
.to_type
= ident
4144 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4145 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4148 def draw(self
, context
):
4149 layout
= self
.layout
4150 for ident
, node_type
, rna_name
in sorted(shaders_vector_nodes_props
, key
=lambda k
: k
[2]):
4151 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4152 props
.to_type
= ident
4155 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4156 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4157 bl_label
= "Converter"
4159 def draw(self
, context
):
4160 layout
= self
.layout
4161 for ident
, node_type
, rna_name
in sorted(shaders_converter_nodes_props
, key
=lambda k
: k
[2]):
4162 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4163 props
.to_type
= ident
4166 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4167 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4170 def draw(self
, context
):
4171 layout
= self
.layout
4172 for ident
, node_type
, rna_name
in sorted(shaders_layout_nodes_props
, key
=lambda k
: k
[2]):
4173 if node_type
!= 'FRAME':
4174 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4175 props
.to_type
= ident
4178 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4179 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4182 def draw(self
, context
):
4183 layout
= self
.layout
4184 for ident
, node_type
, rna_name
in sorted(compo_input_nodes_props
, key
=lambda k
: k
[2]):
4185 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4186 props
.to_type
= ident
4189 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4190 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4193 def draw(self
, context
):
4194 layout
= self
.layout
4195 for ident
, node_type
, rna_name
in sorted(compo_output_nodes_props
, key
=lambda k
: k
[2]):
4196 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4197 props
.to_type
= ident
4200 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4201 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4204 def draw(self
, context
):
4205 layout
= self
.layout
4206 for ident
, node_type
, rna_name
in sorted(compo_color_nodes_props
, key
=lambda k
: k
[2]):
4207 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4208 props
.to_type
= ident
4211 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4212 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4213 bl_label
= "Converter"
4215 def draw(self
, context
):
4216 layout
= self
.layout
4217 for ident
, node_type
, rna_name
in sorted(compo_converter_nodes_props
, key
=lambda k
: k
[2]):
4218 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4219 props
.to_type
= ident
4222 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4223 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4226 def draw(self
, context
):
4227 layout
= self
.layout
4228 for ident
, node_type
, rna_name
in sorted(compo_filter_nodes_props
, key
=lambda k
: k
[2]):
4229 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4230 props
.to_type
= ident
4233 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4234 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4237 def draw(self
, context
):
4238 layout
= self
.layout
4239 for ident
, node_type
, rna_name
in sorted(compo_vector_nodes_props
, key
=lambda k
: k
[2]):
4240 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4241 props
.to_type
= ident
4244 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4245 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4248 def draw(self
, context
):
4249 layout
= self
.layout
4250 for ident
, node_type
, rna_name
in sorted(compo_matte_nodes_props
, key
=lambda k
: k
[2]):
4251 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4252 props
.to_type
= ident
4255 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4256 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4257 bl_label
= "Distort"
4259 def draw(self
, context
):
4260 layout
= self
.layout
4261 for ident
, node_type
, rna_name
in sorted(compo_distort_nodes_props
, key
=lambda k
: k
[2]):
4262 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4263 props
.to_type
= ident
4266 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4267 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4270 def draw(self
, context
):
4271 layout
= self
.layout
4272 for ident
, node_type
, rna_name
in sorted(compo_layout_nodes_props
, key
=lambda k
: k
[2]):
4273 if node_type
!= 'FRAME':
4274 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4275 props
.to_type
= ident
4278 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4279 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4282 def draw(self
, context
):
4283 layout
= self
.layout
4284 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4285 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4286 props
.to_type
= ident
4289 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4290 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4293 def draw(self
, context
):
4294 layout
= self
.layout
4295 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4296 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4297 props
.to_type
= ident
4300 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4301 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4304 def draw(self
, context
):
4305 layout
= self
.layout
4306 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4307 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4308 props
.to_type
= ident
4311 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4312 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4315 def draw(self
, context
):
4316 layout
= self
.layout
4317 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4318 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4319 props
.to_type
= ident
4322 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4323 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4324 bl_label
= "Converter"
4326 def draw(self
, context
):
4327 layout
= self
.layout
4328 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4329 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4330 props
.to_type
= ident
4333 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4334 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4337 def draw(self
, context
):
4338 layout
= self
.layout
4339 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4340 if node_type
!= 'FRAME':
4341 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4342 props
.to_type
= ident
4345 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4346 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4349 def draw(self
, context
):
4350 layout
= self
.layout
4351 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4352 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4353 props
.to_type
= ident
4356 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4357 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4360 def draw(self
, context
):
4361 layout
= self
.layout
4362 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4363 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4364 props
.to_type
= ident
4367 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4368 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4371 def draw(self
, context
):
4372 layout
= self
.layout
4373 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4374 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4375 props
.to_type
= ident
4378 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4379 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4380 bl_label
= "Pattern"
4382 def draw(self
, context
):
4383 layout
= self
.layout
4384 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4385 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4386 props
.to_type
= ident
4389 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4390 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4391 bl_label
= "Textures"
4393 def draw(self
, context
):
4394 layout
= self
.layout
4395 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4396 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4397 props
.to_type
= ident
4400 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4401 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4402 bl_label
= "Converter"
4404 def draw(self
, context
):
4405 layout
= self
.layout
4406 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4407 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4408 props
.to_type
= ident
4411 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4412 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4413 bl_label
= "Distort"
4415 def draw(self
, context
):
4416 layout
= self
.layout
4417 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4418 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4419 props
.to_type
= ident
4422 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4423 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4426 def draw(self
, context
):
4427 layout
= self
.layout
4428 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4429 if node_type
!= 'FRAME':
4430 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4431 props
.to_type
= ident
4435 # APPENDAGES TO EXISTING UI
4439 def select_parent_children_buttons(self
, context
):
4440 layout
= self
.layout
4441 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4442 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4445 def attr_nodes_menu_func(self
, context
):
4446 col
= self
.layout
.column(align
=True)
4447 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4451 def multipleimages_menu_func(self
, context
):
4452 col
= self
.layout
.column(align
=True)
4453 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4454 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4458 def bgreset_menu_func(self
, context
):
4459 self
.layout
.operator(NWResetBG
.bl_idname
)
4462 def save_viewer_menu_func(self
, context
):
4463 if nw_check(context
):
4464 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4465 if context
.scene
.node_tree
.nodes
.active
:
4466 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4467 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4470 def reset_nodes_button(self
, context
):
4471 node_active
= context
.active_node
4472 node_selected
= context
.selected_nodes
4473 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4475 # Check if active node is in the selection and respective type
4476 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4477 row
= self
.layout
.row()
4478 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4479 self
.layout
.separator()
4481 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4482 row
= self
.layout
.row()
4483 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4484 self
.layout
.separator()
4488 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4492 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4493 # props entry: (property name, property value)
4496 # NWMergeNodes with Ctrl (AUTO).
4497 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4498 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4499 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4500 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4501 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4502 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4503 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4504 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4505 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4506 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4507 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4508 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4509 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4510 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4511 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4512 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4513 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4514 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4515 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4516 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4517 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4518 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4519 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4520 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4521 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4522 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4523 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4524 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4525 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4526 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4527 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4528 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4529 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4530 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4531 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4532 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4533 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4534 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4535 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4536 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4537 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4538 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4539 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4540 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4541 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4542 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4543 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4544 # NWMergeNodes with Ctrl Shift (MATH)
4545 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4546 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4547 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4548 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4549 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4550 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4551 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4552 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4553 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4554 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4555 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4556 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4557 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4558 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4559 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4560 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4561 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4562 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4563 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4564 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4565 # BATCH CHANGE NODES
4566 # NWBatchChangeNodes with Alt
4567 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4568 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4569 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4570 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4571 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4572 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4573 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4574 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4575 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4576 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4577 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4578 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4579 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4580 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4581 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4582 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4583 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4584 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4585 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4586 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4587 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4588 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4589 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4590 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4591 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4592 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4593 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4594 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4595 # LINK ACTIVE TO SELECTED
4596 # Don't use names, don't replace links (K)
4597 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4598 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4599 # Don't use names, replace links (Shift K)
4600 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4601 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4602 # Use node name, don't replace links (')
4603 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4604 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4605 # Use node name, replace links (Shift ')
4606 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4607 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4608 # Don't use names, don't replace links (;)
4609 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4610 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4611 # Don't use names, replace links (')
4612 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4613 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4615 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4616 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4617 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4618 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4619 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4620 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4621 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4622 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4623 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4624 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4625 # CLEAR LABEL (Alt L)
4626 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4627 # MODIFY LABEL (Alt Shift L)
4628 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4629 # Copy Label from active to selected
4630 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4631 # DETACH OUTPUTS (Alt Shift D)
4632 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4633 # LINK TO OUTPUT NODE (O)
4634 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4635 # SELECT PARENT/CHILDREN
4637 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4639 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4641 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4642 # Add Principled BSDF Texture Setup
4643 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4645 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4647 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4649 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4651 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4653 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4655 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4657 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, None, "Lazy Mix"),
4659 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, (('with_menu', False),), "Lazy Connect"),
4660 # Lazy Connect with Menu
4661 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, True, True, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4662 # Viewer Tile Center
4663 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4665 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4666 # Reset Nodes (Back Space)
4667 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4669 ('wm.call_menu', 'W', 'PRESS', False, True, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wrangler menu"),
4670 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4671 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4672 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4673 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4674 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4679 NWPrincipledPreferences
,
4699 NWAddPrincipledSetup
,
4701 NWLinkActiveToSelected
,
4703 NWSelectParentChildren
,
4709 NWAddMultipleImages
,
4718 NWConnectionListOutputs
,
4719 NWConnectionListInputs
,
4721 NWBatchChangeNodesMenu
,
4722 NWBatchChangeBlendTypeMenu
,
4723 NWBatchChangeOperationMenu
,
4724 NWCopyToSelectedMenu
,
4727 NWLinkActiveToSelectedMenu
,
4729 NWLinkUseNodeNameMenu
,
4730 NWLinkUseOutputsNamesMenu
,
4732 NWSwitchNodeTypeMenu
,
4733 NWSwitchShadersInputSubmenu
,
4734 NWSwitchShadersOutputSubmenu
,
4735 NWSwitchShadersShaderSubmenu
,
4736 NWSwitchShadersTextureSubmenu
,
4737 NWSwitchShadersColorSubmenu
,
4738 NWSwitchShadersVectorSubmenu
,
4739 NWSwitchShadersConverterSubmenu
,
4740 NWSwitchShadersLayoutSubmenu
,
4741 NWSwitchCompoInputSubmenu
,
4742 NWSwitchCompoOutputSubmenu
,
4743 NWSwitchCompoColorSubmenu
,
4744 NWSwitchCompoConverterSubmenu
,
4745 NWSwitchCompoFilterSubmenu
,
4746 NWSwitchCompoVectorSubmenu
,
4747 NWSwitchCompoMatteSubmenu
,
4748 NWSwitchCompoDistortSubmenu
,
4749 NWSwitchCompoLayoutSubmenu
,
4750 NWSwitchMatInputSubmenu
,
4751 NWSwitchMatOutputSubmenu
,
4752 NWSwitchMatColorSubmenu
,
4753 NWSwitchMatVectorSubmenu
,
4754 NWSwitchMatConverterSubmenu
,
4755 NWSwitchMatLayoutSubmenu
,
4756 NWSwitchTexInputSubmenu
,
4757 NWSwitchTexOutputSubmenu
,
4758 NWSwitchTexColorSubmenu
,
4759 NWSwitchTexPatternSubmenu
,
4760 NWSwitchTexTexturesSubmenu
,
4761 NWSwitchTexConverterSubmenu
,
4762 NWSwitchTexDistortSubmenu
,
4763 NWSwitchTexLayoutSubmenu
,
4767 from bpy
.utils
import register_class
4770 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4771 name
="Busy Drawing!",
4773 description
="An internal property used to store only the first mouse position")
4774 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4775 name
="Lazy Source!",
4777 description
="An internal property used to store the first node in a Lazy Connect operation")
4778 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
4779 name
="Lazy Target!",
4781 description
="An internal property used to store the last node in a Lazy Connect operation")
4782 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
4783 name
="Source Socket!",
4785 description
="An internal property used to store the source socket in a Lazy Connect operation")
4791 addon_keymaps
.clear()
4792 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
4794 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
4795 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
4796 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
4798 for prop
, value
in props
:
4799 setattr(kmi
.properties
, prop
, value
)
4800 addon_keymaps
.append((km
, kmi
))
4803 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
4804 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4805 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
4806 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
4807 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4808 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4809 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
4810 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
4814 from bpy
.utils
import unregister_class
4817 del bpy
.types
.Scene
.NWBusyDrawing
4818 del bpy
.types
.Scene
.NWLazySource
4819 del bpy
.types
.Scene
.NWLazyTarget
4820 del bpy
.types
.Scene
.NWSourceSocket
4823 for km
, kmi
in addon_keymaps
:
4824 km
.keymap_items
.remove(kmi
)
4825 addon_keymaps
.clear()
4828 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
4829 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4830 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
4831 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
4832 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4833 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4834 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
4835 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
4838 unregister_class(cls
)
4840 if __name__
== "__main__":