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, 83, 0),
24 "location": "Node Editor Toolbar or Shift-W",
25 "description": "Various tools to enhance and speed up node-based workflow",
27 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_wrangler.html",
33 from bpy
.types
import Operator
, Panel
, Menu
34 from bpy
.props
import (
43 from bpy_extras
.io_utils
import ImportHelper
, ExportHelper
44 from gpu_extras
.batch
import batch_for_shader
45 from mathutils
import Vector
46 from math
import cos
, sin
, pi
, hypot
50 from itertools
import chain
52 from collections
import namedtuple
56 # list of outputs of Input Render Layer
57 # with attributes determinig if pass is used,
58 # and MultiLayer EXR outputs names and corresponding render engines
60 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_eevee, in_cycles)
61 RL_entry
= namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_eevee', 'in_cycles'])
63 RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
64 RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
65 RL_entry('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
66 RL_entry('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
67 RL_entry('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
68 RL_entry('use_pass_emit', 'Emit', 'Emit', False, True),
69 RL_entry('use_pass_environment', 'Environment', 'Env', False, False),
70 RL_entry('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
71 RL_entry('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
72 RL_entry('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
73 RL_entry('use_pass_indirect', 'Indirect', 'Indirect', False, False),
74 RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', False, True),
75 RL_entry('use_pass_mist', 'Mist', 'Mist', True, True),
76 RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
77 RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', False, True),
78 RL_entry('use_pass_shadow', 'Shadow', 'Shadow', False, True),
79 RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', True, True),
80 RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', True, True),
81 RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
82 RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
83 RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
84 RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
85 RL_entry('use_pass_uv', 'UV', 'UV', True, True),
86 RL_entry('use_pass_vector', 'Speed', 'Vector', False, True),
87 RL_entry('use_pass_z', 'Z', 'Depth', True, True),
91 # (rna_type.identifier, type, rna_type.name)
92 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
93 # Keeping things in alphabetical orde so we don't need to sort later.
94 shaders_input_nodes_props
= (
95 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
96 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
97 ('ShaderNodeBevel', 'BEVEL', 'Bevel'),
98 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
99 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
100 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
101 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
102 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
103 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
104 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
105 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
106 ('ShaderNodeRGB', 'RGB', 'RGB'),
107 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
108 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
109 ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
110 ('ShaderNodeValue', 'VALUE', 'Value'),
111 ('ShaderNodeVertexColor', 'VERTEX_COLOR', 'Vertex Color'),
112 ('ShaderNodeVolumeInfo', 'VOLUME_INFO', 'Volume Info'),
113 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
116 # (rna_type.identifier, type, rna_type.name)
117 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
118 # Keeping things in alphabetical orde so we don't need to sort later.
119 shaders_output_nodes_props
= (
120 ('ShaderNodeOutputAOV', 'OUTPUT_AOV', 'AOV Output'),
121 ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
122 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
123 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
125 # (rna_type.identifier, type, rna_type.name)
126 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
127 # Keeping things in alphabetical orde so we don't need to sort later.
128 shaders_shader_nodes_props
= (
129 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
130 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
131 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
132 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
133 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
134 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
135 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
136 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
137 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
138 ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
139 ('ShaderNodeBsdfHairPrincipled', 'BSDF_HAIR_PRINCIPLED', 'Principled Hair BSDF'),
140 ('ShaderNodeVolumePrincipled', 'PRINCIPLED_VOLUME', 'Principled Volume'),
141 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
142 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
143 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
144 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
145 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
146 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
147 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
148 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
149 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
151 # (rna_type.identifier, type, rna_type.name)
152 # Keeping things in alphabetical orde so we don't need to sort later.
153 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
154 shaders_texture_nodes_props
= (
155 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
156 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
157 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
158 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
159 ('ShaderNodeTexIES', 'TEX_IES', 'IES Texture'),
160 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
161 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
162 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
163 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
164 ('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
165 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
166 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
167 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
168 ('ShaderNodeTexWhiteNoise', 'TEX_WHITE_NOISE', 'White Noise'),
170 # (rna_type.identifier, type, rna_type.name)
171 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
172 # Keeping things in alphabetical orde so we don't need to sort later.
173 shaders_color_nodes_props
= (
174 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
175 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
176 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
177 ('ShaderNodeInvert', 'INVERT', 'Invert'),
178 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
179 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
180 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
182 # (rna_type.identifier, type, rna_type.name)
183 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
184 # Keeping things in alphabetical orde so we don't need to sort later.
185 shaders_vector_nodes_props
= (
186 ('ShaderNodeBump', 'BUMP', 'Bump'),
187 ('ShaderNodeDisplacement', 'DISPLACEMENT', 'Displacement'),
188 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
189 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
190 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
191 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
192 ('ShaderNodeVectorDisplacement', 'VECTOR_DISPLACEMENT', 'Vector Displacement'),
193 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
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 # Keeping things in alphabetical orde so we don't need to sort later.
198 shaders_converter_nodes_props
= (
199 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
200 ('ShaderNodeClamp', 'CLAMP', 'Clamp'),
201 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
202 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
203 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
204 ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
205 ('ShaderNodeMapRange', 'MAP_RANGE', 'Map Range'),
206 ('ShaderNodeMath', 'MATH', 'Math'),
207 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
208 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
209 ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
210 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
211 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
212 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
214 # (rna_type.identifier, type, rna_type.name)
215 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
216 # Keeping things in alphabetical orde so we don't need to sort later.
217 shaders_layout_nodes_props
= (
218 ('NodeFrame', 'FRAME', 'Frame'),
219 ('NodeReroute', 'REROUTE', 'Reroute'),
223 # (rna_type.identifier, type, rna_type.name)
224 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
225 # Keeping things in alphabetical orde so we don't need to sort later.
226 compo_input_nodes_props
= (
227 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
228 ('CompositorNodeImage', 'IMAGE', 'Image'),
229 ('CompositorNodeMask', 'MASK', 'Mask'),
230 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
231 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
232 ('CompositorNodeRGB', 'RGB', 'RGB'),
233 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
234 ('CompositorNodeTime', 'TIME', 'Time'),
235 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
236 ('CompositorNodeValue', 'VALUE', 'Value'),
238 # (rna_type.identifier, type, rna_type.name)
239 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
240 # Keeping things in alphabetical orde so we don't need to sort later.
241 compo_output_nodes_props
= (
242 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
243 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
244 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
245 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
246 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
248 # (rna_type.identifier, type, rna_type.name)
249 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
250 # Keeping things in alphabetical orde so we don't need to sort later.
251 compo_color_nodes_props
= (
252 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
253 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
254 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
255 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
256 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
257 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
258 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
259 ('CompositorNodeInvert', 'INVERT', 'Invert'),
260 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
261 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
262 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
263 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
265 # (rna_type.identifier, type, rna_type.name)
266 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
267 # Keeping things in alphabetical orde so we don't need to sort later.
268 compo_converter_nodes_props
= (
269 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
270 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
271 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
272 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
273 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
274 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
275 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
276 ('CompositorNodeMath', 'MATH', 'Math'),
277 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
278 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
279 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
280 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
281 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
282 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
283 ('CompositorNodeSwitchView', 'VIEWSWITCH', 'View Switch'),
285 # (rna_type.identifier, type, rna_type.name)
286 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
287 # Keeping things in alphabetical orde so we don't need to sort later.
288 compo_filter_nodes_props
= (
289 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
290 ('CompositorNodeBlur', 'BLUR', 'Blur'),
291 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
292 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
293 ('CompositorNodeDenoise', 'DENOISE', 'Denoise'),
294 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
295 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
296 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
297 ('CompositorNodeFilter', 'FILTER', 'Filter'),
298 ('CompositorNodeGlare', 'GLARE', 'Glare'),
299 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
300 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
301 ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
302 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
304 # (rna_type.identifier, type, rna_type.name)
305 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
306 # Keeping things in alphabetical orde so we don't need to sort later.
307 compo_vector_nodes_props
= (
308 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
309 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
310 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
311 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
312 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
314 # (rna_type.identifier, type, rna_type.name)
315 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
316 # Keeping things in alphabetical orde so we don't need to sort later.
317 compo_matte_nodes_props
= (
318 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
319 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
320 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
321 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
322 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
323 ('CompositorNodeCryptomatte', 'CRYPTOMATTE', 'Cryptomatte'),
324 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
325 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
326 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
327 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
328 ('CompositorNodeKeying', 'KEYING', 'Keying'),
329 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
330 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
332 # (rna_type.identifier, type, rna_type.name)
333 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
334 # Keeping things in alphabetical orde so we don't need to sort later.
335 compo_distort_nodes_props
= (
336 ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
337 ('CompositorNodeCrop', 'CROP', 'Crop'),
338 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
339 ('CompositorNodeFlip', 'FLIP', 'Flip'),
340 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
341 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
342 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
343 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
344 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
345 ('CompositorNodeScale', 'SCALE', 'Scale'),
346 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
347 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
348 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
350 # (rna_type.identifier, type, rna_type.name)
351 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
352 # Keeping things in alphabetical orde so we don't need to sort later.
353 compo_layout_nodes_props
= (
354 ('NodeFrame', 'FRAME', 'Frame'),
355 ('NodeReroute', 'REROUTE', 'Reroute'),
356 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
358 # Blender Render material nodes
359 # (rna_type.identifier, type, rna_type.name)
360 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
361 blender_mat_input_nodes_props
= (
362 ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
363 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
364 ('ShaderNodeLightData', 'LIGHT', 'Light Data'),
365 ('ShaderNodeValue', 'VALUE', 'Value'),
366 ('ShaderNodeRGB', 'RGB', 'RGB'),
367 ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
368 ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
369 ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
372 # (rna_type.identifier, type, rna_type.name)
373 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
374 blender_mat_output_nodes_props
= (
375 ('ShaderNodeOutput', 'OUTPUT', 'Output'),
378 # (rna_type.identifier, type, rna_type.name)
379 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
380 blender_mat_color_nodes_props
= (
381 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
382 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
383 ('ShaderNodeInvert', 'INVERT', 'Invert'),
384 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
387 # (rna_type.identifier, type, rna_type.name)
388 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
389 blender_mat_vector_nodes_props
= (
390 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
391 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
392 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
395 # (rna_type.identifier, type, rna_type.name)
396 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
397 blender_mat_converter_nodes_props
= (
398 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
399 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
400 ('ShaderNodeMath', 'MATH', 'Math'),
401 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
402 ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
403 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
404 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
405 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
406 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
409 # (rna_type.identifier, type, rna_type.name)
410 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
411 blender_mat_layout_nodes_props
= (
412 ('NodeReroute', 'REROUTE', 'Reroute'),
416 # (rna_type.identifier, type, rna_type.name)
417 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
418 texture_input_nodes_props
= (
419 ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
420 ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
421 ('TextureNodeTexture', 'TEXTURE', 'Texture'),
422 ('TextureNodeImage', 'IMAGE', 'Image'),
425 # (rna_type.identifier, type, rna_type.name)
426 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
427 texture_output_nodes_props
= (
428 ('TextureNodeOutput', 'OUTPUT', 'Output'),
429 ('TextureNodeViewer', 'VIEWER', 'Viewer'),
432 # (rna_type.identifier, type, rna_type.name)
433 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
434 texture_color_nodes_props
= (
435 ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
436 ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
437 ('TextureNodeInvert', 'INVERT', 'Invert'),
438 ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
439 ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
440 ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
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_pattern_nodes_props
= (
446 ('TextureNodeChecker', 'CHECKER', 'Checker'),
447 ('TextureNodeBricks', 'BRICKS', 'Bricks'),
450 # (rna_type.identifier, type, rna_type.name)
451 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
452 texture_textures_nodes_props
= (
453 ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
454 ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
455 ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
456 ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
457 ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
458 ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
459 ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
460 ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
461 ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
462 ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
465 # (rna_type.identifier, type, rna_type.name)
466 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
467 texture_converter_nodes_props
= (
468 ('TextureNodeMath', 'MATH', 'Math'),
469 ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
470 ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
471 ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
472 ('TextureNodeDistance', 'DISTANCE', 'Distance'),
475 # (rna_type.identifier, type, rna_type.name)
476 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
477 texture_distort_nodes_props
= (
478 ('TextureNodeScale', 'SCALE', 'Scale'),
479 ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
480 ('TextureNodeRotate', 'ROTATE', 'Rotate'),
481 ('TextureNodeAt', 'AT', 'At'),
484 # (rna_type.identifier, type, rna_type.name)
485 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
486 texture_layout_nodes_props
= (
487 ('NodeReroute', 'REROUTE', 'Reroute'),
490 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
491 # used list, not tuple for easy merging with other lists.
493 ('MIX', 'Mix', 'Mix Mode'),
494 ('ADD', 'Add', 'Add Mode'),
495 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
496 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
497 ('SCREEN', 'Screen', 'Screen Mode'),
498 ('DIVIDE', 'Divide', 'Divide Mode'),
499 ('DIFFERENCE', 'Difference', 'Difference Mode'),
500 ('DARKEN', 'Darken', 'Darken Mode'),
501 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
502 ('OVERLAY', 'Overlay', 'Overlay Mode'),
503 ('DODGE', 'Dodge', 'Dodge Mode'),
504 ('BURN', 'Burn', 'Burn Mode'),
505 ('HUE', 'Hue', 'Hue Mode'),
506 ('SATURATION', 'Saturation', 'Saturation Mode'),
507 ('VALUE', 'Value', 'Value Mode'),
508 ('COLOR', 'Color', 'Color Mode'),
509 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
510 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
513 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
514 # used list, not tuple for easy merging with other lists.
516 ('ADD', 'Add', 'Add Mode'),
517 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
518 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
519 ('DIVIDE', 'Divide', 'Divide Mode'),
520 ('MULTIPLY_ADD', 'Multiply Add', 'Multiply Add Mode'),
521 ('SINE', 'Sine', 'Sine Mode'),
522 ('COSINE', 'Cosine', 'Cosine Mode'),
523 ('TANGENT', 'Tangent', 'Tangent Mode'),
524 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
525 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
526 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
527 ('ARCTAN2', 'Arctan2', 'Arctan2 Mode'),
528 ('SINH', 'Hyperbolic Sine', 'Hyperbolic Sine Mode'),
529 ('COSH', 'Hyperbolic Cosine', 'Hyperbolic Cosine Mode'),
530 ('TANH', 'Hyperbolic Tangent', 'Hyperbolic Tangent Mode'),
531 ('POWER', 'Power', 'Power Mode'),
532 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
533 ('SQRT', 'Square Root', 'Square Root Mode'),
534 ('INVERSE_SQRT', 'Inverse Square Root', 'Inverse Square Root Mode'),
535 ('EXPONENT', 'Exponent', 'Exponent Mode'),
536 ('MINIMUM', 'Minimum', 'Minimum Mode'),
537 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
538 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
539 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
540 ('SIGN', 'Sign', 'Sign Mode'),
541 ('COMPARE', 'Compare', 'Compare Mode'),
542 ('SMOOTH_MIN', 'Smooth Minimum', 'Smooth Minimum Mode'),
543 ('SMOOTH_MAX', 'Smooth Maximum', 'Smooth Maximum Mode'),
544 ('FRACT', 'Fraction', 'Fraction Mode'),
545 ('MODULO', 'Modulo', 'Modulo Mode'),
546 ('SNAP', 'Snap', 'Snap Mode'),
547 ('WRAP', 'Wrap', 'Wrap Mode'),
548 ('PINGPONG', 'Pingpong', 'Pingpong Mode'),
549 ('ABSOLUTE', 'Absolute', 'Absolute Mode'),
550 ('ROUND', 'Round', 'Round Mode'),
551 ('FLOOR', 'Floor', 'Floor Mode'),
552 ('CEIL', 'Ceil', 'Ceil Mode'),
553 ('TRUNCATE', 'Truncate', 'Truncate Mode'),
554 ('RADIANS', 'To Radians', 'To Radians Mode'),
555 ('DEGREES', 'To Degrees', 'To Degrees Mode'),
558 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
559 # used list, not tuple for easy merging with other lists.
561 ('CURRENT', 'Current', 'Leave at current state'),
562 ('NEXT', 'Next', 'Next blend type/operation'),
563 ('PREV', 'Prev', 'Previous blend type/operation'),
568 (1.0, 1.0, 1.0, 0.7),
569 (1.0, 0.0, 0.0, 0.7),
573 (0.0, 0.0, 0.0, 1.0),
574 (0.38, 0.77, 0.38, 1.0),
575 (0.38, 0.77, 0.38, 1.0)
578 (0.0, 0.0, 0.0, 1.0),
579 (0.77, 0.77, 0.16, 1.0),
580 (0.77, 0.77, 0.16, 1.0)
583 (0.0, 0.0, 0.0, 1.0),
584 (0.38, 0.38, 0.77, 1.0),
585 (0.38, 0.38, 0.77, 1.0)
588 (0.0, 0.0, 0.0, 1.0),
589 (0.63, 0.63, 0.63, 1.0),
590 (0.63, 0.63, 0.63, 1.0)
593 (1.0, 1.0, 1.0, 0.7),
594 (0.0, 0.0, 0.0, 0.7),
599 viewer_socket_name
= "tmp_viewer"
601 def is_visible_socket(socket
):
602 return not socket
.hide
and socket
.enabled
and socket
.type != 'CUSTOM'
604 def nice_hotkey_name(punc
):
605 # convert the ugly string name into the actual character
607 ('LEFTMOUSE', "LMB"),
608 ('MIDDLEMOUSE', "MMB"),
609 ('RIGHTMOUSE', "RMB"),
610 ('WHEELUPMOUSE', "Wheel Up"),
611 ('WHEELDOWNMOUSE', "Wheel Down"),
612 ('WHEELINMOUSE', "Wheel In"),
613 ('WHEELOUTMOUSE', "Wheel Out"),
626 ('LINE_FEED', "Enter"),
633 ('BACK_SLASH', "\\"),
635 ('NUMPAD_1', "Numpad 1"),
636 ('NUMPAD_2', "Numpad 2"),
637 ('NUMPAD_3', "Numpad 3"),
638 ('NUMPAD_4', "Numpad 4"),
639 ('NUMPAD_5', "Numpad 5"),
640 ('NUMPAD_6', "Numpad 6"),
641 ('NUMPAD_7', "Numpad 7"),
642 ('NUMPAD_8', "Numpad 8"),
643 ('NUMPAD_9', "Numpad 9"),
644 ('NUMPAD_0', "Numpad 0"),
645 ('NUMPAD_PERIOD', "Numpad ."),
646 ('NUMPAD_SLASH', "Numpad /"),
647 ('NUMPAD_ASTERIX', "Numpad *"),
648 ('NUMPAD_MINUS', "Numpad -"),
649 ('NUMPAD_ENTER', "Numpad Enter"),
650 ('NUMPAD_PLUS', "Numpad +"),
653 for (ugly
, nice
) in pairs
:
658 nice_punc
= punc
.replace("_", " ").title()
662 def force_update(context
):
663 context
.space_data
.node_tree
.update_tag()
667 prefs
= bpy
.context
.preferences
.system
668 return prefs
.dpi
* prefs
.pixel_size
/ 72
671 def node_mid_pt(node
, axis
):
673 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
675 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
681 def autolink(node1
, node2
, links
):
684 for outp
in node1
.outputs
:
685 for inp
in node2
.inputs
:
686 if not inp
.is_linked
and inp
.name
== outp
.name
:
691 for outp
in node1
.outputs
:
692 for inp
in node2
.inputs
:
693 if not inp
.is_linked
and inp
.type == outp
.type:
698 # force some connection even if the type doesn't match
699 for outp
in node1
.outputs
:
700 for inp
in node2
.inputs
:
701 if not inp
.is_linked
:
706 # even if no sockets are open, force one of matching type
707 for outp
in node1
.outputs
:
708 for inp
in node2
.inputs
:
709 if inp
.type == outp
.type:
715 for outp
in node1
.outputs
:
716 for inp
in node2
.inputs
:
721 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
725 def node_at_pos(nodes
, context
, event
):
726 nodes_near_mouse
= []
727 nodes_under_mouse
= []
730 store_mouse_cursor(context
, event
)
731 x
, y
= context
.space_data
.cursor_location
735 # Make a list of each corner (and middle of border) for each node.
736 # Will be sorted to find nearest point and thus nearest node
737 node_points_with_dist
= []
740 if node
.type != 'FRAME': # no point trying to link to a frame node
741 locx
= node
.location
.x
742 locy
= node
.location
.y
743 dimx
= node
.dimensions
.x
/dpifac()
744 dimy
= node
.dimensions
.y
/dpifac()
746 locx
+= node
.parent
.location
.x
747 locy
+= node
.parent
.location
.y
748 if node
.parent
.parent
:
749 locx
+= node
.parent
.parent
.location
.x
750 locy
+= node
.parent
.parent
.location
.y
751 if node
.parent
.parent
.parent
:
752 locx
+= node
.parent
.parent
.parent
.location
.x
753 locy
+= node
.parent
.parent
.parent
.location
.y
754 if node
.parent
.parent
.parent
.parent
:
755 # Support three levels or parenting
756 # There's got to be a better way to do this...
759 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- locy
)]) # Top Left
760 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- locy
)]) # Top Right
761 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- dimy
))]) # Bottom Left
762 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- dimy
))]) # Bottom Right
764 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- locy
)]) # Mid Top
765 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- (locy
- dimy
))]) # Mid Bottom
766 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- (dimy
/ 2)))]) # Mid Left
767 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- (dimy
/ 2)))]) # Mid Right
769 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
772 if node
.type != 'FRAME' and skipnode
== False:
773 locx
= node
.location
.x
774 locy
= node
.location
.y
775 dimx
= node
.dimensions
.x
/dpifac()
776 dimy
= node
.dimensions
.y
/dpifac()
778 locx
+= node
.parent
.location
.x
779 locy
+= node
.parent
.location
.y
780 if (locx
<= x
<= locx
+ dimx
) and \
781 (locy
- dimy
<= y
<= locy
):
782 nodes_under_mouse
.append(node
)
784 if len(nodes_under_mouse
) == 1:
785 if nodes_under_mouse
[0] != nearest_node
:
786 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
788 target_node
= nearest_node
# else use the nearest node
790 target_node
= nearest_node
794 def store_mouse_cursor(context
, event
):
795 space
= context
.space_data
796 v2d
= context
.region
.view2d
797 tree
= space
.edit_tree
799 # convert mouse position to the View2D for later node placement
800 if context
.region
.type == 'WINDOW':
801 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
803 space
.cursor_location
= tree
.view_center
805 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=(1.0, 1.0, 1.0, 0.7)):
806 shader
= gpu
.shader
.from_builtin('2D_SMOOTH_COLOR')
808 vertices
= ((x1
, y1
), (x2
, y2
))
809 vertex_colors
= ((colour
[0]+(1.0-colour
[0])/4,
810 colour
[1]+(1.0-colour
[1])/4,
811 colour
[2]+(1.0-colour
[2])/4,
812 colour
[3]+(1.0-colour
[3])/4),
815 batch
= batch_for_shader(shader
, 'LINE_STRIP', {"pos": vertices
, "color": vertex_colors
})
816 bgl
.glLineWidth(size
* dpifac())
822 def draw_circle_2d_filled(shader
, mx
, my
, radius
, colour
=(1.0, 1.0, 1.0, 0.7)):
823 radius
= radius
* dpifac()
825 vertices
= [(radius
* cos(i
* 2 * pi
/ sides
) + mx
,
826 radius
* sin(i
* 2 * pi
/ sides
) + my
)
827 for i
in range(sides
+ 1)]
829 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
831 shader
.uniform_float("color", colour
)
834 def draw_rounded_node_border(shader
, node
, radius
=8, colour
=(1.0, 1.0, 1.0, 0.7)):
835 area_width
= bpy
.context
.area
.width
- (16*dpifac()) - 1
836 bottom_bar
= (16*dpifac()) + 1
838 radius
= radius
*dpifac()
840 nlocx
= (node
.location
.x
+1)*dpifac()
841 nlocy
= (node
.location
.y
+1)*dpifac()
842 ndimx
= node
.dimensions
.x
843 ndimy
= node
.dimensions
.y
844 # This is a stupid way to do this... TODO use while loop
846 nlocx
+= node
.parent
.location
.x
847 nlocy
+= node
.parent
.location
.y
848 if node
.parent
.parent
:
849 nlocx
+= node
.parent
.parent
.location
.x
850 nlocy
+= node
.parent
.parent
.location
.y
851 if node
.parent
.parent
.parent
:
852 nlocx
+= node
.parent
.parent
.parent
.location
.x
853 nlocy
+= node
.parent
.parent
.parent
.location
.y
858 if node
.type == 'REROUTE':
866 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
868 for i
in range(sides
+1):
870 if my
> bottom_bar
and mx
< area_width
:
871 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
872 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
873 vertices
.append((cosine
,sine
))
874 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
876 shader
.uniform_float("color", colour
)
880 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
882 for i
in range(sides
+1):
884 if my
> bottom_bar
and mx
< area_width
:
885 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
886 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
887 vertices
.append((cosine
,sine
))
888 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
890 shader
.uniform_float("color", colour
)
894 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
896 for i
in range(sides
+1):
898 if my
> bottom_bar
and mx
< area_width
:
899 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
900 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
901 vertices
.append((cosine
,sine
))
902 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
904 shader
.uniform_float("color", colour
)
907 # Bottom right corner
908 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
910 for i
in range(sides
+1):
912 if my
> bottom_bar
and mx
< area_width
:
913 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
914 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
915 vertices
.append((cosine
,sine
))
916 batch
= batch_for_shader(shader
, 'TRI_FAN', {"pos": vertices
})
918 shader
.uniform_float("color", colour
)
921 # prepare drawing all edges in one batch
927 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
928 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
929 if m1x
< area_width
and m2x
< area_width
:
930 vertices
.extend([(m2x
-radius
,m2y
), (m2x
,m2y
),
931 (m1x
,m1y
), (m1x
-radius
,m1y
)])
932 indices
.extend([(id_last
, id_last
+1, id_last
+3),
933 (id_last
+3, id_last
+1, id_last
+2)])
937 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
938 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
939 m1x
= min(m1x
, area_width
)
940 m2x
= min(m2x
, area_width
)
941 if m1y
> bottom_bar
and m2y
> bottom_bar
:
942 vertices
.extend([(m1x
,m1y
), (m2x
,m1y
),
943 (m2x
,m1y
+radius
), (m1x
,m1y
+radius
)])
944 indices
.extend([(id_last
, id_last
+1, id_last
+3),
945 (id_last
+3, id_last
+1, id_last
+2)])
949 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
950 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
951 m1y
= max(m1y
, bottom_bar
)
952 m2y
= max(m2y
, bottom_bar
)
953 if m1x
< area_width
and m2x
< area_width
:
954 vertices
.extend([(m1x
,m2y
), (m1x
+radius
,m2y
),
955 (m1x
+radius
,m1y
), (m1x
,m1y
)])
956 indices
.extend([(id_last
, id_last
+1, id_last
+3),
957 (id_last
+3, id_last
+1, id_last
+2)])
961 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
, clip
=False)
962 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
, clip
=False)
963 m1x
= min(m1x
, area_width
)
964 m2x
= min(m2x
, area_width
)
965 if m1y
> bottom_bar
and m2y
> bottom_bar
:
966 vertices
.extend([(m1x
,m2y
), (m2x
,m2y
),
967 (m2x
,m1y
-radius
), (m1x
,m1y
-radius
)])
968 indices
.extend([(id_last
, id_last
+1, id_last
+3),
969 (id_last
+3, id_last
+1, id_last
+2)])
971 # now draw all edges in one batch
972 if len(vertices
) != 0:
973 batch
= batch_for_shader(shader
, 'TRIS', {"pos": vertices
}, indices
=indices
)
975 shader
.uniform_float("color", colour
)
978 def draw_callback_nodeoutline(self
, context
, mode
):
982 bgl
.glEnable(bgl
.GL_BLEND
)
983 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
984 bgl
.glHint(bgl
.GL_LINE_SMOOTH_HINT
, bgl
.GL_NICEST
)
986 nodes
, links
= get_nodes_links(context
)
988 shader
= gpu
.shader
.from_builtin('2D_UNIFORM_COLOR')
991 col_outer
= (1.0, 0.2, 0.2, 0.4)
992 col_inner
= (0.0, 0.0, 0.0, 0.5)
993 col_circle_inner
= (0.3, 0.05, 0.05, 1.0)
994 elif mode
== "LINKMENU":
995 col_outer
= (0.4, 0.6, 1.0, 0.4)
996 col_inner
= (0.0, 0.0, 0.0, 0.5)
997 col_circle_inner
= (0.08, 0.15, .3, 1.0)
999 col_outer
= (0.2, 1.0, 0.2, 0.4)
1000 col_inner
= (0.0, 0.0, 0.0, 0.5)
1001 col_circle_inner
= (0.05, 0.3, 0.05, 1.0)
1003 m1x
= self
.mouse_path
[0][0]
1004 m1y
= self
.mouse_path
[0][1]
1005 m2x
= self
.mouse_path
[-1][0]
1006 m2y
= self
.mouse_path
[-1][1]
1008 n1
= nodes
[context
.scene
.NWLazySource
]
1009 n2
= nodes
[context
.scene
.NWLazyTarget
]
1012 col_outer
= (0.4, 0.4, 0.4, 0.4)
1013 col_inner
= (0.0, 0.0, 0.0, 0.5)
1014 col_circle_inner
= (0.2, 0.2, 0.2, 1.0)
1016 draw_rounded_node_border(shader
, n1
, radius
=6, colour
=col_outer
) # outline
1017 draw_rounded_node_border(shader
, n1
, radius
=5, colour
=col_inner
) # inner
1018 draw_rounded_node_border(shader
, n2
, radius
=6, colour
=col_outer
) # outline
1019 draw_rounded_node_border(shader
, n2
, radius
=5, colour
=col_inner
) # inner
1021 draw_line(m1x
, m1y
, m2x
, m2y
, 5, col_outer
) # line outline
1022 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
1025 draw_circle_2d_filled(shader
, m1x
, m1y
, 7, col_outer
)
1026 draw_circle_2d_filled(shader
, m2x
, m2y
, 7, col_outer
)
1029 draw_circle_2d_filled(shader
, m1x
, m1y
, 5, col_circle_inner
)
1030 draw_circle_2d_filled(shader
, m2x
, m2y
, 5, col_circle_inner
)
1032 bgl
.glDisable(bgl
.GL_BLEND
)
1033 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
1034 def get_active_tree(context
):
1035 tree
= context
.space_data
.node_tree
1037 # Get nodes from currently edited tree.
1038 # If user is editing a group, space_data.node_tree is still the base level (outside group).
1039 # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
1040 # the same as context.active_node, the user is in a group.
1041 # Check recursively until we find the real active node_tree:
1042 if tree
.nodes
.active
:
1043 while tree
.nodes
.active
!= context
.active_node
:
1044 tree
= tree
.nodes
.active
.node_tree
1048 def get_nodes_links(context
):
1049 tree
, path
= get_active_tree(context
)
1050 return tree
.nodes
, tree
.links
1052 def is_viewer_socket(socket
):
1053 # checks if a internal socket is a valid viewer socket
1054 return socket
.name
== viewer_socket_name
and socket
.NWViewerSocket
1056 def get_internal_socket(socket
):
1057 #get the internal socket from a socket inside or outside the group
1059 if node
.type == 'GROUP_OUTPUT':
1060 source_iterator
= node
.inputs
1061 iterator
= node
.id_data
.outputs
1062 elif node
.type == 'GROUP_INPUT':
1063 source_iterator
= node
.outputs
1064 iterator
= node
.id_data
.inputs
1065 elif hasattr(node
, "node_tree"):
1066 if socket
.is_output
:
1067 source_iterator
= node
.outputs
1068 iterator
= node
.node_tree
.outputs
1070 source_iterator
= node
.inputs
1071 iterator
= node
.node_tree
.inputs
1075 for i
, s
in enumerate(source_iterator
):
1080 def is_viewer_link(link
, output_node
):
1081 if "Emission Viewer" in link
.to_node
.name
or link
.to_node
== output_node
and link
.to_socket
== output_node
.inputs
[0]:
1083 if link
.to_node
.type == 'GROUP_OUTPUT':
1084 socket
= get_internal_socket(link
.to_socket
)
1085 if is_viewer_socket(socket
):
1089 def get_group_output_node(tree
):
1090 for node
in tree
.nodes
:
1091 if node
.type == 'GROUP_OUTPUT' and node
.is_active_output
== True:
1094 def get_output_location(tree
):
1095 # get right-most location
1096 sorted_by_xloc
= (sorted(tree
.nodes
, key
=lambda x
: x
.location
.x
))
1097 max_xloc_node
= sorted_by_xloc
[-1]
1098 if max_xloc_node
.name
== 'Emission Viewer':
1099 max_xloc_node
= sorted_by_xloc
[-2]
1101 # get average y location
1103 for node
in tree
.nodes
:
1104 sum_yloc
+= node
.location
.y
1106 loc_x
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1107 loc_y
= sum_yloc
/ len(tree
.nodes
)
1111 class NWPrincipledPreferences(bpy
.types
.PropertyGroup
):
1112 base_color
: StringProperty(
1114 default
='diffuse diff albedo base col color',
1115 description
='Naming Components for Base Color maps')
1116 sss_color
: StringProperty(
1117 name
='Subsurface Color',
1118 default
='sss subsurface',
1119 description
='Naming Components for Subsurface Color maps')
1120 metallic
: StringProperty(
1122 default
='metallic metalness metal mtl',
1123 description
='Naming Components for metallness maps')
1124 specular
: StringProperty(
1126 default
='specularity specular spec spc',
1127 description
='Naming Components for Specular maps')
1128 normal
: StringProperty(
1130 default
='normal nor nrm nrml norm',
1131 description
='Naming Components for Normal maps')
1132 bump
: StringProperty(
1135 description
='Naming Components for bump maps')
1136 rough
: StringProperty(
1138 default
='roughness rough rgh',
1139 description
='Naming Components for roughness maps')
1140 gloss
: StringProperty(
1142 default
='gloss glossy glossiness',
1143 description
='Naming Components for glossy maps')
1144 displacement
: StringProperty(
1145 name
='Displacement',
1146 default
='displacement displace disp dsp height heightmap',
1147 description
='Naming Components for displacement maps')
1150 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1151 bl_idname
= __name__
1153 merge_hide
: EnumProperty(
1154 name
="Hide Mix nodes",
1156 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1157 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1158 ("NEVER", "Never", "Never collapse the new merge nodes")
1160 default
='NON_SHADER',
1161 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded")
1162 merge_position
: EnumProperty(
1163 name
="Mix Node Position",
1165 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1166 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1169 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
1171 show_hotkey_list
: BoolProperty(
1172 name
="Show Hotkey List",
1174 description
="Expand this box into a list of all the hotkeys for functions in this addon"
1176 hotkey_list_filter
: StringProperty(
1177 name
=" Filter by Name",
1179 description
="Show only hotkeys that have this text in their name"
1181 show_principled_lists
: BoolProperty(
1182 name
="Show Principled naming tags",
1184 description
="Expand this box into a list of all naming tags for principled texture setup"
1186 principled_tags
: bpy
.props
.PointerProperty(type=NWPrincipledPreferences
)
1188 def draw(self
, context
):
1189 layout
= self
.layout
1190 col
= layout
.column()
1191 col
.prop(self
, "merge_position")
1192 col
.prop(self
, "merge_hide")
1195 col
= box
.column(align
=True)
1196 col
.prop(self
, "show_principled_lists", text
='Edit tags for auto texture detection in Principled BSDF setup', toggle
=True)
1197 if self
.show_principled_lists
:
1198 tags
= self
.principled_tags
1200 col
.prop(tags
, "base_color")
1201 col
.prop(tags
, "sss_color")
1202 col
.prop(tags
, "metallic")
1203 col
.prop(tags
, "specular")
1204 col
.prop(tags
, "rough")
1205 col
.prop(tags
, "gloss")
1206 col
.prop(tags
, "normal")
1207 col
.prop(tags
, "bump")
1208 col
.prop(tags
, "displacement")
1211 col
= box
.column(align
=True)
1212 hotkey_button_name
= "Show Hotkey List"
1213 if self
.show_hotkey_list
:
1214 hotkey_button_name
= "Hide Hotkey List"
1215 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
1216 if self
.show_hotkey_list
:
1217 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
1219 for hotkey
in kmi_defs
:
1221 hotkey_name
= hotkey
[7]
1223 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
1224 row
= col
.row(align
=True)
1225 row
.label(text
=hotkey_name
)
1226 keystr
= nice_hotkey_name(hotkey
[1])
1228 keystr
= "Shift " + keystr
1230 keystr
= "Alt " + keystr
1232 keystr
= "Ctrl " + keystr
1233 row
.label(text
=keystr
)
1237 def nw_check(context
):
1238 space
= context
.space_data
1239 valid_trees
= ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
1242 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and space
.tree_type
in valid_trees
:
1249 def poll(cls
, context
):
1250 return nw_check(context
)
1254 class NWLazyMix(Operator
, NWBase
):
1255 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1256 bl_idname
= "node.nw_lazy_mix"
1257 bl_label
= "Mix Nodes"
1258 bl_options
= {'REGISTER', 'UNDO'}
1260 def modal(self
, context
, event
):
1261 context
.area
.tag_redraw()
1262 nodes
, links
= get_nodes_links(context
)
1265 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1268 if not context
.scene
.NWBusyDrawing
:
1269 node1
= node_at_pos(nodes
, context
, event
)
1271 context
.scene
.NWBusyDrawing
= node1
.name
1273 if context
.scene
.NWBusyDrawing
!= 'STOP':
1274 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1276 context
.scene
.NWLazySource
= node1
.name
1277 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1279 if event
.type == 'MOUSEMOVE':
1280 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1282 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1283 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1284 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1287 node2
= node_at_pos(nodes
, context
, event
)
1289 context
.scene
.NWBusyDrawing
= node2
.name
1301 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1303 context
.scene
.NWBusyDrawing
= ""
1306 elif event
.type == 'ESC':
1308 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1309 return {'CANCELLED'}
1311 return {'RUNNING_MODAL'}
1313 def invoke(self
, context
, event
):
1314 if context
.area
.type == 'NODE_EDITOR':
1315 # the arguments we pass the the callback
1316 args
= (self
, context
, 'MIX')
1317 # Add the region OpenGL drawing callback
1318 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1319 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1321 self
.mouse_path
= []
1323 context
.window_manager
.modal_handler_add(self
)
1324 return {'RUNNING_MODAL'}
1326 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1327 return {'CANCELLED'}
1330 class NWLazyConnect(Operator
, NWBase
):
1331 """Connect two nodes without clicking a specific socket (automatically determined"""
1332 bl_idname
= "node.nw_lazy_connect"
1333 bl_label
= "Lazy Connect"
1334 bl_options
= {'REGISTER', 'UNDO'}
1335 with_menu
: BoolProperty()
1337 def modal(self
, context
, event
):
1338 context
.area
.tag_redraw()
1339 nodes
, links
= get_nodes_links(context
)
1342 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1345 if not context
.scene
.NWBusyDrawing
:
1346 node1
= node_at_pos(nodes
, context
, event
)
1348 context
.scene
.NWBusyDrawing
= node1
.name
1350 if context
.scene
.NWBusyDrawing
!= 'STOP':
1351 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1353 context
.scene
.NWLazySource
= node1
.name
1354 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1356 if event
.type == 'MOUSEMOVE':
1357 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1359 elif event
.type == 'RIGHTMOUSE' and event
.value
== 'RELEASE':
1360 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1361 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1364 node2
= node_at_pos(nodes
, context
, event
)
1366 context
.scene
.NWBusyDrawing
= node2
.name
1371 link_success
= False
1377 if node
.select
== True:
1379 original_sel
.append(node
)
1381 original_unsel
.append(node
)
1385 #link_success = autolink(node1, node2, links)
1387 if len(node1
.outputs
) > 1 and node2
.inputs
:
1388 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1389 elif len(node1
.outputs
) == 1:
1390 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1392 link_success
= autolink(node1
, node2
, links
)
1394 for node
in original_sel
:
1396 for node
in original_unsel
:
1400 force_update(context
)
1401 context
.scene
.NWBusyDrawing
= ""
1404 elif event
.type == 'ESC':
1405 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1406 return {'CANCELLED'}
1408 return {'RUNNING_MODAL'}
1410 def invoke(self
, context
, event
):
1411 if context
.area
.type == 'NODE_EDITOR':
1412 nodes
, links
= get_nodes_links(context
)
1413 node
= node_at_pos(nodes
, context
, event
)
1415 context
.scene
.NWBusyDrawing
= node
.name
1417 # the arguments we pass the the callback
1421 args
= (self
, context
, mode
)
1422 # Add the region OpenGL drawing callback
1423 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1424 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1426 self
.mouse_path
= []
1428 context
.window_manager
.modal_handler_add(self
)
1429 return {'RUNNING_MODAL'}
1431 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1432 return {'CANCELLED'}
1435 class NWDeleteUnused(Operator
, NWBase
):
1436 """Delete all nodes whose output is not used"""
1437 bl_idname
= 'node.nw_del_unused'
1438 bl_label
= 'Delete Unused Nodes'
1439 bl_options
= {'REGISTER', 'UNDO'}
1441 delete_muted
: BoolProperty(name
="Delete Muted", description
="Delete (but reconnect, like Ctrl-X) all muted nodes", default
=True)
1442 delete_frames
: BoolProperty(name
="Delete Empty Frames", description
="Delete all frames that have no nodes inside them", default
=True)
1444 def is_unused_node(self
, node
):
1445 end_types
= ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1446 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
1447 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
1448 if node
.type in end_types
:
1451 for output
in node
.outputs
:
1457 def poll(cls
, context
):
1459 if nw_check(context
):
1460 if context
.space_data
.node_tree
.nodes
:
1464 def execute(self
, context
):
1465 nodes
, links
= get_nodes_links(context
)
1470 if node
.select
== True:
1471 selection
.append(node
.name
)
1477 temp_deleted_nodes
= []
1478 del_unused_iterations
= len(nodes
)
1479 for it
in range(0, del_unused_iterations
):
1480 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1482 if self
.is_unused_node(node
):
1484 deleted_nodes
.append(node
.name
)
1485 bpy
.ops
.node
.delete()
1487 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1490 if self
.delete_frames
:
1498 frames_in_use
.append(node
.parent
)
1500 if node
.type == 'FRAME' and node
not in frames_in_use
:
1503 repeat
= True # repeat for nested frames
1505 if node
not in frames_in_use
:
1507 deleted_nodes
.append(node
.name
)
1508 bpy
.ops
.node
.delete()
1510 if self
.delete_muted
:
1514 deleted_nodes
.append(node
.name
)
1515 bpy
.ops
.node
.delete_reconnect()
1517 # get unique list of deleted nodes (iterations would count the same node more than once)
1518 deleted_nodes
= list(set(deleted_nodes
))
1519 for n
in deleted_nodes
:
1520 self
.report({'INFO'}, "Node " + n
+ " deleted")
1521 num_deleted
= len(deleted_nodes
)
1526 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1528 self
.report({'INFO'}, "Nothing deleted")
1531 nodes
, links
= get_nodes_links(context
)
1533 if node
.name
in selection
:
1537 def invoke(self
, context
, event
):
1538 return context
.window_manager
.invoke_confirm(self
, event
)
1541 class NWSwapLinks(Operator
, NWBase
):
1542 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1543 bl_idname
= 'node.nw_swap_links'
1544 bl_label
= 'Swap Links'
1545 bl_options
= {'REGISTER', 'UNDO'}
1548 def poll(cls
, context
):
1550 if nw_check(context
):
1551 if context
.selected_nodes
:
1552 valid
= len(context
.selected_nodes
) <= 2
1555 def execute(self
, context
):
1556 nodes
, links
= get_nodes_links(context
)
1557 selected_nodes
= context
.selected_nodes
1558 n1
= selected_nodes
[0]
1561 if len(selected_nodes
) == 2:
1562 n2
= selected_nodes
[1]
1563 if n1
.outputs
and n2
.outputs
:
1568 for output
in n1
.outputs
:
1570 for link
in output
.links
:
1571 n1_outputs
.append([out_index
, link
.to_socket
])
1576 for output
in n2
.outputs
:
1578 for link
in output
.links
:
1579 n2_outputs
.append([out_index
, link
.to_socket
])
1583 for connection
in n1_outputs
:
1585 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1587 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1588 for connection
in n2_outputs
:
1590 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1592 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1594 if n1
.outputs
or n2
.outputs
:
1595 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1597 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1600 elif len(selected_nodes
) == 1:
1604 for i1
in n1
.inputs
:
1607 for i2
in n1
.inputs
:
1608 if i1
.type == i2
.type and i2
.is_linked
:
1610 types
.append ([i1
, similar_types
, i
])
1612 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1617 for i2
in n1
.inputs
:
1618 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1620 i1f
= pair
[0].links
[0].from_socket
1621 i1t
= pair
[0].links
[0].to_socket
1622 i2f
= pair
[1].links
[0].from_socket
1623 i2t
= pair
[1].links
[0].to_socket
1628 fs
= t
[0].links
[0].from_socket
1630 links
.remove(t
[0].links
[0])
1631 if i
+1 == len(n1
.inputs
):
1634 while n1
.inputs
[i
].is_linked
:
1636 links
.new(fs
, n1
.inputs
[i
])
1637 elif len(types
) == 2:
1638 i1f
= types
[0][0].links
[0].from_socket
1639 i1t
= types
[0][0].links
[0].to_socket
1640 i2f
= types
[1][0].links
[0].from_socket
1641 i2t
= types
[1][0].links
[0].to_socket
1646 self
.report({'WARNING'}, "This node has no input connections to swap!")
1648 self
.report({'WARNING'}, "This node has no inputs to swap!")
1650 force_update(context
)
1654 class NWResetBG(Operator
, NWBase
):
1655 """Reset the zoom and position of the background image"""
1656 bl_idname
= 'node.nw_bg_reset'
1657 bl_label
= 'Reset Backdrop'
1658 bl_options
= {'REGISTER', 'UNDO'}
1661 def poll(cls
, context
):
1663 if nw_check(context
):
1664 snode
= context
.space_data
1665 valid
= snode
.tree_type
== 'CompositorNodeTree'
1668 def execute(self
, context
):
1669 context
.space_data
.backdrop_zoom
= 1
1670 context
.space_data
.backdrop_offset
[0] = 0
1671 context
.space_data
.backdrop_offset
[1] = 0
1675 class NWAddAttrNode(Operator
, NWBase
):
1676 """Add an Attribute node with this name"""
1677 bl_idname
= 'node.nw_add_attr_node'
1678 bl_label
= 'Add UV map'
1679 bl_options
= {'REGISTER', 'UNDO'}
1681 attr_name
: StringProperty()
1683 def execute(self
, context
):
1684 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1685 nodes
, links
= get_nodes_links(context
)
1686 nodes
.active
.attribute_name
= self
.attr_name
1689 class NWEmissionViewer(Operator
, NWBase
):
1690 bl_idname
= "node.nw_emission_viewer"
1691 bl_label
= "Emission Viewer"
1692 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1693 bl_options
= {'REGISTER', 'UNDO'}
1696 self
.shader_output_type
= ""
1697 self
.shader_output_ident
= ""
1698 self
.shader_viewer_ident
= ""
1701 def poll(cls
, context
):
1702 if nw_check(context
):
1703 space
= context
.space_data
1704 if space
.tree_type
== 'ShaderNodeTree':
1705 if context
.active_node
:
1706 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1712 def ensure_viewer_socket(self
, node
, socket_type
, connect_socket
=None):
1713 #check if a viewer output already exists in a node group otherwise create
1714 if hasattr(node
, "node_tree"):
1716 if len(node
.node_tree
.outputs
):
1718 for i
, socket
in enumerate(node
.node_tree
.outputs
):
1719 if is_viewer_socket(socket
) and is_visible_socket(node
.outputs
[i
]) and socket
.type == socket_type
:
1720 #if viewer output is already used but leads to the same socket we can still use it
1721 is_used
= self
.is_socket_used_other_mats(socket
)
1723 if connect_socket
== None:
1725 groupout
= get_group_output_node(node
.node_tree
)
1726 groupout_input
= groupout
.inputs
[i
]
1727 links
= groupout_input
.links
1728 if connect_socket
not in [link
.from_socket
for link
in links
]:
1734 if not index
and free_socket
:
1738 #create viewer socket
1739 node
.node_tree
.outputs
.new(socket_type
, viewer_socket_name
)
1740 index
= len(node
.node_tree
.outputs
) - 1
1741 node
.node_tree
.outputs
[index
].NWViewerSocket
= True
1744 def init_shader_variables(self
, space
, shader_type
):
1745 if shader_type
== 'OBJECT':
1746 if space
.id not in [light
for light
in bpy
.data
.lights
]: # cannot use bpy.data.lights directly as iterable
1747 self
.shader_output_type
= "OUTPUT_MATERIAL"
1748 self
.shader_output_ident
= "ShaderNodeOutputMaterial"
1749 self
.shader_viewer_ident
= "ShaderNodeEmission"
1751 self
.shader_output_type
= "OUTPUT_LIGHT"
1752 self
.shader_output_ident
= "ShaderNodeOutputLight"
1753 self
.shader_viewer_ident
= "ShaderNodeEmission"
1755 elif shader_type
== 'WORLD':
1756 self
.shader_output_type
= "OUTPUT_WORLD"
1757 self
.shader_output_ident
= "ShaderNodeOutputWorld"
1758 self
.shader_viewer_ident
= "ShaderNodeBackground"
1760 def get_shader_output_node(self
, tree
):
1761 for node
in tree
.nodes
:
1762 if node
.type == self
.shader_output_type
and node
.is_active_output
== True:
1766 def ensure_group_output(cls
, tree
):
1767 #check if a group output node exists otherwise create
1768 groupout
= get_group_output_node(tree
)
1770 groupout
= tree
.nodes
.new('NodeGroupOutput')
1771 loc_x
, loc_y
= get_output_location(tree
)
1772 groupout
.location
.x
= loc_x
1773 groupout
.location
.y
= loc_y
1774 groupout
.select
= False
1778 def search_sockets(cls
, node
, sockets
, index
=None):
1779 #recursevley scan nodes for viewer sockets and store in list
1780 for i
, input_socket
in enumerate(node
.inputs
):
1781 if index
and i
!= index
:
1783 if len(input_socket
.links
):
1784 link
= input_socket
.links
[0]
1785 next_node
= link
.from_node
1786 external_socket
= link
.from_socket
1787 if hasattr(next_node
, "node_tree"):
1788 for socket_index
, s
in enumerate(next_node
.outputs
):
1789 if s
== external_socket
:
1791 socket
= next_node
.node_tree
.outputs
[socket_index
]
1792 if is_viewer_socket(socket
) and socket
not in sockets
:
1793 sockets
.append(socket
)
1794 #continue search inside of node group but restrict socket to where we came from
1795 groupout
= get_group_output_node(next_node
.node_tree
)
1796 cls
.search_sockets(groupout
, sockets
, index
=socket_index
)
1799 def scan_nodes(cls
, tree
, sockets
):
1800 # get all viewer sockets in a material tree
1801 for node
in tree
.nodes
:
1802 if hasattr(node
, "node_tree"):
1803 for socket
in node
.node_tree
.outputs
:
1804 if is_viewer_socket(socket
) and (socket
not in sockets
):
1805 sockets
.append(socket
)
1806 cls
.scan_nodes(node
.node_tree
, sockets
)
1808 def link_leads_to_used_socket(self
, link
):
1809 #return True if link leads to a socket that is already used in this material
1810 socket
= get_internal_socket(link
.to_socket
)
1811 return (socket
and self
.is_socket_used_active_mat(socket
))
1813 def is_socket_used_active_mat(self
, socket
):
1814 #ensure used sockets in active material is calculated and check given socket
1815 if not hasattr(self
, "used_viewer_sockets_active_mat"):
1816 self
.used_viewer_sockets_active_mat
= []
1817 materialout
= self
.get_shader_output_node(bpy
.context
.space_data
.node_tree
)
1819 emission
= self
.get_viewer_node(materialout
)
1820 self
.search_sockets((emission
if emission
else materialout
), self
.used_viewer_sockets_active_mat
)
1821 return socket
in self
.used_viewer_sockets_active_mat
1823 def is_socket_used_other_mats(self
, socket
):
1824 #ensure used sockets in other materials are calculated and check given socket
1825 if not hasattr(self
, "used_viewer_sockets_other_mats"):
1826 self
.used_viewer_sockets_other_mats
= []
1827 for mat
in bpy
.data
.materials
:
1828 if mat
.node_tree
== bpy
.context
.space_data
.node_tree
or not hasattr(mat
.node_tree
, "nodes"):
1831 materialout
= self
.get_shader_output_node(mat
.node_tree
)
1833 emission
= self
.get_viewer_node(materialout
)
1834 self
.search_sockets((emission
if emission
else materialout
), self
.used_viewer_sockets_other_mats
)
1835 return socket
in self
.used_viewer_sockets_other_mats
1838 def get_viewer_node(materialout
):
1839 input_socket
= materialout
.inputs
[0]
1840 if len(input_socket
.links
) > 0:
1841 node
= input_socket
.links
[0].from_node
1842 if node
.type == 'EMISSION' and node
.name
== "Emission Viewer":
1845 def invoke(self
, context
, event
):
1846 space
= context
.space_data
1847 shader_type
= space
.shader_type
1848 self
.init_shader_variables(space
, shader_type
)
1849 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1850 mlocx
= event
.mouse_region_x
1851 mlocy
= event
.mouse_region_y
1852 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1853 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1854 active_tree
, path_to_tree
= get_active_tree(context
)
1855 nodes
, links
= active_tree
.nodes
, active_tree
.links
1856 base_node_tree
= space
.node_tree
1857 active
= nodes
.active
1858 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1861 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
):
1862 for out
in active
.outputs
:
1863 if is_visible_socket(out
):
1867 # get material_output node
1868 materialout
= None # placeholder node
1871 #scan through all nodes in tree including nodes inside of groups to find viewer sockets
1872 self
.scan_nodes(base_node_tree
, delete_sockets
)
1874 materialout
= self
.get_shader_output_node(base_node_tree
)
1876 materialout
= base_node_tree
.nodes
.new(self
.shader_output_ident
)
1877 materialout
.location
= get_output_location(base_node_tree
)
1878 materialout
.select
= False
1879 # Analyze outputs, add "Emission Viewer" if needed, make links
1882 for i
, out
in enumerate(active
.outputs
):
1883 if is_visible_socket(out
):
1884 valid_outputs
.append(i
)
1886 out_i
= valid_outputs
[0] # Start index of node's outputs
1887 for i
, valid_i
in enumerate(valid_outputs
):
1888 for out_link
in active
.outputs
[valid_i
].links
:
1889 if is_viewer_link(out_link
, materialout
):
1890 if nodes
== base_node_tree
.nodes
or self
.link_leads_to_used_socket(out_link
):
1891 if i
< len(valid_outputs
) - 1:
1892 out_i
= valid_outputs
[i
+ 1]
1894 out_i
= valid_outputs
[0]
1896 make_links
= [] # store sockets for new links
1897 delete_nodes
= [] # store unused nodes to delete in the end
1899 # If output type not 'SHADER' - "Emission Viewer" needed
1900 if active
.outputs
[out_i
].type != 'SHADER':
1901 socket_type
= 'NodeSocketColor'
1902 # get Emission Viewer node
1903 emission_exists
= False
1904 emission_placeholder
= base_node_tree
.nodes
[0]
1905 for node
in base_node_tree
.nodes
:
1906 if "Emission Viewer" in node
.name
:
1907 emission_exists
= True
1908 emission_placeholder
= node
1909 if not emission_exists
:
1910 emission
= base_node_tree
.nodes
.new(self
.shader_viewer_ident
)
1911 emission
.hide
= True
1912 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1913 emission
.label
= "Viewer"
1914 emission
.name
= "Emission Viewer"
1915 emission
.use_custom_color
= True
1916 emission
.color
= (0.6, 0.5, 0.4)
1917 emission
.select
= False
1919 emission
= emission_placeholder
1920 output_socket
= emission
.inputs
[0]
1922 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1923 if emission
.outputs
[0].links
.__len
__() > 0:
1924 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1925 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1927 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1929 # Set brightness of viewer to compensate for Film and CM exposure
1930 if context
.scene
.render
.engine
== 'CYCLES' and hasattr(context
.scene
, 'cycles'):
1931 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1935 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1936 emission
.inputs
[1].default_value
= intensity
1939 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1940 socket_type
= 'NodeSocketShader'
1941 materialout_index
= 1 if active
.outputs
[out_i
].name
== "Volume" else 0
1942 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[materialout_index
]))
1943 output_socket
= materialout
.inputs
[materialout_index
]
1944 for node
in base_node_tree
.nodes
:
1945 if node
.name
== 'Emission Viewer':
1946 delete_nodes
.append((base_node_tree
, node
))
1947 for li_from
, li_to
in make_links
:
1948 base_node_tree
.links
.new(li_from
, li_to
)
1950 # Crate links through node groups until we reach the active node
1951 tree
= base_node_tree
1952 link_end
= output_socket
1953 while tree
.nodes
.active
!= active
:
1954 node
= tree
.nodes
.active
1955 index
= self
.ensure_viewer_socket(node
, socket_type
, connect_socket
=active
.outputs
[out_i
] if node
.node_tree
.nodes
.active
== active
else None)
1956 link_start
= node
.outputs
[index
]
1957 node_socket
= node
.node_tree
.outputs
[index
]
1958 if node_socket
in delete_sockets
:
1959 delete_sockets
.remove(node_socket
)
1960 tree
.links
.new(link_start
, link_end
)
1962 link_end
= self
.ensure_group_output(node
.node_tree
).inputs
[index
]
1963 tree
= tree
.nodes
.active
.node_tree
1964 tree
.links
.new(active
.outputs
[out_i
], link_end
)
1967 for socket
in delete_sockets
:
1968 if not self
.is_socket_used_other_mats(socket
):
1969 tree
= socket
.id_data
1970 tree
.outputs
.remove(socket
)
1973 for tree
, node
in delete_nodes
:
1974 tree
.nodes
.remove(node
)
1976 nodes
.active
= active
1977 active
.select
= True
1979 force_update(context
)
1983 return {'CANCELLED'}
1986 class NWFrameSelected(Operator
, NWBase
):
1987 bl_idname
= "node.nw_frame_selected"
1988 bl_label
= "Frame Selected"
1989 bl_description
= "Add a frame node and parent the selected nodes to it"
1990 bl_options
= {'REGISTER', 'UNDO'}
1992 label_prop
: StringProperty(
1994 description
='The visual name of the frame node',
1997 color_prop
: FloatVectorProperty(
1999 description
="The color of the frame node",
2000 default
=(0.6, 0.6, 0.6),
2001 min=0, max=1, step
=1, precision
=3,
2002 subtype
='COLOR_GAMMA', size
=3
2005 def execute(self
, context
):
2006 nodes
, links
= get_nodes_links(context
)
2009 if node
.select
== True:
2010 selected
.append(node
)
2012 bpy
.ops
.node
.add_node(type='NodeFrame')
2014 frm
.label
= self
.label_prop
2015 frm
.use_custom_color
= True
2016 frm
.color
= self
.color_prop
2018 for node
in selected
:
2024 class NWReloadImages(Operator
, NWBase
):
2025 bl_idname
= "node.nw_reload_images"
2026 bl_label
= "Reload Images"
2027 bl_description
= "Update all the image nodes to match their files on disk"
2029 def execute(self
, context
):
2030 nodes
, links
= get_nodes_links(context
)
2031 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
2034 if node
.type in image_types
:
2035 if node
.type == "TEXTURE":
2036 if node
.texture
: # node has texture assigned
2037 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
2038 if node
.texture
.image
: # texture has image assigned
2039 node
.texture
.image
.reload()
2047 self
.report({'INFO'}, "Reloaded images")
2048 print("Reloaded " + str(num_reloaded
) + " images")
2049 force_update(context
)
2052 self
.report({'WARNING'}, "No images found to reload in this node tree")
2053 return {'CANCELLED'}
2056 class NWSwitchNodeType(Operator
, NWBase
):
2057 """Switch type of selected nodes """
2058 bl_idname
= "node.nw_swtch_node_type"
2059 bl_label
= "Switch Node Type"
2060 bl_options
= {'REGISTER', 'UNDO'}
2062 to_type
: EnumProperty(
2063 name
="Switch to type",
2064 items
=list(shaders_input_nodes_props
) +
2065 list(shaders_output_nodes_props
) +
2066 list(shaders_shader_nodes_props
) +
2067 list(shaders_texture_nodes_props
) +
2068 list(shaders_color_nodes_props
) +
2069 list(shaders_vector_nodes_props
) +
2070 list(shaders_converter_nodes_props
) +
2071 list(shaders_layout_nodes_props
) +
2072 list(compo_input_nodes_props
) +
2073 list(compo_output_nodes_props
) +
2074 list(compo_color_nodes_props
) +
2075 list(compo_converter_nodes_props
) +
2076 list(compo_filter_nodes_props
) +
2077 list(compo_vector_nodes_props
) +
2078 list(compo_matte_nodes_props
) +
2079 list(compo_distort_nodes_props
) +
2080 list(compo_layout_nodes_props
) +
2081 list(blender_mat_input_nodes_props
) +
2082 list(blender_mat_output_nodes_props
) +
2083 list(blender_mat_color_nodes_props
) +
2084 list(blender_mat_vector_nodes_props
) +
2085 list(blender_mat_converter_nodes_props
) +
2086 list(blender_mat_layout_nodes_props
) +
2087 list(texture_input_nodes_props
) +
2088 list(texture_output_nodes_props
) +
2089 list(texture_color_nodes_props
) +
2090 list(texture_pattern_nodes_props
) +
2091 list(texture_textures_nodes_props
) +
2092 list(texture_converter_nodes_props
) +
2093 list(texture_distort_nodes_props
) +
2094 list(texture_layout_nodes_props
)
2097 def execute(self
, context
):
2098 nodes
, links
= get_nodes_links(context
)
2099 to_type
= self
.to_type
2100 # Those types of nodes will not swap.
2101 src_excludes
= ('NodeFrame')
2102 # Those attributes of nodes will be copied if possible
2103 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
2104 'show_options', 'show_preview', 'show_texture',
2105 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
2107 selected
= [n
for n
in nodes
if n
.select
]
2109 for node
in [n
for n
in selected
if
2110 n
.rna_type
.identifier
not in src_excludes
and
2111 n
.rna_type
.identifier
!= to_type
]:
2112 new_node
= nodes
.new(to_type
)
2113 for attr
in attrs_to_pass
:
2114 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
2115 setattr(new_node
, attr
, getattr(node
, attr
))
2116 # set image datablock of dst to image of src
2117 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
2119 new_node
.image
= node
.image
2121 if new_node
.type == 'SWITCH':
2122 new_node
.hide
= True
2123 # Dictionaries: src_sockets and dst_sockets:
2124 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
2125 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
2126 # in 'INPUTS' and 'OUTPUTS':
2127 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
2129 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
2131 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
2132 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
2135 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
2136 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
2138 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
2139 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
2140 # check src node to set src_sockets values and dst node to set dst_sockets dict values
2141 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
2142 # Check node's inputs and outputs and fill proper entries in "sockets" dict
2143 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
2144 # enumerate in inputs, then in outputs
2145 # find name, default value and links of socket
2146 for i
, socket
in enumerate(in_out
):
2147 the_name
= socket
.name
2149 # Not every socket, especially in outputs has "default_value"
2150 if hasattr(socket
, 'default_value'):
2151 dval
= socket
.default_value
2153 for lnk
in socket
.links
:
2154 socket_links
.append(lnk
)
2155 # check type of socket to fill proper keys.
2156 for the_type
in types_order_one
:
2157 if socket
.type == the_type
:
2158 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
2159 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
2160 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
2161 # Check which of the types in inputs/outputs is considered to be "main".
2162 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
2163 for type_check
in types_order_one
:
2164 if sockets
[in_out_name
][type_check
]:
2165 sockets
[in_out_name
]['MAIN'] = type_check
2169 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
2170 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
2173 for inout
, soctype
in (
2174 ('INPUTS', 'MAIN',),
2175 ('INPUTS', 'SHADER',),
2176 ('INPUTS', 'RGBA',),
2177 ('INPUTS', 'VECTOR',),
2178 ('INPUTS', 'VALUE',),
2179 ('OUTPUTS', 'MAIN',),
2180 ('OUTPUTS', 'SHADER',),
2181 ('OUTPUTS', 'RGBA',),
2182 ('OUTPUTS', 'VECTOR',),
2183 ('OUTPUTS', 'VALUE',),
2185 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
2186 if soctype
== 'MAIN':
2187 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
2188 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
2190 sc
= src_sockets
[inout
][soctype
]
2191 dt
= dst_sockets
[inout
][soctype
]
2192 # start with 'dt' to determine number of possibilities.
2193 for i
, soc
in enumerate(dt
):
2194 # if src main has enough entries - match them with dst main sockets by indexes.
2196 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
2197 # add 'VALUE_NAME' criterion to inputs.
2198 if inout
== 'INPUTS' and soctype
== 'VALUE':
2200 if s
[2] == soc
[2]: # if names match
2201 # append src (index, dval), dst (index, dval)
2202 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
2204 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
2205 # This creates better links when relinking textures.
2206 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
2207 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
2209 # Pass default values and RELINK:
2210 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
2211 # INPUTS: Base on matches in proper order.
2212 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
2214 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
2215 new_node
.inputs
[dst_i
].default_value
= src_dval
2216 # Special case: switch to math
2217 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
2218 new_node
.type == 'MATH' and\
2220 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
2221 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
2222 if node
.type == 'MIX_RGB':
2223 if node
.blend_type
in [o
[0] for o
in operations
]:
2224 new_node
.operation
= node
.blend_type
2225 # Special case: switch from math to some types
2226 if node
.type == 'MATH' and\
2227 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
2230 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
2231 if new_node
.type == 'MIX_RGB':
2232 if node
.operation
in [t
[0] for t
in blend_types
]:
2233 new_node
.blend_type
= node
.operation
2234 # Set Fac of MIX_RGB to 1.0
2235 new_node
.inputs
[0].default_value
= 1.0
2236 # make link only when dst matching input is not linked already.
2237 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
2238 in_src_link
= node
.inputs
[src_i
].links
[0]
2239 in_dst_socket
= new_node
.inputs
[dst_i
]
2240 links
.new(in_src_link
.from_socket
, in_dst_socket
)
2241 links
.remove(in_src_link
)
2242 # OUTPUTS: Base on matches in proper order.
2243 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
2244 for out_src_link
in node
.outputs
[src_i
].links
:
2245 out_dst_socket
= new_node
.outputs
[dst_i
]
2246 links
.new(out_dst_socket
, out_src_link
.to_socket
)
2247 # relink rest inputs if possible, no criteria
2248 for src_inp
in node
.inputs
:
2249 for dst_inp
in new_node
.inputs
:
2250 if src_inp
.links
and not dst_inp
.links
:
2251 src_link
= src_inp
.links
[0]
2252 links
.new(src_link
.from_socket
, dst_inp
)
2253 links
.remove(src_link
)
2254 # relink rest outputs if possible, base on node kind if any left.
2255 for src_o
in node
.outputs
:
2256 for out_src_link
in src_o
.links
:
2257 for dst_o
in new_node
.outputs
:
2258 if src_o
.type == dst_o
.type:
2259 links
.new(dst_o
, out_src_link
.to_socket
)
2260 # relink rest outputs no criteria if any left. Link all from first output.
2261 for src_o
in node
.outputs
:
2262 for out_src_link
in src_o
.links
:
2263 if new_node
.outputs
:
2264 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
2266 force_update(context
)
2270 class NWMergeNodes(Operator
, NWBase
):
2271 bl_idname
= "node.nw_merge_nodes"
2272 bl_label
= "Merge Nodes"
2273 bl_description
= "Merge Selected Nodes"
2274 bl_options
= {'REGISTER', 'UNDO'}
2278 description
="All possible blend types and math operations",
2279 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2281 merge_type
: EnumProperty(
2283 description
="Type of Merge to be used",
2285 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2286 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2287 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2288 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2289 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2290 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2294 def execute(self
, context
):
2295 settings
= context
.preferences
.addons
[__name__
].preferences
2296 merge_hide
= settings
.merge_hide
2297 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2300 do_hide_shader
= False
2301 if merge_hide
== 'ALWAYS':
2303 do_hide_shader
= True
2304 elif merge_hide
== 'NON_SHADER':
2307 tree_type
= context
.space_data
.node_tree
.type
2308 if tree_type
== 'COMPOSITING':
2309 node_type
= 'CompositorNode'
2310 elif tree_type
== 'SHADER':
2311 node_type
= 'ShaderNode'
2312 elif tree_type
== 'TEXTURE':
2313 node_type
= 'TextureNode'
2314 nodes
, links
= get_nodes_links(context
)
2316 merge_type
= self
.merge_type
2317 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2318 # 'ZCOMBINE' works only if mode == 'MIX'
2319 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2320 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2323 selected_mix
= [] # entry = [index, loc]
2324 selected_shader
= [] # entry = [index, loc]
2325 selected_math
= [] # entry = [index, loc]
2326 selected_z
= [] # entry = [index, loc]
2327 selected_alphaover
= [] # entry = [index, loc]
2329 for i
, node
in enumerate(nodes
):
2330 if node
.select
and node
.outputs
:
2331 if merge_type
== 'AUTO':
2332 for (type, types_list
, dst
) in (
2333 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2334 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2335 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2337 output_type
= node
.outputs
[0].type
2338 valid_mode
= mode
in types_list
2339 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2340 # Cheat that output type is 'RGBA',
2341 # and that 'MIX' exists in math operations list.
2342 # This way when selected_mix list is analyzed:
2343 # Node data will be appended even though it doesn't meet requirements.
2344 if output_type
!= 'SHADER' and mode
== 'MIX':
2345 output_type
= 'RGBA'
2347 if output_type
== type and valid_mode
:
2348 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2350 for (type, types_list
, dst
) in (
2351 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2352 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2353 ('MATH', [t
[0] for t
in operations
], selected_math
),
2354 ('ZCOMBINE', ('MIX', ), selected_z
),
2355 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2357 if merge_type
== type and mode
in types_list
:
2358 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2359 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2360 # use only 'Mix' nodes for merging.
2361 # For that we add selected_math list to selected_mix list and clear selected_math.
2362 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2363 selected_mix
+= selected_math
2366 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2368 count_before
= len(nodes
)
2369 # sort list by loc_x - reversed
2370 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2372 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2373 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2374 if merge_position
== 'CENTER':
2375 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)
2376 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2382 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2386 if nodes_list
== selected_shader
and not do_hide_shader
:
2388 the_range
= len(nodes_list
) - 1
2389 if len(nodes_list
) == 1:
2391 for i
in range(the_range
):
2392 if nodes_list
== selected_mix
:
2393 add_type
= node_type
+ 'MixRGB'
2394 add
= nodes
.new(add_type
)
2395 add
.blend_type
= mode
2397 add
.inputs
[0].default_value
= 1.0
2398 add
.show_preview
= False
2404 add
.width_hidden
= 100.0
2405 elif nodes_list
== selected_math
:
2406 add_type
= node_type
+ 'Math'
2407 add
= nodes
.new(add_type
)
2408 add
.operation
= mode
2414 add
.width_hidden
= 100.0
2415 elif nodes_list
== selected_shader
:
2417 add_type
= node_type
+ 'MixShader'
2418 add
= nodes
.new(add_type
)
2419 add
.hide
= do_hide_shader
2424 add
.width_hidden
= 100.0
2426 add_type
= node_type
+ 'AddShader'
2427 add
= nodes
.new(add_type
)
2428 add
.hide
= do_hide_shader
2433 add
.width_hidden
= 100.0
2434 elif nodes_list
== selected_z
:
2435 add
= nodes
.new('CompositorNodeZcombine')
2436 add
.show_preview
= False
2442 add
.width_hidden
= 100.0
2443 elif nodes_list
== selected_alphaover
:
2444 add
= nodes
.new('CompositorNodeAlphaOver')
2445 add
.show_preview
= False
2451 add
.width_hidden
= 100.0
2452 add
.location
= loc_x
, loc_y
2456 count_after
= len(nodes
)
2457 index
= count_after
- 1
2458 first_selected
= nodes
[nodes_list
[0][0]]
2459 # "last" node has been added as first, so its index is count_before.
2460 last_add
= nodes
[count_before
]
2462 # Two nodes were selected and first selected has no output links, second selected has output links.
2463 # Then add links from last add to all links 'to_socket' of out links of second selected.
2464 if len(nodes_list
) == 2:
2465 if not first_selected
.outputs
[0].links
:
2466 second_selected
= nodes
[nodes_list
[1][0]]
2467 for ss_link
in second_selected
.outputs
[0].links
:
2468 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2469 # Create list of invalid indexes.
2470 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2471 # Link only if "to_node" index not in invalid indexes list.
2472 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2473 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2474 # add links from last_add to all links 'to_socket' of out links of first selected.
2475 for fs_link
in first_selected
.outputs
[0].links
:
2476 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2477 # Create list of invalid indexes.
2478 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2479 # Link only if "to_node" index not in invalid indexes list.
2480 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2481 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2482 # add link from "first" selected and "first" add node
2483 node_to
= nodes
[count_after
- 1]
2484 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2485 if node_to
.type == 'ZCOMBINE':
2486 for fs_out
in first_selected
.outputs
:
2487 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2488 links
.new(fs_out
, node_to
.inputs
[1])
2490 # add links between added ADD nodes and between selected and ADD nodes
2491 for i
in range(count_adds
):
2492 if i
< count_adds
- 1:
2493 node_from
= nodes
[index
]
2494 node_to
= nodes
[index
- 1]
2495 node_to_input_i
= first
2496 node_to_z_i
= 1 # if z combine - link z to first z input
2497 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2498 if node_to
.type == 'ZCOMBINE':
2499 for from_out
in node_from
.outputs
:
2500 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2501 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2502 if len(nodes_list
) > 1:
2503 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2504 node_to
= nodes
[index
]
2505 node_to_input_i
= second
2506 node_to_z_i
= 3 # if z combine - link z to second z input
2507 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2508 if node_to
.type == 'ZCOMBINE':
2509 for from_out
in node_from
.outputs
:
2510 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2511 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2513 # set "last" of added nodes as active
2514 nodes
.active
= last_add
2515 for i
, x
, y
, dx
, h
in nodes_list
:
2516 nodes
[i
].select
= False
2521 class NWBatchChangeNodes(Operator
, NWBase
):
2522 bl_idname
= "node.nw_batch_change"
2523 bl_label
= "Batch Change"
2524 bl_description
= "Batch Change Blend Type and Math Operation"
2525 bl_options
= {'REGISTER', 'UNDO'}
2527 blend_type
: EnumProperty(
2529 items
=blend_types
+ navs
,
2531 operation
: EnumProperty(
2533 items
=operations
+ navs
,
2536 def execute(self
, context
):
2538 nodes
, links
= get_nodes_links(context
)
2539 blend_type
= self
.blend_type
2540 operation
= self
.operation
2541 for node
in context
.selected_nodes
:
2542 if node
.type == 'MIX_RGB':
2543 if not blend_type
in [nav
[0] for nav
in navs
]:
2544 node
.blend_type
= blend_type
2546 if blend_type
== 'NEXT':
2547 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2548 #index = blend_types.index(node.blend_type)
2549 if index
== len(blend_types
) - 1:
2550 node
.blend_type
= blend_types
[0][0]
2552 node
.blend_type
= blend_types
[index
+ 1][0]
2554 if blend_type
== 'PREV':
2555 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2557 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2559 node
.blend_type
= blend_types
[index
- 1][0]
2561 if node
.type == 'MATH':
2562 if not operation
in [nav
[0] for nav
in navs
]:
2563 node
.operation
= operation
2565 if operation
== 'NEXT':
2566 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2567 #index = operations.index(node.operation)
2568 if index
== len(operations
) - 1:
2569 node
.operation
= operations
[0][0]
2571 node
.operation
= operations
[index
+ 1][0]
2573 if operation
== 'PREV':
2574 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2575 #index = operations.index(node.operation)
2577 node
.operation
= operations
[len(operations
) - 1][0]
2579 node
.operation
= operations
[index
- 1][0]
2584 class NWChangeMixFactor(Operator
, NWBase
):
2585 bl_idname
= "node.nw_factor"
2586 bl_label
= "Change Factor"
2587 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2588 bl_options
= {'REGISTER', 'UNDO'}
2590 # option: Change factor.
2591 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2592 # Else - change factor by option value.
2593 option
: FloatProperty()
2595 def execute(self
, context
):
2596 nodes
, links
= get_nodes_links(context
)
2597 option
= self
.option
2598 selected
= [] # entry = index
2599 for si
, node
in enumerate(nodes
):
2601 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2605 fac
= nodes
[si
].inputs
[0]
2606 nodes
[si
].hide
= False
2607 if option
in {0.0, 1.0}:
2608 fac
.default_value
= option
2610 fac
.default_value
+= option
2615 class NWCopySettings(Operator
, NWBase
):
2616 bl_idname
= "node.nw_copy_settings"
2617 bl_label
= "Copy Settings"
2618 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2619 bl_options
= {'REGISTER', 'UNDO'}
2622 def poll(cls
, context
):
2624 if nw_check(context
):
2626 context
.active_node
is not None and
2627 context
.active_node
.type != 'FRAME'
2632 def execute(self
, context
):
2633 node_active
= context
.active_node
2634 node_selected
= context
.selected_nodes
2637 if not (len(node_selected
) > 1):
2638 self
.report({'ERROR'}, "2 nodes must be selected at least")
2639 return {'CANCELLED'}
2641 # Check if active node is in the selection
2642 selected_node_names
= [n
.name
for n
in node_selected
]
2643 if node_active
.name
not in selected_node_names
:
2644 self
.report({'ERROR'}, "No active node")
2645 return {'CANCELLED'}
2647 # Get nodes in selection by type
2648 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2650 if not (len(valid_nodes
) > 1) and node_active
:
2651 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2652 return {'CANCELLED'}
2654 if len(valid_nodes
) != len(node_selected
):
2655 # Report nodes that are not valid
2656 valid_node_names
= [n
.name
for n
in valid_nodes
]
2657 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2658 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2660 # Reference original
2662 #node_selected_names = [n.name for n in node_selected]
2667 # Deselect all nodes
2668 for i
in node_selected
:
2671 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2672 # Run through all other nodes
2673 for node
in valid_nodes
[1:]:
2675 # Check for frame node
2676 parent
= node
.parent
if node
.parent
else None
2677 node_loc
= [node
.location
.x
, node
.location
.y
]
2679 # Select original to duplicate
2682 # Duplicate selected node
2683 bpy
.ops
.node
.duplicate()
2684 new_node
= context
.selected_nodes
[0]
2687 new_node
.select
= False
2689 # Properties to copy
2690 node_tree
= node
.id_data
2691 props_to_copy
= 'bl_idname name location height width'.split(' ')
2695 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2696 for i
in (i
for i
in mappings
if i
.is_linked
):
2698 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2701 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2702 props_to_copy
.pop(0)
2704 for prop
in props_to_copy
:
2705 setattr(new_node
, prop
, props
[prop
])
2707 # Get the node tree to remove the old node
2708 nodes
= node_tree
.nodes
2710 new_node
.name
= props
['name']
2713 new_node
.parent
= parent
2714 new_node
.location
= node_loc
2716 for str_from
, str_to
in reconnections
:
2717 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2719 success_names
.append(new_node
.name
)
2722 node_tree
.nodes
.active
= orig
2723 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2727 class NWCopyLabel(Operator
, NWBase
):
2728 bl_idname
= "node.nw_copy_label"
2729 bl_label
= "Copy Label"
2730 bl_options
= {'REGISTER', 'UNDO'}
2732 option
: EnumProperty(
2734 description
="Source of name of label",
2736 ('FROM_ACTIVE', 'from active', 'from active node',),
2737 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2738 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2742 def execute(self
, context
):
2743 nodes
, links
= get_nodes_links(context
)
2744 option
= self
.option
2745 active
= nodes
.active
2746 if option
== 'FROM_ACTIVE':
2748 src_label
= active
.label
2749 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2750 node
.label
= src_label
2751 elif option
== 'FROM_NODE':
2752 selected
= [n
for n
in nodes
if n
.select
]
2753 for node
in selected
:
2754 for input in node
.inputs
:
2756 src
= input.links
[0].from_node
2757 node
.label
= src
.label
2759 elif option
== 'FROM_SOCKET':
2760 selected
= [n
for n
in nodes
if n
.select
]
2761 for node
in selected
:
2762 for input in node
.inputs
:
2764 src
= input.links
[0].from_socket
2765 node
.label
= src
.name
2771 class NWClearLabel(Operator
, NWBase
):
2772 bl_idname
= "node.nw_clear_label"
2773 bl_label
= "Clear Label"
2774 bl_options
= {'REGISTER', 'UNDO'}
2776 option
: BoolProperty()
2778 def execute(self
, context
):
2779 nodes
, links
= get_nodes_links(context
)
2780 for node
in [n
for n
in nodes
if n
.select
]:
2785 def invoke(self
, context
, event
):
2787 return self
.execute(context
)
2789 return context
.window_manager
.invoke_confirm(self
, event
)
2792 class NWModifyLabels(Operator
, NWBase
):
2793 """Modify Labels of all selected nodes"""
2794 bl_idname
= "node.nw_modify_labels"
2795 bl_label
= "Modify Labels"
2796 bl_options
= {'REGISTER', 'UNDO'}
2798 prepend
: StringProperty(
2799 name
="Add to Beginning"
2801 append
: StringProperty(
2804 replace_from
: StringProperty(
2805 name
="Text to Replace"
2807 replace_to
: StringProperty(
2811 def execute(self
, context
):
2812 nodes
, links
= get_nodes_links(context
)
2813 for node
in [n
for n
in nodes
if n
.select
]:
2814 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2818 def invoke(self
, context
, event
):
2822 return context
.window_manager
.invoke_props_dialog(self
)
2825 class NWAddTextureSetup(Operator
, NWBase
):
2826 bl_idname
= "node.nw_add_texture"
2827 bl_label
= "Texture Setup"
2828 bl_description
= "Add Texture Node Setup to Selected Shaders"
2829 bl_options
= {'REGISTER', 'UNDO'}
2831 add_mapping
: BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2834 def poll(cls
, context
):
2836 if nw_check(context
):
2837 space
= context
.space_data
2838 if space
.tree_type
== 'ShaderNodeTree':
2842 def execute(self
, context
):
2843 nodes
, links
= get_nodes_links(context
)
2844 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2845 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2846 selected_nodes
= [n
for n
in nodes
if n
.select
]
2847 for t_node
in selected_nodes
:
2851 for index
, i
in enumerate(t_node
.inputs
):
2857 locx
= t_node
.location
.x
2858 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2860 xoffset
= [500, 700]
2862 if t_node
.type in texture_types
+ ['MAPPING']:
2863 xoffset
= [290, 500]
2867 image_type
= 'ShaderNodeTexImage'
2869 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2870 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2871 if t_node
.type == 'BACKGROUND':
2872 image_type
= 'ShaderNodeTexEnvironment'
2875 tex
= nodes
.new(image_type
)
2876 tex
.location
= [locx
- 200, locy
+ 112]
2878 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2880 t_node
.select
= False
2881 if self
.add_mapping
or is_texture
:
2882 if t_node
.type != 'MAPPING':
2883 m
= nodes
.new('ShaderNodeMapping')
2884 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2888 coord
= nodes
.new('ShaderNodeTexCoord')
2889 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2892 links
.new(m
.outputs
[0], tex
.inputs
[0])
2893 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2896 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2897 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2899 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2903 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2904 bl_idname
= "node.nw_add_textures_for_principled"
2905 bl_label
= "Principled Texture Setup"
2906 bl_description
= "Add Texture Node Setup for Principled BSDF"
2907 bl_options
= {'REGISTER', 'UNDO'}
2909 directory
: StringProperty(
2913 description
='Folder to search in for image files'
2915 files
: CollectionProperty(
2916 type=bpy
.types
.OperatorFileListElement
,
2917 options
={'HIDDEN', 'SKIP_SAVE'}
2920 relative_path
: BoolProperty(
2921 name
='Relative Path',
2922 description
='Select the file relative to the blend file',
2931 def draw(self
, context
):
2932 layout
= self
.layout
2933 layout
.alignment
= 'LEFT'
2935 layout
.prop(self
, 'relative_path')
2938 def poll(cls
, context
):
2940 if nw_check(context
):
2941 space
= context
.space_data
2942 if space
.tree_type
== 'ShaderNodeTree':
2946 def execute(self
, context
):
2947 # Check if everything is ok
2948 if not self
.directory
:
2949 self
.report({'INFO'}, 'No Folder Selected')
2950 return {'CANCELLED'}
2951 if not self
.files
[:]:
2952 self
.report({'INFO'}, 'No Files Selected')
2953 return {'CANCELLED'}
2955 nodes
, links
= get_nodes_links(context
)
2956 active_node
= nodes
.active
2957 if not (active_node
and active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled'):
2958 self
.report({'INFO'}, 'Select Principled BSDF')
2959 return {'CANCELLED'}
2962 def split_into__components(fname
):
2963 # Split filename into components
2964 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2966 fname
= path
.splitext(fname
)[0]
2968 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2969 # Separate CamelCase by space
2970 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2971 # Replace common separators with SPACE
2972 seperators
= ['_', '.', '-', '__', '--', '#']
2973 for sep
in seperators
:
2974 fname
= fname
.replace(sep
, ' ')
2976 components
= fname
.split(' ')
2977 components
= [c
.lower() for c
in components
]
2980 # Filter textures names for texturetypes in filenames
2981 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2982 tags
= context
.preferences
.addons
[__name__
].preferences
.principled_tags
2983 normal_abbr
= tags
.normal
.split(' ')
2984 bump_abbr
= tags
.bump
.split(' ')
2985 gloss_abbr
= tags
.gloss
.split(' ')
2986 rough_abbr
= tags
.rough
.split(' ')
2988 ['Displacement', tags
.displacement
.split(' '), None],
2989 ['Base Color', tags
.base_color
.split(' '), None],
2990 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2991 ['Metallic', tags
.metallic
.split(' '), None],
2992 ['Specular', tags
.specular
.split(' '), None],
2993 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2994 ['Normal', normal_abbr
+ bump_abbr
, None],
2997 # Look through texture_types and set value as filename of first matched file
2998 def match_files_to_socket_names():
2999 for sname
in socketnames
:
3000 for file in self
.files
:
3002 filenamecomponents
= split_into__components(fname
)
3003 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
3004 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
3009 match_files_to_socket_names()
3010 # Remove socketnames without found files
3011 socketnames
= [s
for s
in socketnames
if s
[2]
3012 and path
.exists(self
.directory
+s
[2])]
3014 self
.report({'INFO'}, 'No matching images found')
3015 print('No matching images found')
3016 return {'CANCELLED'}
3018 # Don't override path earlier as os.path is used to check the absolute path
3019 import_path
= self
.directory
3020 if self
.relative_path
:
3021 if bpy
.data
.filepath
:
3022 import_path
= bpy
.path
.relpath(self
.directory
)
3024 self
.report({'WARNING'}, 'Relative paths cannot be used with unsaved scenes!')
3025 print('Relative paths cannot be used with unsaved scenes!')
3028 print('\nMatched Textures:')
3032 roughness_node
= None
3033 for i
, sname
in enumerate(socketnames
):
3034 print(i
, sname
[0], sname
[2])
3036 # DISPLACEMENT NODES
3037 if sname
[0] == 'Displacement':
3038 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
3039 img
= bpy
.data
.images
.load(path
.join(import_path
, sname
[2]))
3040 disp_texture
.image
= img
3041 disp_texture
.label
= 'Displacement'
3042 if disp_texture
.image
:
3043 disp_texture
.image
.colorspace_settings
.is_data
= True
3045 # Add displacement offset nodes
3046 disp_node
= nodes
.new(type='ShaderNodeDisplacement')
3047 disp_node
.location
= active_node
.location
+ Vector((0, -560))
3048 link
= links
.new(disp_node
.inputs
[0], disp_texture
.outputs
[0])
3050 # TODO Turn on true displacement in the material
3051 # Too complicated for now
3054 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
3056 if not output_node
[0].inputs
[2].is_linked
:
3057 link
= links
.new(output_node
[0].inputs
[2], disp_node
.outputs
[0])
3061 if not active_node
.inputs
[sname
[0]].is_linked
:
3062 # No texture node connected -> add texture node with new image
3063 texture_node
= nodes
.new(type='ShaderNodeTexImage')
3064 img
= bpy
.data
.images
.load(path
.join(import_path
, sname
[2]))
3065 texture_node
.image
= img
3068 if sname
[0] == 'Normal':
3069 # Test if new texture node is normal or bump map
3070 fname_components
= split_into__components(sname
[2])
3071 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
3072 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
3074 # If Normal add normal node in between
3075 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
3076 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
3078 # If Bump add bump node in between
3079 normal_node
= nodes
.new(type='ShaderNodeBump')
3080 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
3082 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
3083 normal_node_texture
= texture_node
3085 elif sname
[0] == 'Roughness':
3086 # Test if glossy or roughness map
3087 fname_components
= split_into__components(sname
[2])
3088 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
3089 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
3092 # If Roughness nothing to to
3093 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
3096 # If Gloss Map add invert node
3097 invert_node
= nodes
.new(type='ShaderNodeInvert')
3098 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
3100 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
3101 roughness_node
= texture_node
3104 # This is a simple connection Texture --> Input slot
3105 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
3107 # Use non-color for all but 'Base Color' Textures
3108 if not sname
[0] in ['Base Color'] and texture_node
.image
:
3109 texture_node
.image
.colorspace_settings
.is_data
= True
3112 # If already texture connected. add to node list for alignment
3113 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
3115 # This are all connected texture nodes
3116 texture_nodes
.append(texture_node
)
3117 texture_node
.label
= sname
[0]
3120 texture_nodes
.append(disp_texture
)
3123 for i
, texture_node
in enumerate(texture_nodes
):
3124 offset
= Vector((-550, (i
* -280) + 200))
3125 texture_node
.location
= active_node
.location
+ offset
3128 # Extra alignment if normal node was added
3129 normal_node
.location
= normal_node_texture
.location
+ Vector((300, 0))
3132 # Alignment of invert node if glossy map
3133 invert_node
.location
= roughness_node
.location
+ Vector((300, 0))
3135 # Add texture input + mapping
3136 mapping
= nodes
.new(type='ShaderNodeMapping')
3137 mapping
.location
= active_node
.location
+ Vector((-1050, 0))
3138 if len(texture_nodes
) > 1:
3139 # If more than one texture add reroute node in between
3140 reroute
= nodes
.new(type='NodeReroute')
3141 texture_nodes
.append(reroute
)
3142 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
3143 reroute
.location
= tex_coords
+ Vector((-50, -120))
3144 for texture_node
in texture_nodes
:
3145 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
3146 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
3148 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
3150 # Connect texture_coordiantes to mapping node
3151 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
3152 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
3153 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
3155 # Create frame around tex coords and mapping
3156 frame
= nodes
.new(type='NodeFrame')
3157 frame
.label
= 'Mapping'
3158 mapping
.parent
= frame
3159 texture_input
.parent
= frame
3162 # Create frame around texture nodes
3163 frame
= nodes
.new(type='NodeFrame')
3164 frame
.label
= 'Textures'
3165 for tnode
in texture_nodes
:
3166 tnode
.parent
= frame
3170 active_node
.select
= False
3173 force_update(context
)
3177 class NWAddReroutes(Operator
, NWBase
):
3178 """Add Reroute Nodes and link them to outputs of selected nodes"""
3179 bl_idname
= "node.nw_add_reroutes"
3180 bl_label
= "Add Reroutes"
3181 bl_description
= "Add Reroutes to Outputs"
3182 bl_options
= {'REGISTER', 'UNDO'}
3184 option
: EnumProperty(
3187 ('ALL', 'to all', 'Add to all outputs'),
3188 ('LOOSE', 'to loose', 'Add only to loose outputs'),
3189 ('LINKED', 'to linked', 'Add only to linked outputs'),
3193 def execute(self
, context
):
3194 tree_type
= context
.space_data
.node_tree
.type
3195 option
= self
.option
3196 nodes
, links
= get_nodes_links(context
)
3197 # output valid when option is 'all' or when 'loose' output has no links
3199 post_select
= [] # nodes to be selected after execution
3200 # create reroutes and recreate links
3201 for node
in [n
for n
in nodes
if n
.select
]:
3206 # unhide 'REROUTE' nodes to avoid issues with location.y
3207 if node
.type == 'REROUTE':
3209 # When node is hidden - width_hidden not usable.
3210 # Hack needed to calculate real width
3212 bpy
.ops
.node
.select_all(action
='DESELECT')
3213 helper
= nodes
.new('NodeReroute')
3214 helper
.select
= True
3216 # resize node and helper to zero. Then check locations to calculate width
3217 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
3218 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
3219 # restore node location
3220 node
.location
= x
, y
3223 # only helper is selected now
3224 bpy
.ops
.node
.delete()
3225 x
= node
.location
.x
+ width
+ 20.0
3226 if node
.type != 'REROUTE':
3230 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
3231 for out_i
, output
in enumerate(node
.outputs
):
3232 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
3233 # if node != 'R_LAYERS' - "pass_used" not needed, so set it to True
3234 if node
.type != 'R_LAYERS':
3236 else: # if 'R_LAYERS' check if output represent used render pass
3237 node_scene
= node
.scene
3238 node_layer
= node
.layer
3239 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
3240 if output
.name
== 'Alpha':
3243 # check entries in global 'rl_outputs' variable
3244 for rlo
in rl_outputs
:
3245 if output
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
3246 pass_used
= getattr(node_scene
.view_layers
[node_layer
], rlo
.render_pass
)
3249 valid
= ((option
== 'ALL') or
3250 (option
== 'LOOSE' and not output
.links
) or
3251 (option
== 'LINKED' and output
.links
))
3252 # Add reroutes only if valid, but offset location in all cases.
3254 n
= nodes
.new('NodeReroute')
3256 for link
in output
.links
:
3257 links
.new(n
.outputs
[0], link
.to_socket
)
3258 links
.new(output
, n
.inputs
[0])
3260 post_select
.append(n
)
3264 # disselect the node so that after execution of script only newly created nodes are selected
3266 # nicer reroutes distribution along y when node.hide
3268 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
3269 for reroute
in [r
for r
in nodes
if r
.select
]:
3270 reroute
.location
.y
-= y_translate
3271 for node
in post_select
:
3277 class NWLinkActiveToSelected(Operator
, NWBase
):
3278 """Link active node to selected nodes basing on various criteria"""
3279 bl_idname
= "node.nw_link_active_to_selected"
3280 bl_label
= "Link Active Node to Selected"
3281 bl_options
= {'REGISTER', 'UNDO'}
3283 replace
: BoolProperty()
3284 use_node_name
: BoolProperty()
3285 use_outputs_names
: BoolProperty()
3288 def poll(cls
, context
):
3290 if nw_check(context
):
3291 if context
.active_node
is not None:
3292 if context
.active_node
.select
:
3296 def execute(self
, context
):
3297 nodes
, links
= get_nodes_links(context
)
3298 replace
= self
.replace
3299 use_node_name
= self
.use_node_name
3300 use_outputs_names
= self
.use_outputs_names
3301 active
= nodes
.active
3302 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3303 outputs
= [] # Only usable outputs of active nodes will be stored here.
3304 for out
in active
.outputs
:
3305 if active
.type != 'R_LAYERS':
3308 # 'R_LAYERS' node type needs special handling.
3309 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3310 # Only outputs that represent used passes should be taken into account
3311 # Check if pass represented by output is used.
3312 # global 'rl_outputs' list will be used for that
3313 for rlo
in rl_outputs
:
3314 pass_used
= False # initial value. Will be set to True if pass is used
3315 if out
.name
== 'Alpha':
3316 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3318 elif out
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
3319 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3320 pass_used
= getattr(active
.scene
.view_layers
[active
.layer
], rlo
.render_pass
)
3324 doit
= True # Will be changed to False when links successfully added to previous output.
3327 for node
in selected
:
3328 dst_name
= node
.name
# Will be compared with src_name if needed.
3329 # When node has label - use it as dst_name
3331 dst_name
= node
.label
3332 valid
= True # Initial value. Will be changed to False if names don't match.
3333 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3335 # Set src_name to source node name or label
3336 src_name
= active
.name
3338 src_name
= active
.label
3339 elif use_outputs_names
:
3340 src_name
= (out
.name
, )
3341 for rlo
in rl_outputs
:
3342 if out
.name
in {rlo
.output_name
, rlo
.exr_output_name
}:
3343 src_name
= (rlo
.output_name
, rlo
.exr_output_name
)
3344 if dst_name
not in src_name
:
3347 for input in node
.inputs
:
3348 if input.type == out
.type or node
.type == 'REROUTE':
3349 if replace
or not input.is_linked
:
3350 links
.new(out
, input)
3351 if not use_node_name
and not use_outputs_names
:
3358 class NWAlignNodes(Operator
, NWBase
):
3359 '''Align the selected nodes neatly in a row/column'''
3360 bl_idname
= "node.nw_align_nodes"
3361 bl_label
= "Align Nodes"
3362 bl_options
= {'REGISTER', 'UNDO'}
3363 margin
: IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3365 def execute(self
, context
):
3366 nodes
, links
= get_nodes_links(context
)
3367 margin
= self
.margin
3371 if node
.select
and node
.type != 'FRAME':
3372 selection
.append(node
)
3374 # If no nodes are selected, align all nodes
3378 elif nodes
.active
in selection
:
3379 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3381 # Check if nodes should be laid out horizontally or vertically
3382 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3383 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3384 x_range
= max(x_locs
) - min(x_locs
)
3385 y_range
= max(y_locs
) - min(y_locs
)
3386 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3387 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3388 horizontal
= x_range
> y_range
3390 # Sort selection by location of node mid-point
3392 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3394 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3398 for node
in selection
:
3399 current_margin
= margin
3400 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3403 node
.location
.x
= current_pos
3404 current_pos
+= current_margin
+ node
.dimensions
.x
3405 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3407 node
.location
.y
= current_pos
3408 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3409 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3411 # If active node is selected, center nodes around it
3412 if active_loc
is not None:
3413 active_loc_diff
= active_loc
- nodes
.active
.location
3414 for node
in selection
:
3415 node
.location
+= active_loc_diff
3416 else: # Position nodes centered around where they used to be
3417 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
])
3418 new_mid
= (max(locs
) + min(locs
)) / 2
3419 for node
in selection
:
3421 node
.location
.x
+= (mid_x
- new_mid
)
3423 node
.location
.y
+= (mid_y
- new_mid
)
3428 class NWSelectParentChildren(Operator
, NWBase
):
3429 bl_idname
= "node.nw_select_parent_child"
3430 bl_label
= "Select Parent or Children"
3431 bl_options
= {'REGISTER', 'UNDO'}
3433 option
: EnumProperty(
3436 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3437 ('CHILD', 'Select Children', 'Select members of selected frame'),
3441 def execute(self
, context
):
3442 nodes
, links
= get_nodes_links(context
)
3443 option
= self
.option
3444 selected
= [node
for node
in nodes
if node
.select
]
3445 if option
== 'PARENT':
3446 for sel
in selected
:
3449 parent
.select
= True
3450 else: # option == 'CHILD'
3451 for sel
in selected
:
3452 children
= [node
for node
in nodes
if node
.parent
== sel
]
3453 for kid
in children
:
3459 class NWDetachOutputs(Operator
, NWBase
):
3460 """Detach outputs of selected node leaving inputs linked"""
3461 bl_idname
= "node.nw_detach_outputs"
3462 bl_label
= "Detach Outputs"
3463 bl_options
= {'REGISTER', 'UNDO'}
3465 def execute(self
, context
):
3466 nodes
, links
= get_nodes_links(context
)
3467 selected
= context
.selected_nodes
3468 bpy
.ops
.node
.duplicate_move_keep_inputs()
3469 new_nodes
= context
.selected_nodes
3470 bpy
.ops
.node
.select_all(action
="DESELECT")
3471 for node
in selected
:
3473 bpy
.ops
.node
.delete_reconnect()
3474 for new_node
in new_nodes
:
3475 new_node
.select
= True
3476 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3481 class NWLinkToOutputNode(Operator
, NWBase
):
3482 """Link to Composite node or Material Output node"""
3483 bl_idname
= "node.nw_link_out"
3484 bl_label
= "Connect to Output"
3485 bl_options
= {'REGISTER', 'UNDO'}
3488 def poll(cls
, context
):
3490 if nw_check(context
):
3491 if context
.active_node
is not None:
3492 for out
in context
.active_node
.outputs
:
3493 if is_visible_socket(out
):
3498 def execute(self
, context
):
3499 nodes
, links
= get_nodes_links(context
)
3500 active
= nodes
.active
3503 tree_type
= context
.space_data
.tree_type
3504 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3505 output_types_compo
= ['COMPOSITE']
3506 output_types_blender_mat
= ['OUTPUT']
3507 output_types_textures
= ['OUTPUT']
3508 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3510 if node
.type in output_types
:
3514 bpy
.ops
.node
.select_all(action
="DESELECT")
3515 if tree_type
== 'ShaderNodeTree':
3516 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3517 elif tree_type
== 'CompositorNodeTree':
3518 output_node
= nodes
.new('CompositorNodeComposite')
3519 elif tree_type
== 'TextureNodeTree':
3520 output_node
= nodes
.new('TextureNodeOutput')
3521 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3522 output_node
.location
.y
= active
.location
.y
3523 if (output_node
and active
.outputs
):
3524 for i
, output
in enumerate(active
.outputs
):
3525 if is_visible_socket(output
):
3528 for i
, output
in enumerate(active
.outputs
):
3529 if output
.type == output_node
.inputs
[0].type and is_visible_socket(output
):
3534 if tree_type
== 'ShaderNodeTree':
3535 if active
.outputs
[output_index
].name
== 'Volume':
3537 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3539 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3541 force_update(context
) # viewport render does not update
3546 class NWMakeLink(Operator
, NWBase
):
3547 """Make a link from one socket to another"""
3548 bl_idname
= 'node.nw_make_link'
3549 bl_label
= 'Make Link'
3550 bl_options
= {'REGISTER', 'UNDO'}
3551 from_socket
: IntProperty()
3552 to_socket
: IntProperty()
3554 def execute(self
, context
):
3555 nodes
, links
= get_nodes_links(context
)
3557 n1
= nodes
[context
.scene
.NWLazySource
]
3558 n2
= nodes
[context
.scene
.NWLazyTarget
]
3560 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3562 force_update(context
)
3567 class NWCallInputsMenu(Operator
, NWBase
):
3568 """Link from this output"""
3569 bl_idname
= 'node.nw_call_inputs_menu'
3570 bl_label
= 'Make Link'
3571 bl_options
= {'REGISTER', 'UNDO'}
3572 from_socket
: IntProperty()
3574 def execute(self
, context
):
3575 nodes
, links
= get_nodes_links(context
)
3577 context
.scene
.NWSourceSocket
= self
.from_socket
3579 n1
= nodes
[context
.scene
.NWLazySource
]
3580 n2
= nodes
[context
.scene
.NWLazyTarget
]
3581 if len(n2
.inputs
) > 1:
3582 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3583 elif len(n2
.inputs
) == 1:
3584 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3588 class NWAddSequence(Operator
, NWBase
, ImportHelper
):
3589 """Add an Image Sequence"""
3590 bl_idname
= 'node.nw_add_sequence'
3591 bl_label
= 'Import Image Sequence'
3592 bl_options
= {'REGISTER', 'UNDO'}
3594 directory
: StringProperty(
3597 filename
: StringProperty(
3600 files
: CollectionProperty(
3601 type=bpy
.types
.OperatorFileListElement
,
3602 options
={'HIDDEN', 'SKIP_SAVE'}
3605 def execute(self
, context
):
3606 nodes
, links
= get_nodes_links(context
)
3607 directory
= self
.directory
3608 filename
= self
.filename
3610 tree
= context
.space_data
.node_tree
3613 # print ("\nDIR:", directory)
3614 # print ("FN:", filename)
3615 # print ("Fs:", list(f.name for f in files), '\n')
3617 if tree
.type == 'SHADER':
3618 node_type
= "ShaderNodeTexImage"
3619 elif tree
.type == 'COMPOSITING':
3620 node_type
= "CompositorNodeImage"
3622 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3623 return {'CANCELLED'}
3625 if not files
[0].name
and not filename
:
3626 self
.report({'ERROR'}, "No file chosen")
3627 return {'CANCELLED'}
3628 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3629 # User has selected multiple files without an active one, or the active one is non-existant
3630 filename
= files
[0].name
3632 if not path
.exists(directory
+filename
):
3633 self
.report({'ERROR'}, filename
+" does not exist!")
3634 return {'CANCELLED'}
3636 without_ext
= '.'.join(filename
.split('.')[:-1])
3638 # if last digit isn't a number, it's not a sequence
3639 if not without_ext
[-1].isdigit():
3640 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3641 return {'CANCELLED'}
3644 extension
= filename
.split('.')[-1]
3645 reverse
= without_ext
[::-1] # reverse string
3648 for char
in reverse
:
3654 without_num
= without_ext
[:count_numbers
*-1]
3656 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3658 num_frames
= len(files
)
3660 nodes_list
= [node
for node
in nodes
]
3662 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3663 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3667 yloc
+= node_mid_pt(node
, 'y')
3668 yloc
= yloc
/len(nodes
)
3673 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3675 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3677 node
.label
= name_with_hashes
3679 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3680 img
.source
= 'SEQUENCE'
3681 img
.name
= name_with_hashes
3683 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3684 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
3685 image_user
.frame_duration
= num_frames
3690 class NWAddMultipleImages(Operator
, NWBase
, ImportHelper
):
3691 """Add multiple images at once"""
3692 bl_idname
= 'node.nw_add_multiple_images'
3693 bl_label
= 'Open Selected Images'
3694 bl_options
= {'REGISTER', 'UNDO'}
3695 directory
: StringProperty(
3698 files
: CollectionProperty(
3699 type=bpy
.types
.OperatorFileListElement
,
3700 options
={'HIDDEN', 'SKIP_SAVE'}
3703 def execute(self
, context
):
3704 nodes
, links
= get_nodes_links(context
)
3706 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3708 if context
.space_data
.node_tree
.type == 'SHADER':
3709 node_type
= "ShaderNodeTexImage"
3710 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3711 node_type
= "CompositorNodeImage"
3713 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3714 return {'CANCELLED'}
3717 for f
in self
.files
:
3720 node
= nodes
.new(node_type
)
3721 new_nodes
.append(node
)
3724 node
.width_hidden
= 100
3725 node
.location
.x
= xloc
3726 node
.location
.y
= yloc
3729 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3732 # shift new nodes up to center of tree
3733 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3735 if node
in new_nodes
:
3737 node
.location
.y
+= (list_size
/2)
3743 class NWViewerFocus(bpy
.types
.Operator
):
3744 """Set the viewer tile center to the mouse position"""
3745 bl_idname
= "node.nw_viewer_focus"
3746 bl_label
= "Viewer Focus"
3748 x
: bpy
.props
.IntProperty()
3749 y
: bpy
.props
.IntProperty()
3752 def poll(cls
, context
):
3753 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3755 def execute(self
, context
):
3758 def invoke(self
, context
, event
):
3759 render
= context
.scene
.render
3760 space
= context
.space_data
3761 percent
= render
.resolution_percentage
*0.01
3763 nodes
, links
= get_nodes_links(context
)
3764 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3767 mlocx
= event
.mouse_region_x
3768 mlocy
= event
.mouse_region_y
3769 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3771 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3772 region_x
= context
.region
.width
3773 region_y
= context
.region
.height
3775 region_center_x
= context
.region
.width
/ 2
3776 region_center_y
= context
.region
.height
/ 2
3778 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3779 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3781 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_offset
[0]
3782 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_offset
[1]
3784 margin_x
= region_center_x
- backdrop_center_x
3785 margin_y
= region_center_y
- backdrop_center_y
3787 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3788 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3790 for node
in viewers
:
3791 node
.center_x
= abs_mouse_x
3792 node
.center_y
= abs_mouse_y
3794 return {'PASS_THROUGH'}
3796 return self
.execute(context
)
3799 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3800 """Save the current viewer node to an image file"""
3801 bl_idname
= "node.nw_save_viewer"
3802 bl_label
= "Save This Image"
3803 filepath
: StringProperty(subtype
="FILE_PATH")
3804 filename_ext
: EnumProperty(
3806 description
="Choose the file format to save to",
3807 items
=(('.bmp', "BMP", ""),
3808 ('.rgb', 'IRIS', ""),
3809 ('.png', 'PNG', ""),
3810 ('.jpg', 'JPEG', ""),
3811 ('.jp2', 'JPEG2000', ""),
3812 ('.tga', 'TARGA', ""),
3813 ('.cin', 'CINEON', ""),
3814 ('.dpx', 'DPX', ""),
3815 ('.exr', 'OPEN_EXR', ""),
3816 ('.hdr', 'HDR', ""),
3817 ('.tif', 'TIFF', "")),
3822 def poll(cls
, context
):
3824 if nw_check(context
):
3825 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3826 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3827 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3831 def execute(self
, context
):
3848 basename
, ext
= path
.splitext(fp
)
3849 old_render_format
= context
.scene
.render
.image_settings
.file_format
3850 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3851 context
.area
.type = "IMAGE_EDITOR"
3852 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3853 context
.area
.spaces
[0].image
.save_render(fp
)
3854 context
.area
.type = "NODE_EDITOR"
3855 context
.scene
.render
.image_settings
.file_format
= old_render_format
3859 class NWResetNodes(bpy
.types
.Operator
):
3860 """Reset Nodes in Selection"""
3861 bl_idname
= "node.nw_reset_nodes"
3862 bl_label
= "Reset Nodes"
3863 bl_options
= {'REGISTER', 'UNDO'}
3866 def poll(cls
, context
):
3867 space
= context
.space_data
3868 return space
.type == 'NODE_EDITOR'
3870 def execute(self
, context
):
3871 node_active
= context
.active_node
3872 node_selected
= context
.selected_nodes
3873 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3875 # Check if one node is selected at least
3876 if not (len(node_selected
) > 0):
3877 self
.report({'ERROR'}, "1 node must be selected at least")
3878 return {'CANCELLED'}
3880 active_node_name
= node_active
.name
if node_active
.select
else None
3881 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3883 # Create output lists
3884 selected_node_names
= [n
.name
for n
in node_selected
]
3887 # Reset all valid children in a frame
3888 node_active_is_frame
= False
3889 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3890 node_tree
= node_active
.id_data
3891 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3893 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3894 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3895 node_active_is_frame
= True
3897 # Check if valid nodes in selection
3898 if not (len(valid_nodes
) > 0):
3899 # Check for frames only
3900 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3901 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3902 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3904 self
.report({'ERROR'}, "No valid node(s) in selection")
3905 return {'CANCELLED'}
3907 # Report nodes that are not valid
3908 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3909 valid_node_names
= [n
.name
for n
in valid_nodes
]
3910 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3911 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3913 # Deselect all nodes
3914 for i
in node_selected
:
3917 # Run through all valid nodes
3918 for node
in valid_nodes
:
3920 parent
= node
.parent
if node
.parent
else None
3921 node_loc
= [node
.location
.x
, node
.location
.y
]
3923 node_tree
= node
.id_data
3924 props_to_copy
= 'bl_idname name location height width'.split(' ')
3927 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3928 for i
in (i
for i
in mappings
if i
.is_linked
):
3930 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3932 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3934 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3935 props_to_copy
.pop(0)
3937 for prop
in props_to_copy
:
3938 setattr(new_node
, prop
, props
[prop
])
3940 nodes
= node_tree
.nodes
3942 new_node
.name
= props
['name']
3945 new_node
.parent
= parent
3946 new_node
.location
= node_loc
3948 for str_from
, str_to
in reconnections
:
3949 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3951 new_node
.select
= False
3952 success_names
.append(new_node
.name
)
3954 # Reselect all nodes
3955 if selected_node_names
and node_active_is_frame
is False:
3956 for i
in selected_node_names
:
3957 node_tree
.nodes
[i
].select
= True
3959 if active_node_name
is not None:
3960 node_tree
.nodes
[active_node_name
].select
= True
3961 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3963 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3971 def drawlayout(context
, layout
, mode
='non-panel'):
3972 tree_type
= context
.space_data
.tree_type
3974 col
= layout
.column(align
=True)
3975 col
.menu(NWMergeNodesMenu
.bl_idname
)
3978 col
= layout
.column(align
=True)
3979 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3982 if tree_type
== 'ShaderNodeTree':
3983 col
= layout
.column(align
=True)
3984 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3985 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3988 col
= layout
.column(align
=True)
3989 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3990 col
.operator(NWSwapLinks
.bl_idname
)
3991 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3994 col
= layout
.column(align
=True)
3995 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3996 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3999 col
= layout
.column(align
=True)
4001 row
= col
.row(align
=True)
4002 row
.operator(NWClearLabel
.bl_idname
).option
= True
4003 row
.operator(NWModifyLabels
.bl_idname
)
4005 col
.operator(NWClearLabel
.bl_idname
).option
= True
4006 col
.operator(NWModifyLabels
.bl_idname
)
4007 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
4009 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
4012 col
= layout
.column(align
=True)
4013 if tree_type
== 'CompositorNodeTree':
4014 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
4015 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
4018 col
= layout
.column(align
=True)
4019 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
4022 col
= layout
.column(align
=True)
4023 col
.operator(NWAlignNodes
.bl_idname
, icon
='CENTER_ONLY')
4026 col
= layout
.column(align
=True)
4027 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
4031 class NodeWranglerPanel(Panel
, NWBase
):
4032 bl_idname
= "NODE_PT_nw_node_wrangler"
4033 bl_space_type
= 'NODE_EDITOR'
4034 bl_label
= "Node Wrangler"
4035 bl_region_type
= "UI"
4036 bl_category
= "Node Wrangler"
4038 prepend
: StringProperty(
4041 append
: StringProperty()
4042 remove
: StringProperty()
4044 def draw(self
, context
):
4045 self
.layout
.label(text
="(Quick access: Shift+W)")
4046 drawlayout(context
, self
.layout
, mode
='panel')
4052 class NodeWranglerMenu(Menu
, NWBase
):
4053 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
4054 bl_label
= "Node Wrangler"
4056 def draw(self
, context
):
4057 self
.layout
.operator_context
= 'INVOKE_DEFAULT'
4058 drawlayout(context
, self
.layout
)
4061 class NWMergeNodesMenu(Menu
, NWBase
):
4062 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
4063 bl_label
= "Merge Selected Nodes"
4065 def draw(self
, context
):
4066 type = context
.space_data
.tree_type
4067 layout
= self
.layout
4068 if type == 'ShaderNodeTree':
4069 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
4070 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
4071 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
4072 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
4074 props
.merge_type
= 'ZCOMBINE'
4075 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
4077 props
.merge_type
= 'ALPHAOVER'
4080 class NWMergeShadersMenu(Menu
, NWBase
):
4081 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
4082 bl_label
= "Merge Selected Nodes using Shaders"
4084 def draw(self
, context
):
4085 layout
= self
.layout
4086 for type in ('MIX', 'ADD'):
4087 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
4089 props
.merge_type
= 'SHADER'
4092 class NWMergeMixMenu(Menu
, NWBase
):
4093 bl_idname
= "NODE_MT_nw_merge_mix_menu"
4094 bl_label
= "Merge Selected Nodes using Mix"
4096 def draw(self
, context
):
4097 layout
= self
.layout
4098 for type, name
, description
in blend_types
:
4099 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
4101 props
.merge_type
= 'MIX'
4104 class NWConnectionListOutputs(Menu
, NWBase
):
4105 bl_idname
= "NODE_MT_nw_connection_list_out"
4108 def draw(self
, context
):
4109 layout
= self
.layout
4110 nodes
, links
= get_nodes_links(context
)
4112 n1
= nodes
[context
.scene
.NWLazySource
]
4114 if n1
.type == "R_LAYERS":
4116 for o
in n1
.outputs
:
4117 if o
.enabled
: # Check which passes the render layer has enabled
4118 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
4122 for o
in n1
.outputs
:
4123 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
4127 class NWConnectionListInputs(Menu
, NWBase
):
4128 bl_idname
= "NODE_MT_nw_connection_list_in"
4131 def draw(self
, context
):
4132 layout
= self
.layout
4133 nodes
, links
= get_nodes_links(context
)
4135 n2
= nodes
[context
.scene
.NWLazyTarget
]
4139 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
4140 op
.from_socket
= context
.scene
.NWSourceSocket
4141 op
.to_socket
= index
4145 class NWMergeMathMenu(Menu
, NWBase
):
4146 bl_idname
= "NODE_MT_nw_merge_math_menu"
4147 bl_label
= "Merge Selected Nodes using Math"
4149 def draw(self
, context
):
4150 layout
= self
.layout
4151 for type, name
, description
in operations
:
4152 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
4154 props
.merge_type
= 'MATH'
4157 class NWBatchChangeNodesMenu(Menu
, NWBase
):
4158 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
4159 bl_label
= "Batch Change Selected Nodes"
4161 def draw(self
, context
):
4162 layout
= self
.layout
4163 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
4164 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
4167 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
4168 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
4169 bl_label
= "Batch Change Blend Type"
4171 def draw(self
, context
):
4172 layout
= self
.layout
4173 for type, name
, description
in blend_types
:
4174 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
4175 props
.blend_type
= type
4176 props
.operation
= 'CURRENT'
4179 class NWBatchChangeOperationMenu(Menu
, NWBase
):
4180 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
4181 bl_label
= "Batch Change Math Operation"
4183 def draw(self
, context
):
4184 layout
= self
.layout
4185 for type, name
, description
in operations
:
4186 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
4187 props
.blend_type
= 'CURRENT'
4188 props
.operation
= type
4191 class NWCopyToSelectedMenu(Menu
, NWBase
):
4192 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
4193 bl_label
= "Copy to Selected"
4195 def draw(self
, context
):
4196 layout
= self
.layout
4197 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
4198 layout
.menu(NWCopyLabelMenu
.bl_idname
)
4201 class NWCopyLabelMenu(Menu
, NWBase
):
4202 bl_idname
= "NODE_MT_nw_copy_label_menu"
4203 bl_label
= "Copy Label"
4205 def draw(self
, context
):
4206 layout
= self
.layout
4207 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
4208 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
4209 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
4212 class NWAddReroutesMenu(Menu
, NWBase
):
4213 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
4214 bl_label
= "Add Reroutes"
4215 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
4217 def draw(self
, context
):
4218 layout
= self
.layout
4219 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
4220 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
4221 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
4224 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
4225 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
4226 bl_label
= "Link Active to Selected"
4228 def draw(self
, context
):
4229 layout
= self
.layout
4230 layout
.menu(NWLinkStandardMenu
.bl_idname
)
4231 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
4232 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
4235 class NWLinkStandardMenu(Menu
, NWBase
):
4236 bl_idname
= "NODE_MT_nw_link_standard_menu"
4237 bl_label
= "To All Selected"
4239 def draw(self
, context
):
4240 layout
= self
.layout
4241 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
4242 props
.replace
= False
4243 props
.use_node_name
= False
4244 props
.use_outputs_names
= False
4245 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4246 props
.replace
= True
4247 props
.use_node_name
= False
4248 props
.use_outputs_names
= False
4251 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
4252 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
4253 bl_label
= "Use Node Name/Label"
4255 def draw(self
, context
):
4256 layout
= self
.layout
4257 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
4258 props
.replace
= False
4259 props
.use_node_name
= True
4260 props
.use_outputs_names
= False
4261 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4262 props
.replace
= True
4263 props
.use_node_name
= True
4264 props
.use_outputs_names
= False
4267 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
4268 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
4269 bl_label
= "Use Outputs Names"
4271 def draw(self
, context
):
4272 layout
= self
.layout
4273 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
4274 props
.replace
= False
4275 props
.use_node_name
= False
4276 props
.use_outputs_names
= True
4277 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
4278 props
.replace
= True
4279 props
.use_node_name
= False
4280 props
.use_outputs_names
= True
4283 class NWVertColMenu(bpy
.types
.Menu
):
4284 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
4285 bl_label
= "Vertex Colors"
4288 def poll(cls
, context
):
4290 if nw_check(context
):
4291 snode
= context
.space_data
4292 valid
= snode
.tree_type
== 'ShaderNodeTree'
4295 def draw(self
, context
):
4297 nodes
, links
= get_nodes_links(context
)
4298 mat
= context
.object.active_material
4301 for obj
in bpy
.data
.objects
:
4302 for slot
in obj
.material_slots
:
4303 if slot
.material
== mat
:
4307 if obj
.data
.vertex_colors
:
4308 for vcol
in obj
.data
.vertex_colors
:
4309 vcols
.append(vcol
.name
)
4310 vcols
= list(set(vcols
)) # get a unique list
4314 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4316 l
.label(text
="No Vertex Color layers on objects with this material")
4319 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4320 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4321 bl_label
= "Switch Type to..."
4323 def draw(self
, context
):
4324 layout
= self
.layout
4325 tree
= context
.space_data
.node_tree
4326 if tree
.type == 'SHADER':
4327 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4328 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4329 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4330 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4331 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4332 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4333 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4334 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4335 if tree
.type == 'COMPOSITING':
4336 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4337 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4338 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4339 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4340 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4341 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4342 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4343 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4344 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4345 if tree
.type == 'TEXTURE':
4346 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4347 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4348 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4349 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4350 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4351 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4352 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4353 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4356 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4357 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4360 def draw(self
, context
):
4361 layout
= self
.layout
4362 for ident
, node_type
, rna_name
in shaders_input_nodes_props
:
4363 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4364 props
.to_type
= ident
4367 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4368 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4371 def draw(self
, context
):
4372 layout
= self
.layout
4373 for ident
, node_type
, rna_name
in shaders_output_nodes_props
:
4374 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4375 props
.to_type
= ident
4378 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4379 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4382 def draw(self
, context
):
4383 layout
= self
.layout
4384 for ident
, node_type
, rna_name
in shaders_shader_nodes_props
:
4385 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4386 props
.to_type
= ident
4389 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4390 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4391 bl_label
= "Texture"
4393 def draw(self
, context
):
4394 layout
= self
.layout
4395 for ident
, node_type
, rna_name
in shaders_texture_nodes_props
:
4396 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4397 props
.to_type
= ident
4400 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4401 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4404 def draw(self
, context
):
4405 layout
= self
.layout
4406 for ident
, node_type
, rna_name
in shaders_color_nodes_props
:
4407 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4408 props
.to_type
= ident
4411 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4412 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4415 def draw(self
, context
):
4416 layout
= self
.layout
4417 for ident
, node_type
, rna_name
in shaders_vector_nodes_props
:
4418 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4419 props
.to_type
= ident
4422 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4423 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4424 bl_label
= "Converter"
4426 def draw(self
, context
):
4427 layout
= self
.layout
4428 for ident
, node_type
, rna_name
in shaders_converter_nodes_props
:
4429 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4430 props
.to_type
= ident
4433 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4434 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4437 def draw(self
, context
):
4438 layout
= self
.layout
4439 for ident
, node_type
, rna_name
in shaders_layout_nodes_props
:
4440 if node_type
!= 'FRAME':
4441 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4442 props
.to_type
= ident
4445 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4446 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4449 def draw(self
, context
):
4450 layout
= self
.layout
4451 for ident
, node_type
, rna_name
in compo_input_nodes_props
:
4452 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4453 props
.to_type
= ident
4456 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4457 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4460 def draw(self
, context
):
4461 layout
= self
.layout
4462 for ident
, node_type
, rna_name
in compo_output_nodes_props
:
4463 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4464 props
.to_type
= ident
4467 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4468 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4471 def draw(self
, context
):
4472 layout
= self
.layout
4473 for ident
, node_type
, rna_name
in compo_color_nodes_props
:
4474 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4475 props
.to_type
= ident
4478 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4479 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4480 bl_label
= "Converter"
4482 def draw(self
, context
):
4483 layout
= self
.layout
4484 for ident
, node_type
, rna_name
in compo_converter_nodes_props
:
4485 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4486 props
.to_type
= ident
4489 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4490 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4493 def draw(self
, context
):
4494 layout
= self
.layout
4495 for ident
, node_type
, rna_name
in compo_filter_nodes_props
:
4496 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4497 props
.to_type
= ident
4500 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4501 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4504 def draw(self
, context
):
4505 layout
= self
.layout
4506 for ident
, node_type
, rna_name
in compo_vector_nodes_props
:
4507 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4508 props
.to_type
= ident
4511 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4512 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4515 def draw(self
, context
):
4516 layout
= self
.layout
4517 for ident
, node_type
, rna_name
in compo_matte_nodes_props
:
4518 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4519 props
.to_type
= ident
4522 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4523 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4524 bl_label
= "Distort"
4526 def draw(self
, context
):
4527 layout
= self
.layout
4528 for ident
, node_type
, rna_name
in compo_distort_nodes_props
:
4529 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4530 props
.to_type
= ident
4533 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4534 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4537 def draw(self
, context
):
4538 layout
= self
.layout
4539 for ident
, node_type
, rna_name
in compo_layout_nodes_props
:
4540 if node_type
!= 'FRAME':
4541 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4542 props
.to_type
= ident
4545 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4546 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4549 def draw(self
, context
):
4550 layout
= self
.layout
4551 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4552 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4553 props
.to_type
= ident
4556 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4557 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4560 def draw(self
, context
):
4561 layout
= self
.layout
4562 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4563 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4564 props
.to_type
= ident
4567 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4568 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4571 def draw(self
, context
):
4572 layout
= self
.layout
4573 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4574 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4575 props
.to_type
= ident
4578 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4579 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4582 def draw(self
, context
):
4583 layout
= self
.layout
4584 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4585 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4586 props
.to_type
= ident
4589 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4590 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4591 bl_label
= "Converter"
4593 def draw(self
, context
):
4594 layout
= self
.layout
4595 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4596 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4597 props
.to_type
= ident
4600 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4601 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4604 def draw(self
, context
):
4605 layout
= self
.layout
4606 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4607 if node_type
!= 'FRAME':
4608 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4609 props
.to_type
= ident
4612 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4613 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4616 def draw(self
, context
):
4617 layout
= self
.layout
4618 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4619 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4620 props
.to_type
= ident
4623 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4624 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4627 def draw(self
, context
):
4628 layout
= self
.layout
4629 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4630 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4631 props
.to_type
= ident
4634 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4635 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4638 def draw(self
, context
):
4639 layout
= self
.layout
4640 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4641 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4642 props
.to_type
= ident
4645 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4646 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4647 bl_label
= "Pattern"
4649 def draw(self
, context
):
4650 layout
= self
.layout
4651 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4652 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4653 props
.to_type
= ident
4656 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4657 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4658 bl_label
= "Textures"
4660 def draw(self
, context
):
4661 layout
= self
.layout
4662 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4663 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4664 props
.to_type
= ident
4667 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4668 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4669 bl_label
= "Converter"
4671 def draw(self
, context
):
4672 layout
= self
.layout
4673 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4674 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4675 props
.to_type
= ident
4678 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4679 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4680 bl_label
= "Distort"
4682 def draw(self
, context
):
4683 layout
= self
.layout
4684 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4685 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4686 props
.to_type
= ident
4689 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4690 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4693 def draw(self
, context
):
4694 layout
= self
.layout
4695 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4696 if node_type
!= 'FRAME':
4697 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4698 props
.to_type
= ident
4702 # APPENDAGES TO EXISTING UI
4706 def select_parent_children_buttons(self
, context
):
4707 layout
= self
.layout
4708 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4709 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4712 def attr_nodes_menu_func(self
, context
):
4713 col
= self
.layout
.column(align
=True)
4714 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4718 def multipleimages_menu_func(self
, context
):
4719 col
= self
.layout
.column(align
=True)
4720 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4721 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4725 def bgreset_menu_func(self
, context
):
4726 self
.layout
.operator(NWResetBG
.bl_idname
)
4729 def save_viewer_menu_func(self
, context
):
4730 if nw_check(context
):
4731 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4732 if context
.scene
.node_tree
.nodes
.active
:
4733 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4734 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4737 def reset_nodes_button(self
, context
):
4738 node_active
= context
.active_node
4739 node_selected
= context
.selected_nodes
4740 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4742 # Check if active node is in the selection and respective type
4743 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4744 row
= self
.layout
.row()
4745 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4746 self
.layout
.separator()
4748 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4749 row
= self
.layout
.row()
4750 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4751 self
.layout
.separator()
4755 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4759 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4760 # props entry: (property name, property value)
4763 # NWMergeNodes with Ctrl (AUTO).
4764 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4765 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4766 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4767 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4768 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4769 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4770 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4771 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4772 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4773 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4774 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4775 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4776 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4777 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4778 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4779 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4780 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4781 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4782 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4783 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4784 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4785 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4786 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4787 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4788 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4789 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4790 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4791 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4792 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4793 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4794 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4795 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4796 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4797 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4798 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4799 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4800 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4801 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4802 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4803 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4804 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4805 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4806 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4807 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4808 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4809 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4810 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4811 # NWMergeNodes with Ctrl Shift (MATH)
4812 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4813 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4814 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4815 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4816 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4817 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4818 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4819 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4820 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4821 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4822 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4823 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4824 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4825 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4826 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4827 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4828 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4829 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4830 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4831 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4832 # BATCH CHANGE NODES
4833 # NWBatchChangeNodes with Alt
4834 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4835 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4836 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4837 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4838 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4839 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4840 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4841 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4842 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4843 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4844 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4845 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4846 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4847 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4848 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4849 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4850 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4851 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4852 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4853 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4854 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4855 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4856 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4857 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4858 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4859 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4860 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4861 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4862 # LINK ACTIVE TO SELECTED
4863 # Don't use names, don't replace links (K)
4864 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4865 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4866 # Don't use names, replace links (Shift K)
4867 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4868 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4869 # Use node name, don't replace links (')
4870 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4871 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4872 # Use node name, replace links (Shift ')
4873 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4874 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4875 # Don't use names, don't replace links (;)
4876 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4877 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4878 # Don't use names, replace links (')
4879 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4880 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4882 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4883 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4884 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4885 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4886 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4887 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4888 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4889 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4890 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4891 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4892 # CLEAR LABEL (Alt L)
4893 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4894 # MODIFY LABEL (Alt Shift L)
4895 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4896 # Copy Label from active to selected
4897 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4898 # DETACH OUTPUTS (Alt Shift D)
4899 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4900 # LINK TO OUTPUT NODE (O)
4901 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4902 # SELECT PARENT/CHILDREN
4904 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4906 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4908 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4909 # Add Principled BSDF Texture Setup
4910 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4912 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4914 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4916 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4918 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4920 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4922 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4924 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, None, "Lazy Mix"),
4926 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, (('with_menu', False),), "Lazy Connect"),
4927 # Lazy Connect with Menu
4928 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, True, True, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4929 # Viewer Tile Center
4930 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4932 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4933 # Reset Nodes (Back Space)
4934 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4936 ('wm.call_menu', 'W', 'PRESS', False, True, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wrangler menu"),
4937 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4938 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4939 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4940 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4941 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4946 NWPrincipledPreferences
,
4966 NWAddPrincipledSetup
,
4968 NWLinkActiveToSelected
,
4970 NWSelectParentChildren
,
4976 NWAddMultipleImages
,
4985 NWConnectionListOutputs
,
4986 NWConnectionListInputs
,
4988 NWBatchChangeNodesMenu
,
4989 NWBatchChangeBlendTypeMenu
,
4990 NWBatchChangeOperationMenu
,
4991 NWCopyToSelectedMenu
,
4994 NWLinkActiveToSelectedMenu
,
4996 NWLinkUseNodeNameMenu
,
4997 NWLinkUseOutputsNamesMenu
,
4999 NWSwitchNodeTypeMenu
,
5000 NWSwitchShadersInputSubmenu
,
5001 NWSwitchShadersOutputSubmenu
,
5002 NWSwitchShadersShaderSubmenu
,
5003 NWSwitchShadersTextureSubmenu
,
5004 NWSwitchShadersColorSubmenu
,
5005 NWSwitchShadersVectorSubmenu
,
5006 NWSwitchShadersConverterSubmenu
,
5007 NWSwitchShadersLayoutSubmenu
,
5008 NWSwitchCompoInputSubmenu
,
5009 NWSwitchCompoOutputSubmenu
,
5010 NWSwitchCompoColorSubmenu
,
5011 NWSwitchCompoConverterSubmenu
,
5012 NWSwitchCompoFilterSubmenu
,
5013 NWSwitchCompoVectorSubmenu
,
5014 NWSwitchCompoMatteSubmenu
,
5015 NWSwitchCompoDistortSubmenu
,
5016 NWSwitchCompoLayoutSubmenu
,
5017 NWSwitchMatInputSubmenu
,
5018 NWSwitchMatOutputSubmenu
,
5019 NWSwitchMatColorSubmenu
,
5020 NWSwitchMatVectorSubmenu
,
5021 NWSwitchMatConverterSubmenu
,
5022 NWSwitchMatLayoutSubmenu
,
5023 NWSwitchTexInputSubmenu
,
5024 NWSwitchTexOutputSubmenu
,
5025 NWSwitchTexColorSubmenu
,
5026 NWSwitchTexPatternSubmenu
,
5027 NWSwitchTexTexturesSubmenu
,
5028 NWSwitchTexConverterSubmenu
,
5029 NWSwitchTexDistortSubmenu
,
5030 NWSwitchTexLayoutSubmenu
,
5034 from bpy
.utils
import register_class
5037 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
5038 name
="Busy Drawing!",
5040 description
="An internal property used to store only the first mouse position")
5041 bpy
.types
.Scene
.NWLazySource
= StringProperty(
5042 name
="Lazy Source!",
5044 description
="An internal property used to store the first node in a Lazy Connect operation")
5045 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
5046 name
="Lazy Target!",
5048 description
="An internal property used to store the last node in a Lazy Connect operation")
5049 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
5050 name
="Source Socket!",
5052 description
="An internal property used to store the source socket in a Lazy Connect operation")
5053 bpy
.types
.NodeSocketInterface
.NWViewerSocket
= BoolProperty(
5056 description
="An internal property used to determine if a socket is generated by the addon"
5063 addon_keymaps
.clear()
5064 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
5066 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
5067 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
5068 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
5070 for prop
, value
in props
:
5071 setattr(kmi
.properties
, prop
, value
)
5072 addon_keymaps
.append((km
, kmi
))
5075 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
5076 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
5077 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
5078 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
5079 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
5080 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
5081 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
5082 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
5086 from bpy
.utils
import unregister_class
5089 del bpy
.types
.Scene
.NWBusyDrawing
5090 del bpy
.types
.Scene
.NWLazySource
5091 del bpy
.types
.Scene
.NWLazyTarget
5092 del bpy
.types
.Scene
.NWSourceSocket
5093 del bpy
.types
.NodeSocketInterface
.NWViewerSocket
5096 for km
, kmi
in addon_keymaps
:
5097 km
.keymap_items
.remove(kmi
)
5098 addon_keymaps
.clear()
5101 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
5102 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
5103 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
5104 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
5105 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
5106 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
5107 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
5108 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
5111 unregister_class(cls
)
5113 if __name__
== "__main__":