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, 78, 0),
24 "location": "Node Editor Toolbar or Ctrl-Space",
25 "description": "Various tools to enhance and speed up node-based workflow",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Nodes/Nodes_Efficiency_Tools",
33 from bpy
.types
import Operator
, Panel
, Menu
34 from bpy
.props
import FloatProperty
, EnumProperty
, BoolProperty
, IntProperty
, StringProperty
, FloatVectorProperty
, CollectionProperty
35 from bpy_extras
.io_utils
import ImportHelper
, ExportHelper
36 from mathutils
import Vector
37 from math
import cos
, sin
, pi
, hypot
41 from itertools
import chain
43 from collections
import namedtuple
47 # list of outputs of Input Render Layer
48 # with attributes determinig if pass is used,
49 # and MultiLayer EXR outputs names and corresponding render engines
51 # rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_internal, in_cycles)
52 RL_entry
= namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_internal', 'in_cycles'])
54 RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
55 RL_entry('use_pass_color', 'Color', 'Color', True, False),
56 RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
57 RL_entry('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
58 RL_entry('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
59 RL_entry('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
60 RL_entry('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
61 RL_entry('use_pass_emit', 'Emit', 'Emit', True, False),
62 RL_entry('use_pass_environment', 'Environment', 'Env', True, False),
63 RL_entry('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
64 RL_entry('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
65 RL_entry('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
66 RL_entry('use_pass_indirect', 'Indirect', 'Indirect', True, False),
67 RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
68 RL_entry('use_pass_mist', 'Mist', 'Mist', True, False),
69 RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
70 RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
71 RL_entry('use_pass_reflection', 'Reflect', 'Reflect', True, False),
72 RL_entry('use_pass_refraction', 'Refract', 'Refract', True, False),
73 RL_entry('use_pass_shadow', 'Shadow', 'Shadow', True, True),
74 RL_entry('use_pass_specular', 'Specular', 'Spec', True, False),
75 RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', False, True),
76 RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', False, True),
77 RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
78 RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
79 RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
80 RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
81 RL_entry('use_pass_uv', 'UV', 'UV', True, True),
82 RL_entry('use_pass_vector', 'Speed', 'Vector', True, True),
83 RL_entry('use_pass_z', 'Z', 'Depth', True, True),
87 # (rna_type.identifier, type, rna_type.name)
88 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
89 shaders_input_nodes_props
= (
90 ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
91 ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
92 ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
93 ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
94 ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
95 ('ShaderNodeRGB', 'RGB', 'RGB'),
96 ('ShaderNodeValue', 'VALUE', 'Value'),
97 ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
98 ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
99 ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
100 ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
101 ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
102 ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
103 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
104 ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
106 # (rna_type.identifier, type, rna_type.name)
107 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
108 shaders_output_nodes_props
= (
109 ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
110 ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp Output'),
111 ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
113 # (rna_type.identifier, type, rna_type.name)
114 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
115 shaders_shader_nodes_props
= (
116 ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
117 ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
118 ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
119 ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
120 ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
121 ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
122 ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
123 ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
124 ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
125 ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
126 ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
127 ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
128 ('ShaderNodeEmission', 'EMISSION', 'Emission'),
129 ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
130 ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
131 ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
132 ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
133 ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
134 ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
135 ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
137 # (rna_type.identifier, type, rna_type.name)
138 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
139 shaders_texture_nodes_props
= (
140 ('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
141 ('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
142 ('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
143 ('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
144 ('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
145 ('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
146 ('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
147 ('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
148 ('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
149 ('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
150 ('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
151 ('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
153 # (rna_type.identifier, type, rna_type.name)
154 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
155 shaders_color_nodes_props
= (
156 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
157 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
158 ('ShaderNodeInvert', 'INVERT', 'Invert'),
159 ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
160 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
161 ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
162 ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
164 # (rna_type.identifier, type, rna_type.name)
165 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
166 shaders_vector_nodes_props
= (
167 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
168 ('ShaderNodeBump', 'BUMP', 'Bump'),
169 ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
170 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
171 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
172 ('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
174 # (rna_type.identifier, type, rna_type.name)
175 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
176 shaders_converter_nodes_props
= (
177 ('ShaderNodeMath', 'MATH', 'Math'),
178 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
179 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
180 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
181 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
182 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
183 ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
184 ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
185 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
186 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
187 ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
188 ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
190 # (rna_type.identifier, type, rna_type.name)
191 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
192 shaders_layout_nodes_props
= (
193 ('NodeFrame', 'FRAME', 'Frame'),
194 ('NodeReroute', 'REROUTE', 'Reroute'),
198 # (rna_type.identifier, type, rna_type.name)
199 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
200 compo_input_nodes_props
= (
201 ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
202 ('CompositorNodeImage', 'IMAGE', 'Image'),
203 ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
204 ('CompositorNodeMask', 'MASK', 'Mask'),
205 ('CompositorNodeRGB', 'RGB', 'RGB'),
206 ('CompositorNodeValue', 'VALUE', 'Value'),
207 ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
208 ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
209 ('CompositorNodeTime', 'TIME', 'Time'),
210 ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
212 # (rna_type.identifier, type, rna_type.name)
213 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
214 compo_output_nodes_props
= (
215 ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
216 ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
217 ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
218 ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
219 ('CompositorNodeLevels', 'LEVELS', 'Levels'),
221 # (rna_type.identifier, type, rna_type.name)
222 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
223 compo_color_nodes_props
= (
224 ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
225 ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
226 ('CompositorNodeInvert', 'INVERT', 'Invert'),
227 ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
228 ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
229 ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
230 ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
231 ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
232 ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
233 ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
234 ('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
235 ('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
237 # (rna_type.identifier, type, rna_type.name)
238 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
239 compo_converter_nodes_props
= (
240 ('CompositorNodeMath', 'MATH', 'Math'),
241 ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
242 ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
243 ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
244 ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
245 ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
246 ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
247 ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
248 ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
249 ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
250 ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
251 ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
252 ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
253 ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
255 # (rna_type.identifier, type, rna_type.name)
256 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
257 compo_filter_nodes_props
= (
258 ('CompositorNodeBlur', 'BLUR', 'Blur'),
259 ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
260 ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
261 ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
262 ('CompositorNodeFilter', 'FILTER', 'Filter'),
263 ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
264 ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
265 ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
266 ('CompositorNodeGlare', 'GLARE', 'Glare'),
267 ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
268 ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
269 ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
270 ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
272 # (rna_type.identifier, type, rna_type.name)
273 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
274 compo_vector_nodes_props
= (
275 ('CompositorNodeNormal', 'NORMAL', 'Normal'),
276 ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
277 ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
278 ('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
279 ('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
281 # (rna_type.identifier, type, rna_type.name)
282 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
283 compo_matte_nodes_props
= (
284 ('CompositorNodeKeying', 'KEYING', 'Keying'),
285 ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
286 ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
287 ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
288 ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
289 ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
290 ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
291 ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
292 ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
293 ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
294 ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
295 ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
297 # (rna_type.identifier, type, rna_type.name)
298 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
299 compo_distort_nodes_props
= (
300 ('CompositorNodeScale', 'SCALE', 'Scale'),
301 ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
302 ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
303 ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
304 ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
305 ('CompositorNodeFlip', 'FLIP', 'Flip'),
306 ('CompositorNodeCrop', 'CROP', 'Crop'),
307 ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
308 ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
309 ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
310 ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
311 ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
312 ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
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 compo_layout_nodes_props
= (
317 ('NodeFrame', 'FRAME', 'Frame'),
318 ('NodeReroute', 'REROUTE', 'Reroute'),
319 ('CompositorNodeSwitch', 'SWITCH', 'Switch'),
321 # Blender Render material nodes
322 # (rna_type.identifier, type, rna_type.name)
323 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
324 blender_mat_input_nodes_props
= (
325 ('ShaderNodeMaterial', 'MATERIAL', 'Material'),
326 ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
327 ('ShaderNodeLampData', 'LAMP', 'Lamp Data'),
328 ('ShaderNodeValue', 'VALUE', 'Value'),
329 ('ShaderNodeRGB', 'RGB', 'RGB'),
330 ('ShaderNodeTexture', 'TEXTURE', 'Texture'),
331 ('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
332 ('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
335 # (rna_type.identifier, type, rna_type.name)
336 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
337 blender_mat_output_nodes_props
= (
338 ('ShaderNodeOutput', 'OUTPUT', 'Output'),
341 # (rna_type.identifier, type, rna_type.name)
342 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
343 blender_mat_color_nodes_props
= (
344 ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
345 ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
346 ('ShaderNodeInvert', 'INVERT', 'Invert'),
347 ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
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 blender_mat_vector_nodes_props
= (
353 ('ShaderNodeNormal', 'NORMAL', 'Normal'),
354 ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
355 ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
358 # (rna_type.identifier, type, rna_type.name)
359 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
360 blender_mat_converter_nodes_props
= (
361 ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
362 ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
363 ('ShaderNodeMath', 'MATH', 'Math'),
364 ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
365 ('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
366 ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
367 ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
368 ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
369 ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
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_layout_nodes_props
= (
375 ('NodeReroute', 'REROUTE', 'Reroute'),
379 # (rna_type.identifier, type, rna_type.name)
380 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
381 texture_input_nodes_props
= (
382 ('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
383 ('TextureNodeCoordinates', 'COORD', 'Coordinates'),
384 ('TextureNodeTexture', 'TEXTURE', 'Texture'),
385 ('TextureNodeImage', 'IMAGE', 'Image'),
388 # (rna_type.identifier, type, rna_type.name)
389 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
390 texture_output_nodes_props
= (
391 ('TextureNodeOutput', 'OUTPUT', 'Output'),
392 ('TextureNodeViewer', 'VIEWER', 'Viewer'),
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 texture_color_nodes_props
= (
398 ('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
399 ('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
400 ('TextureNodeInvert', 'INVERT', 'Invert'),
401 ('TextureNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
402 ('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
403 ('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
406 # (rna_type.identifier, type, rna_type.name)
407 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
408 texture_pattern_nodes_props
= (
409 ('TextureNodeChecker', 'CHECKER', 'Checker'),
410 ('TextureNodeBricks', 'BRICKS', 'Bricks'),
413 # (rna_type.identifier, type, rna_type.name)
414 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
415 texture_textures_nodes_props
= (
416 ('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
417 ('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
418 ('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
419 ('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
420 ('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
421 ('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
422 ('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
423 ('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
424 ('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
425 ('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
428 # (rna_type.identifier, type, rna_type.name)
429 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
430 texture_converter_nodes_props
= (
431 ('TextureNodeMath', 'MATH', 'Math'),
432 ('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
433 ('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
434 ('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
435 ('TextureNodeDistance', 'DISTANCE', 'Distance'),
438 # (rna_type.identifier, type, rna_type.name)
439 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
440 texture_distort_nodes_props
= (
441 ('TextureNodeScale', 'SCALE', 'Scale'),
442 ('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
443 ('TextureNodeRotate', 'ROTATE', 'Rotate'),
444 ('TextureNodeAt', 'AT', 'At'),
447 # (rna_type.identifier, type, rna_type.name)
448 # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
449 texture_layout_nodes_props
= (
450 ('NodeReroute', 'REROUTE', 'Reroute'),
453 # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
454 # used list, not tuple for easy merging with other lists.
456 ('MIX', 'Mix', 'Mix Mode'),
457 ('ADD', 'Add', 'Add Mode'),
458 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
459 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
460 ('SCREEN', 'Screen', 'Screen Mode'),
461 ('DIVIDE', 'Divide', 'Divide Mode'),
462 ('DIFFERENCE', 'Difference', 'Difference Mode'),
463 ('DARKEN', 'Darken', 'Darken Mode'),
464 ('LIGHTEN', 'Lighten', 'Lighten Mode'),
465 ('OVERLAY', 'Overlay', 'Overlay Mode'),
466 ('DODGE', 'Dodge', 'Dodge Mode'),
467 ('BURN', 'Burn', 'Burn Mode'),
468 ('HUE', 'Hue', 'Hue Mode'),
469 ('SATURATION', 'Saturation', 'Saturation Mode'),
470 ('VALUE', 'Value', 'Value Mode'),
471 ('COLOR', 'Color', 'Color Mode'),
472 ('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
473 ('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
476 # list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
477 # used list, not tuple for easy merging with other lists.
479 ('ADD', 'Add', 'Add Mode'),
480 ('SUBTRACT', 'Subtract', 'Subtract Mode'),
481 ('MULTIPLY', 'Multiply', 'Multiply Mode'),
482 ('DIVIDE', 'Divide', 'Divide Mode'),
483 ('SINE', 'Sine', 'Sine Mode'),
484 ('COSINE', 'Cosine', 'Cosine Mode'),
485 ('TANGENT', 'Tangent', 'Tangent Mode'),
486 ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
487 ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
488 ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
489 ('POWER', 'Power', 'Power Mode'),
490 ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
491 ('MINIMUM', 'Minimum', 'Minimum Mode'),
492 ('MAXIMUM', 'Maximum', 'Maximum Mode'),
493 ('ROUND', 'Round', 'Round Mode'),
494 ('LESS_THAN', 'Less Than', 'Less Than Mode'),
495 ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
496 ('MODULO', 'Modulo', 'Modulo Mode'),
497 ('ABSOLUTE', 'Absolute', 'Absolute Mode'),
500 # in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
501 # used list, not tuple for easy merging with other lists.
503 ('CURRENT', 'Current', 'Leave at current state'),
504 ('NEXT', 'Next', 'Next blend type/operation'),
505 ('PREV', 'Prev', 'Previous blend type/operation'),
510 (1.0, 1.0, 1.0, 0.7),
511 (1.0, 0.0, 0.0, 0.7),
515 (0.0, 0.0, 0.0, 1.0),
516 (0.38, 0.77, 0.38, 1.0),
517 (0.38, 0.77, 0.38, 1.0)
520 (0.0, 0.0, 0.0, 1.0),
521 (0.77, 0.77, 0.16, 1.0),
522 (0.77, 0.77, 0.16, 1.0)
525 (0.0, 0.0, 0.0, 1.0),
526 (0.38, 0.38, 0.77, 1.0),
527 (0.38, 0.38, 0.77, 1.0)
530 (0.0, 0.0, 0.0, 1.0),
531 (0.63, 0.63, 0.63, 1.0),
532 (0.63, 0.63, 0.63, 1.0)
535 (1.0, 1.0, 1.0, 0.7),
536 (0.0, 0.0, 0.0, 0.7),
542 def nice_hotkey_name(punc
):
543 # convert the ugly string name into the actual character
545 ('LEFTMOUSE', "LMB"),
546 ('MIDDLEMOUSE', "MMB"),
547 ('RIGHTMOUSE', "RMB"),
548 ('SELECTMOUSE', "Select"),
549 ('WHEELUPMOUSE', "Wheel Up"),
550 ('WHEELDOWNMOUSE', "Wheel Down"),
551 ('WHEELINMOUSE', "Wheel In"),
552 ('WHEELOUTMOUSE', "Wheel Out"),
565 ('LINE_FEED', "Enter"),
572 ('BACK_SLASH', "\\"),
574 ('NUMPAD_1', "Numpad 1"),
575 ('NUMPAD_2', "Numpad 2"),
576 ('NUMPAD_3', "Numpad 3"),
577 ('NUMPAD_4', "Numpad 4"),
578 ('NUMPAD_5', "Numpad 5"),
579 ('NUMPAD_6', "Numpad 6"),
580 ('NUMPAD_7', "Numpad 7"),
581 ('NUMPAD_8', "Numpad 8"),
582 ('NUMPAD_9', "Numpad 9"),
583 ('NUMPAD_0', "Numpad 0"),
584 ('NUMPAD_PERIOD', "Numpad ."),
585 ('NUMPAD_SLASH', "Numpad /"),
586 ('NUMPAD_ASTERIX', "Numpad *"),
587 ('NUMPAD_MINUS', "Numpad -"),
588 ('NUMPAD_ENTER', "Numpad Enter"),
589 ('NUMPAD_PLUS', "Numpad +"),
592 for (ugly
, nice
) in pairs
:
597 nice_punc
= punc
.replace("_", " ").title()
601 def force_update(context
):
602 context
.space_data
.node_tree
.update_tag()
606 prefs
= bpy
.context
.user_preferences
.system
607 return prefs
.dpi
* prefs
.pixel_size
/ 72
610 def node_mid_pt(node
, axis
):
612 d
= node
.location
.x
+ (node
.dimensions
.x
/ 2)
614 d
= node
.location
.y
- (node
.dimensions
.y
/ 2)
620 def autolink(node1
, node2
, links
):
623 for outp
in node1
.outputs
:
624 for inp
in node2
.inputs
:
625 if not inp
.is_linked
and inp
.name
== outp
.name
:
630 for outp
in node1
.outputs
:
631 for inp
in node2
.inputs
:
632 if not inp
.is_linked
and inp
.type == outp
.type:
637 # force some connection even if the type doesn't match
638 for outp
in node1
.outputs
:
639 for inp
in node2
.inputs
:
640 if not inp
.is_linked
:
645 # even if no sockets are open, force one of matching type
646 for outp
in node1
.outputs
:
647 for inp
in node2
.inputs
:
648 if inp
.type == outp
.type:
654 for outp
in node1
.outputs
:
655 for inp
in node2
.inputs
:
660 print("Could not make a link from " + node1
.name
+ " to " + node2
.name
)
664 def node_at_pos(nodes
, context
, event
):
665 nodes_near_mouse
= []
666 nodes_under_mouse
= []
669 store_mouse_cursor(context
, event
)
670 x
, y
= context
.space_data
.cursor_location
674 # Make a list of each corner (and middle of border) for each node.
675 # Will be sorted to find nearest point and thus nearest node
676 node_points_with_dist
= []
679 if node
.type != 'FRAME': # no point trying to link to a frame node
680 locx
= node
.location
.x
681 locy
= node
.location
.y
682 dimx
= node
.dimensions
.x
/dpifac()
683 dimy
= node
.dimensions
.y
/dpifac()
685 locx
+= node
.parent
.location
.x
686 locy
+= node
.parent
.location
.y
687 if node
.parent
.parent
:
688 locx
+= node
.parent
.parent
.location
.x
689 locy
+= node
.parent
.parent
.location
.y
690 if node
.parent
.parent
.parent
:
691 locx
+= node
.parent
.parent
.parent
.location
.x
692 locy
+= node
.parent
.parent
.parent
.location
.y
693 if node
.parent
.parent
.parent
.parent
:
694 # Support three levels or parenting
695 # There's got to be a better way to do this...
698 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- locy
)]) # Top Left
699 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- locy
)]) # Top Right
700 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- dimy
))]) # Bottom Left
701 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- dimy
))]) # Bottom Right
703 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- locy
)]) # Mid Top
704 node_points_with_dist
.append([node
, hypot(x
- (locx
+ (dimx
/ 2)), y
- (locy
- dimy
))]) # Mid Bottom
705 node_points_with_dist
.append([node
, hypot(x
- locx
, y
- (locy
- (dimy
/ 2)))]) # Mid Left
706 node_points_with_dist
.append([node
, hypot(x
- (locx
+ dimx
), y
- (locy
- (dimy
/ 2)))]) # Mid Right
708 nearest_node
= sorted(node_points_with_dist
, key
=lambda k
: k
[1])[0][0]
711 if node
.type != 'FRAME' and skipnode
== False:
712 locx
= node
.location
.x
713 locy
= node
.location
.y
714 dimx
= node
.dimensions
.x
/dpifac()
715 dimy
= node
.dimensions
.y
/dpifac()
717 locx
+= node
.parent
.location
.x
718 locy
+= node
.parent
.location
.y
719 if (locx
<= x
<= locx
+ dimx
) and \
720 (locy
- dimy
<= y
<= locy
):
721 nodes_under_mouse
.append(node
)
723 if len(nodes_under_mouse
) == 1:
724 if nodes_under_mouse
[0] != nearest_node
:
725 target_node
= nodes_under_mouse
[0] # use the node under the mouse if there is one and only one
727 target_node
= nearest_node
# else use the nearest node
729 target_node
= nearest_node
733 def store_mouse_cursor(context
, event
):
734 space
= context
.space_data
735 v2d
= context
.region
.view2d
736 tree
= space
.edit_tree
738 # convert mouse position to the View2D for later node placement
739 if context
.region
.type == 'WINDOW':
740 space
.cursor_location_from_region(event
.mouse_region_x
, event
.mouse_region_y
)
742 space
.cursor_location
= tree
.view_center
745 def draw_line(x1
, y1
, x2
, y2
, size
, colour
=[1.0, 1.0, 1.0, 0.7]):
746 shademodel_state
= bgl
.Buffer(bgl
.GL_INT
, 1)
747 bgl
.glGetIntegerv(bgl
.GL_SHADE_MODEL
, shademodel_state
)
749 bgl
.glEnable(bgl
.GL_BLEND
)
750 bgl
.glLineWidth(size
* dpifac())
751 bgl
.glShadeModel(bgl
.GL_SMOOTH
)
752 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
754 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
756 bgl
.glColor4f(colour
[0]+(1.0-colour
[0])/4, colour
[1]+(1.0-colour
[1])/4, colour
[2]+(1.0-colour
[2])/4, colour
[3]+(1.0-colour
[3])/4)
757 bgl
.glVertex2f(x1
, y1
)
758 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
759 bgl
.glVertex2f(x2
, y2
)
764 bgl
.glShadeModel(shademodel_state
[0])
765 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
768 def draw_circle(mx
, my
, radius
, colour
=[1.0, 1.0, 1.0, 0.7]):
769 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
770 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
771 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
772 radius
= radius
* dpifac()
774 for i
in range(sides
+ 1):
775 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
776 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
777 bgl
.glVertex2f(cosine
, sine
)
779 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
782 def draw_rounded_node_border(node
, radius
=8, colour
=[1.0, 1.0, 1.0, 0.7]):
783 bgl
.glEnable(bgl
.GL_BLEND
)
784 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
786 area_width
= bpy
.context
.area
.width
- (16*dpifac()) - 1
787 bottom_bar
= (16*dpifac()) + 1
789 radius
= radius
*dpifac()
790 bgl
.glColor4f(colour
[0], colour
[1], colour
[2], colour
[3])
792 nlocx
= (node
.location
.x
+1)*dpifac()
793 nlocy
= (node
.location
.y
+1)*dpifac()
794 ndimx
= node
.dimensions
.x
795 ndimy
= node
.dimensions
.y
796 # This is a stupid way to do this... TODO use while loop
798 nlocx
+= node
.parent
.location
.x
799 nlocy
+= node
.parent
.location
.y
800 if node
.parent
.parent
:
801 nlocx
+= node
.parent
.parent
.location
.x
802 nlocy
+= node
.parent
.parent
.location
.y
803 if node
.parent
.parent
.parent
:
804 nlocx
+= node
.parent
.parent
.parent
.location
.x
805 nlocy
+= node
.parent
.parent
.parent
.location
.y
810 if node
.type == 'REROUTE':
818 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
819 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
820 bgl
.glVertex2f(mx
,my
)
821 for i
in range(sides
+1):
823 if my
> bottom_bar
and mx
< area_width
:
824 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
825 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
826 bgl
.glVertex2f(cosine
, sine
)
830 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
831 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
832 bgl
.glVertex2f(mx
,my
)
833 for i
in range(sides
+1):
835 if my
> bottom_bar
and mx
< area_width
:
836 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
837 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
838 bgl
.glVertex2f(cosine
, sine
)
842 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
843 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
844 bgl
.glVertex2f(mx
,my
)
845 for i
in range(sides
+1):
847 if my
> bottom_bar
and mx
< area_width
:
848 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
849 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
850 bgl
.glVertex2f(cosine
, sine
)
853 # Bottom right corner
854 bgl
.glBegin(bgl
.GL_TRIANGLE_FAN
)
855 mx
, my
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
856 bgl
.glVertex2f(mx
,my
)
857 for i
in range(sides
+1):
859 if my
> bottom_bar
and mx
< area_width
:
860 cosine
= radius
* cos(i
* 2 * pi
/ sides
) + mx
861 sine
= radius
* sin(i
* 2 * pi
/ sides
) + my
862 bgl
.glVertex2f(cosine
, sine
)
867 bgl
.glBegin(bgl
.GL_QUADS
)
868 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
869 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
- ndimy
, clip
=False)
870 m1y
= max(m1y
, bottom_bar
)
871 m2y
= max(m2y
, bottom_bar
)
872 if m1x
< area_width
and m2x
< area_width
:
873 bgl
.glVertex2f(m2x
-radius
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
874 bgl
.glVertex2f(m2x
,m2y
)
875 bgl
.glVertex2f(m1x
,m1y
)
876 bgl
.glVertex2f(m1x
-radius
,m1y
)
880 bgl
.glBegin(bgl
.GL_QUADS
)
881 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
, clip
=False)
882 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
883 m1x
= min(m1x
, area_width
)
884 m2x
= min(m2x
, area_width
)
885 if m1y
> bottom_bar
and m2y
> bottom_bar
:
886 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
887 bgl
.glVertex2f(m2x
,m2y
)
888 bgl
.glVertex2f(m2x
,m1y
+radius
)
889 bgl
.glVertex2f(m1x
,m1y
+radius
)
893 bgl
.glBegin(bgl
.GL_QUADS
)
894 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
, clip
=False)
895 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
- ndimy
, clip
=False)
896 m1y
= max(m1y
, bottom_bar
)
897 m2y
= max(m2y
, bottom_bar
)
898 if m1x
< area_width
and m2x
< area_width
:
899 bgl
.glVertex2f(m2x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
900 bgl
.glVertex2f(m2x
+radius
,m2y
)
901 bgl
.glVertex2f(m1x
+radius
,m1y
)
902 bgl
.glVertex2f(m1x
,m1y
)
906 bgl
.glBegin(bgl
.GL_QUADS
)
907 m1x
, m1y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
, nlocy
-ndimy
, clip
=False)
908 m2x
, m2y
= bpy
.context
.region
.view2d
.view_to_region(nlocx
+ ndimx
, nlocy
-ndimy
, clip
=False)
909 m1x
= min(m1x
, area_width
)
910 m2x
= min(m2x
, area_width
)
911 if m1y
> bottom_bar
and m2y
> bottom_bar
:
912 bgl
.glVertex2f(m1x
,m2y
) # draw order is important, start with bottom left and go anti-clockwise
913 bgl
.glVertex2f(m2x
,m2y
)
914 bgl
.glVertex2f(m2x
,m1y
-radius
)
915 bgl
.glVertex2f(m1x
,m1y
-radius
)
920 bgl
.glDisable(bgl
.GL_BLEND
)
921 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
924 def draw_callback_nodeoutline(self
, context
, mode
):
926 nodes
, links
= get_nodes_links(context
)
927 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
930 col_outer
= [1.0, 0.2, 0.2, 0.4]
931 col_inner
= [0.0, 0.0, 0.0, 0.5]
932 col_circle_inner
= [0.3, 0.05, 0.05, 1.0]
933 elif mode
== "LINKMENU":
934 col_outer
= [0.4, 0.6, 1.0, 0.4]
935 col_inner
= [0.0, 0.0, 0.0, 0.5]
936 col_circle_inner
= [0.08, 0.15, .3, 1.0]
938 col_outer
= [0.2, 1.0, 0.2, 0.4]
939 col_inner
= [0.0, 0.0, 0.0, 0.5]
940 col_circle_inner
= [0.05, 0.3, 0.05, 1.0]
942 m1x
= self
.mouse_path
[0][0]
943 m1y
= self
.mouse_path
[0][1]
944 m2x
= self
.mouse_path
[-1][0]
945 m2y
= self
.mouse_path
[-1][1]
947 n1
= nodes
[context
.scene
.NWLazySource
]
948 n2
= nodes
[context
.scene
.NWLazyTarget
]
951 col_outer
= [0.4, 0.4, 0.4, 0.4]
952 col_inner
= [0.0, 0.0, 0.0, 0.5]
953 col_circle_inner
= [0.2, 0.2, 0.2, 1.0]
955 draw_rounded_node_border(n1
, radius
=6, colour
=col_outer
) # outline
956 draw_rounded_node_border(n1
, radius
=5, colour
=col_inner
) # inner
957 draw_rounded_node_border(n2
, radius
=6, colour
=col_outer
) # outline
958 draw_rounded_node_border(n2
, radius
=5, colour
=col_inner
) # inner
960 draw_line(m1x
, m1y
, m2x
, m2y
, 5, col_outer
) # line outline
961 draw_line(m1x
, m1y
, m2x
, m2y
, 2, col_inner
) # line inner
964 draw_circle(m1x
, m1y
, 7, col_outer
)
965 draw_circle(m2x
, m2y
, 7, col_outer
)
968 draw_circle(m1x
, m1y
, 5, col_circle_inner
)
969 draw_circle(m2x
, m2y
, 5, col_circle_inner
)
971 # restore opengl defaults
973 bgl
.glDisable(bgl
.GL_BLEND
)
974 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
976 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
979 def get_nodes_links(context
):
980 tree
= context
.space_data
.node_tree
982 # Get nodes from currently edited tree.
983 # If user is editing a group, space_data.node_tree is still the base level (outside group).
984 # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
985 # the same as context.active_node, the user is in a group.
986 # Check recursively until we find the real active node_tree:
987 if tree
.nodes
.active
:
988 while tree
.nodes
.active
!= context
.active_node
:
989 tree
= tree
.nodes
.active
.node_tree
991 return tree
.nodes
, tree
.links
994 class NWPrincipledPreferences(bpy
.types
.PropertyGroup
):
995 base_color
= StringProperty(
997 default
='diffuse diff albedo base col color',
998 description
='Naming Components for Base Color maps')
999 sss_color
= StringProperty(
1000 name
='Subsurface Color',
1001 default
='sss subsurface',
1002 description
='Naming Components for Subsurface Color maps')
1003 metallic
= StringProperty(
1005 default
='metallic metalness metal mtl',
1006 description
='Naming Components for metallness maps')
1007 specular
= StringProperty(
1009 default
='specularity specular spec spc',
1010 description
='Naming Components for Specular maps')
1011 normal
= StringProperty(
1013 default
='normal nor nrm nrml norm',
1014 description
='Naming Components for Normal maps')
1015 bump
= StringProperty(
1018 description
='Naming Components for bump maps')
1019 rough
= StringProperty(
1021 default
='roughness rough rgh',
1022 description
='Naming Components for roughness maps')
1023 gloss
= StringProperty(
1025 default
='gloss glossy glossyness',
1026 description
='Naming Components for glossy maps')
1027 displacement
= StringProperty(
1028 name
='Displacement',
1029 default
='displacement displace disp dsp height heightmap',
1030 description
='Naming Components for displacement maps')
1033 class NWNodeWrangler(bpy
.types
.AddonPreferences
):
1034 bl_idname
= __name__
1036 merge_hide
= EnumProperty(
1037 name
="Hide Mix nodes",
1039 ("ALWAYS", "Always", "Always collapse the new merge nodes"),
1040 ("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
1041 ("NEVER", "Never", "Never collapse the new merge nodes")
1043 default
='NON_SHADER',
1044 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy whether to collapse them or show the full node with options expanded")
1045 merge_position
= EnumProperty(
1046 name
="Mix Node Position",
1048 ("CENTER", "Center", "Place the Mix node between the two nodes"),
1049 ("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
1052 description
="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specifiy the position of the new nodes")
1054 show_hotkey_list
= BoolProperty(
1055 name
="Show Hotkey List",
1057 description
="Expand this box into a list of all the hotkeys for functions in this addon"
1059 hotkey_list_filter
= StringProperty(
1060 name
=" Filter by Name",
1062 description
="Show only hotkeys that have this text in their name"
1064 show_principled_lists
= BoolProperty(
1065 name
="Show Principled naming tags",
1067 description
="Expand this box into a list of all naming tags for principled texture setup"
1069 principled_tags
= bpy
.props
.PointerProperty(type=NWPrincipledPreferences
)
1071 def draw(self
, context
):
1072 layout
= self
.layout
1073 col
= layout
.column()
1074 col
.prop(self
, "merge_position")
1075 col
.prop(self
, "merge_hide")
1078 col
= box
.column(align
=True)
1079 col
.prop(self
, "show_principled_lists", text
='Edit tags for auto texture detection in Principled BSDF setup', toggle
=True)
1080 if self
.show_principled_lists
:
1081 tags
= self
.principled_tags
1083 col
.prop(tags
, "base_color")
1084 col
.prop(tags
, "sss_color")
1085 col
.prop(tags
, "metallic")
1086 col
.prop(tags
, "specular")
1087 col
.prop(tags
, "rough")
1088 col
.prop(tags
, "gloss")
1089 col
.prop(tags
, "normal")
1090 col
.prop(tags
, "bump")
1091 col
.prop(tags
, "displacement")
1094 col
= box
.column(align
=True)
1095 hotkey_button_name
= "Show Hotkey List"
1096 if self
.show_hotkey_list
:
1097 hotkey_button_name
= "Hide Hotkey List"
1098 col
.prop(self
, "show_hotkey_list", text
=hotkey_button_name
, toggle
=True)
1099 if self
.show_hotkey_list
:
1100 col
.prop(self
, "hotkey_list_filter", icon
="VIEWZOOM")
1102 for hotkey
in kmi_defs
:
1104 hotkey_name
= hotkey
[7]
1106 if self
.hotkey_list_filter
.lower() in hotkey_name
.lower():
1107 row
= col
.row(align
=True)
1108 row
.label(hotkey_name
)
1109 keystr
= nice_hotkey_name(hotkey
[1])
1111 keystr
= "Shift " + keystr
1113 keystr
= "Alt " + keystr
1115 keystr
= "Ctrl " + keystr
1120 def nw_check(context
):
1121 space
= context
.space_data
1122 valid_trees
= ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree"]
1125 if space
.type == 'NODE_EDITOR' and space
.node_tree
is not None and space
.tree_type
in valid_trees
:
1132 def poll(cls
, context
):
1133 return nw_check(context
)
1137 class NWLazyMix(Operator
, NWBase
):
1138 """Add a Mix RGB/Shader node by interactively drawing lines between nodes"""
1139 bl_idname
= "node.nw_lazy_mix"
1140 bl_label
= "Mix Nodes"
1141 bl_options
= {'REGISTER', 'UNDO'}
1143 def modal(self
, context
, event
):
1144 context
.area
.tag_redraw()
1145 nodes
, links
= get_nodes_links(context
)
1148 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1151 if not context
.scene
.NWBusyDrawing
:
1152 node1
= node_at_pos(nodes
, context
, event
)
1154 context
.scene
.NWBusyDrawing
= node1
.name
1156 if context
.scene
.NWBusyDrawing
!= 'STOP':
1157 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1159 context
.scene
.NWLazySource
= node1
.name
1160 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1162 if event
.type == 'MOUSEMOVE':
1163 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1165 elif event
.type == 'RIGHTMOUSE':
1166 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1167 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1170 node2
= node_at_pos(nodes
, context
, event
)
1172 context
.scene
.NWBusyDrawing
= node2
.name
1184 bpy
.ops
.node
.nw_merge_nodes(mode
="MIX", merge_type
="AUTO")
1186 context
.scene
.NWBusyDrawing
= ""
1189 elif event
.type == 'ESC':
1191 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1192 return {'CANCELLED'}
1194 return {'RUNNING_MODAL'}
1196 def invoke(self
, context
, event
):
1197 if context
.area
.type == 'NODE_EDITOR':
1198 # the arguments we pass the the callback
1199 args
= (self
, context
, 'MIX')
1200 # Add the region OpenGL drawing callback
1201 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1202 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1204 self
.mouse_path
= []
1206 context
.window_manager
.modal_handler_add(self
)
1207 return {'RUNNING_MODAL'}
1209 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1210 return {'CANCELLED'}
1213 class NWLazyConnect(Operator
, NWBase
):
1214 """Connect two nodes without clicking a specific socket (automatically determined"""
1215 bl_idname
= "node.nw_lazy_connect"
1216 bl_label
= "Lazy Connect"
1217 bl_options
= {'REGISTER', 'UNDO'}
1218 with_menu
= BoolProperty()
1220 def modal(self
, context
, event
):
1221 context
.area
.tag_redraw()
1222 nodes
, links
= get_nodes_links(context
)
1225 start_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1228 if not context
.scene
.NWBusyDrawing
:
1229 node1
= node_at_pos(nodes
, context
, event
)
1231 context
.scene
.NWBusyDrawing
= node1
.name
1233 if context
.scene
.NWBusyDrawing
!= 'STOP':
1234 node1
= nodes
[context
.scene
.NWBusyDrawing
]
1236 context
.scene
.NWLazySource
= node1
.name
1237 context
.scene
.NWLazyTarget
= node_at_pos(nodes
, context
, event
).name
1239 if event
.type == 'MOUSEMOVE':
1240 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
1242 elif event
.type == 'RIGHTMOUSE':
1243 end_pos
= [event
.mouse_region_x
, event
.mouse_region_y
]
1244 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1247 node2
= node_at_pos(nodes
, context
, event
)
1249 context
.scene
.NWBusyDrawing
= node2
.name
1254 link_success
= False
1260 if node
.select
== True:
1262 original_sel
.append(node
)
1264 original_unsel
.append(node
)
1268 #link_success = autolink(node1, node2, links)
1270 if len(node1
.outputs
) > 1 and node2
.inputs
:
1271 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListOutputs
.bl_idname
)
1272 elif len(node1
.outputs
) == 1:
1273 bpy
.ops
.node
.nw_call_inputs_menu(from_socket
=0)
1275 link_success
= autolink(node1
, node2
, links
)
1277 for node
in original_sel
:
1279 for node
in original_unsel
:
1283 force_update(context
)
1284 context
.scene
.NWBusyDrawing
= ""
1287 elif event
.type == 'ESC':
1288 bpy
.types
.SpaceNodeEditor
.draw_handler_remove(self
._handle
, 'WINDOW')
1289 return {'CANCELLED'}
1291 return {'RUNNING_MODAL'}
1293 def invoke(self
, context
, event
):
1294 if context
.area
.type == 'NODE_EDITOR':
1295 nodes
, links
= get_nodes_links(context
)
1296 node
= node_at_pos(nodes
, context
, event
)
1298 context
.scene
.NWBusyDrawing
= node
.name
1300 # the arguments we pass the the callback
1304 args
= (self
, context
, mode
)
1305 # Add the region OpenGL drawing callback
1306 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
1307 self
._handle
= bpy
.types
.SpaceNodeEditor
.draw_handler_add(draw_callback_nodeoutline
, args
, 'WINDOW', 'POST_PIXEL')
1309 self
.mouse_path
= []
1311 context
.window_manager
.modal_handler_add(self
)
1312 return {'RUNNING_MODAL'}
1314 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1315 return {'CANCELLED'}
1318 class NWDeleteUnused(Operator
, NWBase
):
1319 """Delete all nodes whose output is not used"""
1320 bl_idname
= 'node.nw_del_unused'
1321 bl_label
= 'Delete Unused Nodes'
1322 bl_options
= {'REGISTER', 'UNDO'}
1324 delete_muted
= BoolProperty(name
="Delete Muted", description
="Delete (but reconnect, like Ctrl-X) all muted nodes", default
=True)
1325 delete_frames
= BoolProperty(name
="Delete Empty Frames", description
="Delete all frames that have no nodes inside them", default
=True)
1327 def is_unused_node(self
, node
):
1328 end_types
= ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
1329 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LAMP', \
1330 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
1331 if node
.type in end_types
:
1334 for output
in node
.outputs
:
1340 def poll(cls
, context
):
1342 if nw_check(context
):
1343 if context
.space_data
.node_tree
.nodes
:
1347 def execute(self
, context
):
1348 nodes
, links
= get_nodes_links(context
)
1353 if node
.select
== True:
1354 selection
.append(node
.name
)
1360 temp_deleted_nodes
= []
1361 del_unused_iterations
= len(nodes
)
1362 for it
in range(0, del_unused_iterations
):
1363 temp_deleted_nodes
= list(deleted_nodes
) # keep record of last iteration
1365 if self
.is_unused_node(node
):
1367 deleted_nodes
.append(node
.name
)
1368 bpy
.ops
.node
.delete()
1370 if temp_deleted_nodes
== deleted_nodes
: # stop iterations when there are no more nodes to be deleted
1373 if self
.delete_frames
:
1381 frames_in_use
.append(node
.parent
)
1383 if node
.type == 'FRAME' and node
not in frames_in_use
:
1386 repeat
= True # repeat for nested frames
1388 if node
not in frames_in_use
:
1390 deleted_nodes
.append(node
.name
)
1391 bpy
.ops
.node
.delete()
1393 if self
.delete_muted
:
1397 deleted_nodes
.append(node
.name
)
1398 bpy
.ops
.node
.delete_reconnect()
1400 # get unique list of deleted nodes (iterations would count the same node more than once)
1401 deleted_nodes
= list(set(deleted_nodes
))
1402 for n
in deleted_nodes
:
1403 self
.report({'INFO'}, "Node " + n
+ " deleted")
1404 num_deleted
= len(deleted_nodes
)
1409 self
.report({'INFO'}, "Deleted " + str(num_deleted
) + n
)
1411 self
.report({'INFO'}, "Nothing deleted")
1414 nodes
, links
= get_nodes_links(context
)
1416 if node
.name
in selection
:
1420 def invoke(self
, context
, event
):
1421 return context
.window_manager
.invoke_confirm(self
, event
)
1424 class NWSwapLinks(Operator
, NWBase
):
1425 """Swap the output connections of the two selected nodes, or two similar inputs of a single node"""
1426 bl_idname
= 'node.nw_swap_links'
1427 bl_label
= 'Swap Links'
1428 bl_options
= {'REGISTER', 'UNDO'}
1431 def poll(cls
, context
):
1433 if nw_check(context
):
1434 if context
.selected_nodes
:
1435 valid
= len(context
.selected_nodes
) <= 2
1438 def execute(self
, context
):
1439 nodes
, links
= get_nodes_links(context
)
1440 selected_nodes
= context
.selected_nodes
1441 n1
= selected_nodes
[0]
1444 if len(selected_nodes
) == 2:
1445 n2
= selected_nodes
[1]
1446 if n1
.outputs
and n2
.outputs
:
1451 for output
in n1
.outputs
:
1453 for link
in output
.links
:
1454 n1_outputs
.append([out_index
, link
.to_socket
])
1459 for output
in n2
.outputs
:
1461 for link
in output
.links
:
1462 n2_outputs
.append([out_index
, link
.to_socket
])
1466 for connection
in n1_outputs
:
1468 links
.new(n2
.outputs
[connection
[0]], connection
[1])
1470 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1471 for connection
in n2_outputs
:
1473 links
.new(n1
.outputs
[connection
[0]], connection
[1])
1475 self
.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
1477 if n1
.outputs
or n2
.outputs
:
1478 self
.report({'WARNING'}, "One of the nodes has no outputs!")
1480 self
.report({'WARNING'}, "Neither of the nodes have outputs!")
1483 elif len(selected_nodes
) == 1:
1487 for i1
in n1
.inputs
:
1490 for i2
in n1
.inputs
:
1491 if i1
.type == i2
.type and i2
.is_linked
:
1493 types
.append ([i1
, similar_types
, i
])
1495 types
.sort(key
=lambda k
: k
[1], reverse
=True)
1500 for i2
in n1
.inputs
:
1501 if t
[0].type == i2
.type == t
[0].type and t
[0] != i2
and i2
.is_linked
:
1503 i1f
= pair
[0].links
[0].from_socket
1504 i1t
= pair
[0].links
[0].to_socket
1505 i2f
= pair
[1].links
[0].from_socket
1506 i2t
= pair
[1].links
[0].to_socket
1511 fs
= t
[0].links
[0].from_socket
1513 links
.remove(t
[0].links
[0])
1514 if i
+1 == len(n1
.inputs
):
1517 while n1
.inputs
[i
].is_linked
:
1519 links
.new(fs
, n1
.inputs
[i
])
1520 elif len(types
) == 2:
1521 i1f
= types
[0][0].links
[0].from_socket
1522 i1t
= types
[0][0].links
[0].to_socket
1523 i2f
= types
[1][0].links
[0].from_socket
1524 i2t
= types
[1][0].links
[0].to_socket
1529 self
.report({'WARNING'}, "This node has no input connections to swap!")
1531 self
.report({'WARNING'}, "This node has no inputs to swap!")
1533 force_update(context
)
1537 class NWResetBG(Operator
, NWBase
):
1538 """Reset the zoom and position of the background image"""
1539 bl_idname
= 'node.nw_bg_reset'
1540 bl_label
= 'Reset Backdrop'
1541 bl_options
= {'REGISTER', 'UNDO'}
1544 def poll(cls
, context
):
1546 if nw_check(context
):
1547 snode
= context
.space_data
1548 valid
= snode
.tree_type
== 'CompositorNodeTree'
1551 def execute(self
, context
):
1552 context
.space_data
.backdrop_zoom
= 1
1553 context
.space_data
.backdrop_x
= 0
1554 context
.space_data
.backdrop_y
= 0
1558 class NWAddAttrNode(Operator
, NWBase
):
1559 """Add an Attribute node with this name"""
1560 bl_idname
= 'node.nw_add_attr_node'
1561 bl_label
= 'Add UV map'
1562 attr_name
= StringProperty()
1563 bl_options
= {'REGISTER', 'UNDO'}
1565 def execute(self
, context
):
1566 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type="ShaderNodeAttribute")
1567 nodes
, links
= get_nodes_links(context
)
1568 nodes
.active
.attribute_name
= self
.attr_name
1572 class NWEmissionViewer(Operator
, NWBase
):
1573 bl_idname
= "node.nw_emission_viewer"
1574 bl_label
= "Emission Viewer"
1575 bl_description
= "Connect active node to Emission Shader for shadeless previews"
1576 bl_options
= {'REGISTER', 'UNDO'}
1579 def poll(cls
, context
):
1580 is_cycles
= context
.scene
.render
.engine
== 'CYCLES'
1581 if nw_check(context
):
1582 space
= context
.space_data
1583 if space
.tree_type
== 'ShaderNodeTree' and is_cycles
:
1584 if context
.active_node
:
1585 if context
.active_node
.type != "OUTPUT_MATERIAL" or context
.active_node
.type != "OUTPUT_WORLD":
1591 def invoke(self
, context
, event
):
1592 space
= context
.space_data
1593 shader_type
= space
.shader_type
1594 if shader_type
== 'OBJECT':
1595 if space
.id not in [lamp
for lamp
in bpy
.data
.lamps
]: # cannot use bpy.data.lamps directly as iterable
1596 shader_output_type
= "OUTPUT_MATERIAL"
1597 shader_output_ident
= "ShaderNodeOutputMaterial"
1598 shader_viewer_ident
= "ShaderNodeEmission"
1600 shader_output_type
= "OUTPUT_LAMP"
1601 shader_output_ident
= "ShaderNodeOutputLamp"
1602 shader_viewer_ident
= "ShaderNodeEmission"
1604 elif shader_type
== 'WORLD':
1605 shader_output_type
= "OUTPUT_WORLD"
1606 shader_output_ident
= "ShaderNodeOutputWorld"
1607 shader_viewer_ident
= "ShaderNodeBackground"
1608 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
]
1609 mlocx
= event
.mouse_region_x
1610 mlocy
= event
.mouse_region_y
1611 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
1612 if 'FINISHED' in select_node
: # only run if mouse click is on a node
1613 nodes
, links
= get_nodes_links(context
)
1614 in_group
= context
.active_node
!= space
.node_tree
.nodes
.active
1615 active
= nodes
.active
1616 output_types
= [x
[1] for x
in shaders_output_nodes_props
]
1619 if (active
.name
!= "Emission Viewer") and (active
.type not in output_types
) and not in_group
:
1620 for out
in active
.outputs
:
1625 # get material_output node, store selection, deselect all
1626 materialout
= None # placeholder node
1629 if node
.type == shader_output_type
:
1632 selection
.append(node
.name
)
1635 # get right-most location
1636 sorted_by_xloc
= (sorted(nodes
, key
=lambda x
: x
.location
.x
))
1637 max_xloc_node
= sorted_by_xloc
[-1]
1638 if max_xloc_node
.name
== 'Emission Viewer':
1639 max_xloc_node
= sorted_by_xloc
[-2]
1641 # get average y location
1644 sum_yloc
+= node
.location
.y
1646 new_locx
= max_xloc_node
.location
.x
+ max_xloc_node
.dimensions
.x
+ 80
1647 new_locy
= sum_yloc
/ len(nodes
)
1649 materialout
= nodes
.new(shader_output_ident
)
1650 materialout
.location
.x
= new_locx
1651 materialout
.location
.y
= new_locy
1652 materialout
.select
= False
1653 # Analyze outputs, add "Emission Viewer" if needed, make links
1656 for i
, out
in enumerate(active
.outputs
):
1658 valid_outputs
.append(i
)
1660 out_i
= valid_outputs
[0] # Start index of node's outputs
1661 for i
, valid_i
in enumerate(valid_outputs
):
1662 for out_link
in active
.outputs
[valid_i
].links
:
1663 if "Emission Viewer" in out_link
.to_node
.name
or (out_link
.to_node
== materialout
and out_link
.to_socket
== materialout
.inputs
[0]):
1664 if i
< len(valid_outputs
) - 1:
1665 out_i
= valid_outputs
[i
+ 1]
1667 out_i
= valid_outputs
[0]
1668 make_links
= [] # store sockets for new links
1670 # If output type not 'SHADER' - "Emission Viewer" needed
1671 if active
.outputs
[out_i
].type != 'SHADER':
1672 # get Emission Viewer node
1673 emission_exists
= False
1674 emission_placeholder
= nodes
[0]
1676 if "Emission Viewer" in node
.name
:
1677 emission_exists
= True
1678 emission_placeholder
= node
1679 if not emission_exists
:
1680 emission
= nodes
.new(shader_viewer_ident
)
1681 emission
.hide
= True
1682 emission
.location
= [materialout
.location
.x
, (materialout
.location
.y
+ 40)]
1683 emission
.label
= "Viewer"
1684 emission
.name
= "Emission Viewer"
1685 emission
.use_custom_color
= True
1686 emission
.color
= (0.6, 0.5, 0.4)
1687 emission
.select
= False
1689 emission
= emission_placeholder
1690 make_links
.append((active
.outputs
[out_i
], emission
.inputs
[0]))
1692 # If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
1693 if emission
.outputs
[0].links
.__len
__() > 0:
1694 if not emission
.outputs
[0].links
[0].to_node
== materialout
:
1695 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1697 make_links
.append((emission
.outputs
[0], materialout
.inputs
[0]))
1699 # Set brightness of viewer to compensate for Film and CM exposure
1700 intensity
= 1/context
.scene
.cycles
.film_exposure
# Film exposure is a multiplier
1701 intensity
/= pow(2, (context
.scene
.view_settings
.exposure
)) # CM exposure is measured in stops/EVs (2^x)
1702 emission
.inputs
[1].default_value
= intensity
1705 # Output type is 'SHADER', no Viewer needed. Delete Viewer if exists.
1706 make_links
.append((active
.outputs
[out_i
], materialout
.inputs
[1 if active
.outputs
[out_i
].name
== "Volume" else 0]))
1708 if node
.name
== 'Emission Viewer':
1710 bpy
.ops
.node
.delete()
1711 for li_from
, li_to
in make_links
:
1712 links
.new(li_from
, li_to
)
1714 nodes
.active
= active
1716 if node
.name
in selection
:
1718 force_update(context
)
1721 return {'CANCELLED'}
1724 class NWFrameSelected(Operator
, NWBase
):
1725 bl_idname
= "node.nw_frame_selected"
1726 bl_label
= "Frame Selected"
1727 bl_description
= "Add a frame node and parent the selected nodes to it"
1728 bl_options
= {'REGISTER', 'UNDO'}
1729 label_prop
= StringProperty(name
='Label', default
=' ', description
='The visual name of the frame node')
1730 color_prop
= FloatVectorProperty(name
="Color", description
="The color of the frame node", default
=(0.6, 0.6, 0.6),
1731 min=0, max=1, step
=1, precision
=3, subtype
='COLOR_GAMMA', size
=3)
1733 def execute(self
, context
):
1734 nodes
, links
= get_nodes_links(context
)
1737 if node
.select
== True:
1738 selected
.append(node
)
1740 bpy
.ops
.node
.add_node(type='NodeFrame')
1742 frm
.label
= self
.label_prop
1743 frm
.use_custom_color
= True
1744 frm
.color
= self
.color_prop
1746 for node
in selected
:
1752 class NWReloadImages(Operator
, NWBase
):
1753 bl_idname
= "node.nw_reload_images"
1754 bl_label
= "Reload Images"
1755 bl_description
= "Update all the image nodes to match their files on disk"
1757 def execute(self
, context
):
1758 nodes
, links
= get_nodes_links(context
)
1759 image_types
= ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
1762 if node
.type in image_types
:
1763 if node
.type == "TEXTURE":
1764 if node
.texture
: # node has texture assigned
1765 if node
.texture
.type in ['IMAGE', 'ENVIRONMENT_MAP']:
1766 if node
.texture
.image
: # texture has image assigned
1767 node
.texture
.image
.reload()
1775 self
.report({'INFO'}, "Reloaded images")
1776 print("Reloaded " + str(num_reloaded
) + " images")
1777 force_update(context
)
1780 self
.report({'WARNING'}, "No images found to reload in this node tree")
1781 return {'CANCELLED'}
1784 class NWSwitchNodeType(Operator
, NWBase
):
1785 """Switch type of selected nodes """
1786 bl_idname
= "node.nw_swtch_node_type"
1787 bl_label
= "Switch Node Type"
1788 bl_options
= {'REGISTER', 'UNDO'}
1790 to_type
= EnumProperty(
1791 name
="Switch to type",
1792 items
=list(shaders_input_nodes_props
) +
1793 list(shaders_output_nodes_props
) +
1794 list(shaders_shader_nodes_props
) +
1795 list(shaders_texture_nodes_props
) +
1796 list(shaders_color_nodes_props
) +
1797 list(shaders_vector_nodes_props
) +
1798 list(shaders_converter_nodes_props
) +
1799 list(shaders_layout_nodes_props
) +
1800 list(compo_input_nodes_props
) +
1801 list(compo_output_nodes_props
) +
1802 list(compo_color_nodes_props
) +
1803 list(compo_converter_nodes_props
) +
1804 list(compo_filter_nodes_props
) +
1805 list(compo_vector_nodes_props
) +
1806 list(compo_matte_nodes_props
) +
1807 list(compo_distort_nodes_props
) +
1808 list(compo_layout_nodes_props
) +
1809 list(blender_mat_input_nodes_props
) +
1810 list(blender_mat_output_nodes_props
) +
1811 list(blender_mat_color_nodes_props
) +
1812 list(blender_mat_vector_nodes_props
) +
1813 list(blender_mat_converter_nodes_props
) +
1814 list(blender_mat_layout_nodes_props
) +
1815 list(texture_input_nodes_props
) +
1816 list(texture_output_nodes_props
) +
1817 list(texture_color_nodes_props
) +
1818 list(texture_pattern_nodes_props
) +
1819 list(texture_textures_nodes_props
) +
1820 list(texture_converter_nodes_props
) +
1821 list(texture_distort_nodes_props
) +
1822 list(texture_layout_nodes_props
)
1825 def execute(self
, context
):
1826 nodes
, links
= get_nodes_links(context
)
1827 to_type
= self
.to_type
1828 # Those types of nodes will not swap.
1829 src_excludes
= ('NodeFrame')
1830 # Those attributes of nodes will be copied if possible
1831 attrs_to_pass
= ('color', 'hide', 'label', 'mute', 'parent',
1832 'show_options', 'show_preview', 'show_texture',
1833 'use_alpha', 'use_clamp', 'use_custom_color', 'location'
1835 selected
= [n
for n
in nodes
if n
.select
]
1837 for node
in [n
for n
in selected
if
1838 n
.rna_type
.identifier
not in src_excludes
and
1839 n
.rna_type
.identifier
!= to_type
]:
1840 new_node
= nodes
.new(to_type
)
1841 for attr
in attrs_to_pass
:
1842 if hasattr(node
, attr
) and hasattr(new_node
, attr
):
1843 setattr(new_node
, attr
, getattr(node
, attr
))
1844 # set image datablock of dst to image of src
1845 if hasattr(node
, 'image') and hasattr(new_node
, 'image'):
1847 new_node
.image
= node
.image
1849 if new_node
.type == 'SWITCH':
1850 new_node
.hide
= True
1851 # Dictionaries: src_sockets and dst_sockets:
1852 # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
1853 # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
1854 # in 'INPUTS' and 'OUTPUTS':
1855 # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
1857 # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1859 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1860 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1863 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1864 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
1866 types_order_one
= 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
1867 types_order_two
= 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
1868 # check src node to set src_sockets values and dst node to set dst_sockets dict values
1869 for sockets
, nd
in ((src_sockets
, node
), (dst_sockets
, new_node
)):
1870 # Check node's inputs and outputs and fill proper entries in "sockets" dict
1871 for in_out
, in_out_name
in ((nd
.inputs
, 'INPUTS'), (nd
.outputs
, 'OUTPUTS')):
1872 # enumerate in inputs, then in outputs
1873 # find name, default value and links of socket
1874 for i
, socket
in enumerate(in_out
):
1875 the_name
= socket
.name
1877 # Not every socket, especially in outputs has "default_value"
1878 if hasattr(socket
, 'default_value'):
1879 dval
= socket
.default_value
1881 for lnk
in socket
.links
:
1882 socket_links
.append(lnk
)
1883 # check type of socket to fill proper keys.
1884 for the_type
in types_order_one
:
1885 if socket
.type == the_type
:
1886 # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
1887 # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
1888 sockets
[in_out_name
][the_type
].append((len(sockets
[in_out_name
][the_type
]), i
, the_name
, dval
, socket_links
))
1889 # Check which of the types in inputs/outputs is considered to be "main".
1890 # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
1891 for type_check
in types_order_one
:
1892 if sockets
[in_out_name
][type_check
]:
1893 sockets
[in_out_name
]['MAIN'] = type_check
1897 'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1898 'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
1901 for inout
, soctype
in (
1902 ('INPUTS', 'MAIN',),
1903 ('INPUTS', 'SHADER',),
1904 ('INPUTS', 'RGBA',),
1905 ('INPUTS', 'VECTOR',),
1906 ('INPUTS', 'VALUE',),
1907 ('OUTPUTS', 'MAIN',),
1908 ('OUTPUTS', 'SHADER',),
1909 ('OUTPUTS', 'RGBA',),
1910 ('OUTPUTS', 'VECTOR',),
1911 ('OUTPUTS', 'VALUE',),
1913 if src_sockets
[inout
][soctype
] and dst_sockets
[inout
][soctype
]:
1914 if soctype
== 'MAIN':
1915 sc
= src_sockets
[inout
][src_sockets
[inout
]['MAIN']]
1916 dt
= dst_sockets
[inout
][dst_sockets
[inout
]['MAIN']]
1918 sc
= src_sockets
[inout
][soctype
]
1919 dt
= dst_sockets
[inout
][soctype
]
1920 # start with 'dt' to determine number of possibilities.
1921 for i
, soc
in enumerate(dt
):
1922 # if src main has enough entries - match them with dst main sockets by indexes.
1924 matches
[inout
][soctype
].append(((sc
[i
][1], sc
[i
][3]), (soc
[1], soc
[3])))
1925 # add 'VALUE_NAME' criterion to inputs.
1926 if inout
== 'INPUTS' and soctype
== 'VALUE':
1928 if s
[2] == soc
[2]: # if names match
1929 # append src (index, dval), dst (index, dval)
1930 matches
['INPUTS']['VALUE_NAME'].append(((s
[1], s
[3]), (soc
[1], soc
[3])))
1932 # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
1933 # This creates better links when relinking textures.
1934 if src_sockets
['INPUTS']['MAIN'] == 'VECTOR' and matches
['INPUTS']['VECTOR']:
1935 matches
['INPUTS']['MAIN'] = matches
['INPUTS']['VECTOR']
1937 # Pass default values and RELINK:
1938 for tp
in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
1939 # INPUTS: Base on matches in proper order.
1940 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['INPUTS'][tp
]:
1942 if src_dval
and dst_dval
and tp
in {'RGBA', 'VALUE_NAME'}:
1943 new_node
.inputs
[dst_i
].default_value
= src_dval
1944 # Special case: switch to math
1945 if node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1946 new_node
.type == 'MATH' and\
1948 new_dst_dval
= max(src_dval
[0], src_dval
[1], src_dval
[2])
1949 new_node
.inputs
[dst_i
].default_value
= new_dst_dval
1950 if node
.type == 'MIX_RGB':
1951 if node
.blend_type
in [o
[0] for o
in operations
]:
1952 new_node
.operation
= node
.blend_type
1953 # Special case: switch from math to some types
1954 if node
.type == 'MATH' and\
1955 new_node
.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
1958 new_node
.inputs
[dst_i
].default_value
[i
] = src_dval
1959 if new_node
.type == 'MIX_RGB':
1960 if node
.operation
in [t
[0] for t
in blend_types
]:
1961 new_node
.blend_type
= node
.operation
1962 # Set Fac of MIX_RGB to 1.0
1963 new_node
.inputs
[0].default_value
= 1.0
1964 # make link only when dst matching input is not linked already.
1965 if node
.inputs
[src_i
].links
and not new_node
.inputs
[dst_i
].links
:
1966 in_src_link
= node
.inputs
[src_i
].links
[0]
1967 in_dst_socket
= new_node
.inputs
[dst_i
]
1968 links
.new(in_src_link
.from_socket
, in_dst_socket
)
1969 links
.remove(in_src_link
)
1970 # OUTPUTS: Base on matches in proper order.
1971 for (src_i
, src_dval
), (dst_i
, dst_dval
) in matches
['OUTPUTS'][tp
]:
1972 for out_src_link
in node
.outputs
[src_i
].links
:
1973 out_dst_socket
= new_node
.outputs
[dst_i
]
1974 links
.new(out_dst_socket
, out_src_link
.to_socket
)
1975 # relink rest inputs if possible, no criteria
1976 for src_inp
in node
.inputs
:
1977 for dst_inp
in new_node
.inputs
:
1978 if src_inp
.links
and not dst_inp
.links
:
1979 src_link
= src_inp
.links
[0]
1980 links
.new(src_link
.from_socket
, dst_inp
)
1981 links
.remove(src_link
)
1982 # relink rest outputs if possible, base on node kind if any left.
1983 for src_o
in node
.outputs
:
1984 for out_src_link
in src_o
.links
:
1985 for dst_o
in new_node
.outputs
:
1986 if src_o
.type == dst_o
.type:
1987 links
.new(dst_o
, out_src_link
.to_socket
)
1988 # relink rest outputs no criteria if any left. Link all from first output.
1989 for src_o
in node
.outputs
:
1990 for out_src_link
in src_o
.links
:
1991 if new_node
.outputs
:
1992 links
.new(new_node
.outputs
[0], out_src_link
.to_socket
)
1994 force_update(context
)
1998 class NWMergeNodes(Operator
, NWBase
):
1999 bl_idname
= "node.nw_merge_nodes"
2000 bl_label
= "Merge Nodes"
2001 bl_description
= "Merge Selected Nodes"
2002 bl_options
= {'REGISTER', 'UNDO'}
2004 mode
= EnumProperty(
2006 description
="All possible blend types and math operations",
2007 items
=blend_types
+ [op
for op
in operations
if op
not in blend_types
],
2009 merge_type
= EnumProperty(
2011 description
="Type of Merge to be used",
2013 ('AUTO', 'Auto', 'Automatic Output Type Detection'),
2014 ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
2015 ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
2016 ('MATH', 'Math Node', 'Merge using Math Nodes'),
2017 ('ZCOMBINE', 'Z-Combine Node', 'Merge using Z-Combine Nodes'),
2018 ('ALPHAOVER', 'Alpha Over Node', 'Merge using Alpha Over Nodes'),
2022 def execute(self
, context
):
2023 settings
= context
.user_preferences
.addons
[__name__
].preferences
2024 merge_hide
= settings
.merge_hide
2025 merge_position
= settings
.merge_position
# 'center' or 'bottom'
2028 do_hide_shader
= False
2029 if merge_hide
== 'ALWAYS':
2031 do_hide_shader
= True
2032 elif merge_hide
== 'NON_SHADER':
2035 tree_type
= context
.space_data
.node_tree
.type
2036 if tree_type
== 'COMPOSITING':
2037 node_type
= 'CompositorNode'
2038 elif tree_type
== 'SHADER':
2039 node_type
= 'ShaderNode'
2040 elif tree_type
== 'TEXTURE':
2041 node_type
= 'TextureNode'
2042 nodes
, links
= get_nodes_links(context
)
2044 merge_type
= self
.merge_type
2045 # Prevent trying to add Z-Combine in not 'COMPOSITING' node tree.
2046 # 'ZCOMBINE' works only if mode == 'MIX'
2047 # Setting mode to None prevents trying to add 'ZCOMBINE' node.
2048 if (merge_type
== 'ZCOMBINE' or merge_type
== 'ALPHAOVER') and tree_type
!= 'COMPOSITING':
2051 selected_mix
= [] # entry = [index, loc]
2052 selected_shader
= [] # entry = [index, loc]
2053 selected_math
= [] # entry = [index, loc]
2054 selected_z
= [] # entry = [index, loc]
2055 selected_alphaover
= [] # entry = [index, loc]
2057 for i
, node
in enumerate(nodes
):
2058 if node
.select
and node
.outputs
:
2059 if merge_type
== 'AUTO':
2060 for (type, types_list
, dst
) in (
2061 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2062 ('RGBA', [t
[0] for t
in blend_types
], selected_mix
),
2063 ('VALUE', [t
[0] for t
in operations
], selected_math
),
2065 output_type
= node
.outputs
[0].type
2066 valid_mode
= mode
in types_list
2067 # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
2068 # Cheat that output type is 'RGBA',
2069 # and that 'MIX' exists in math operations list.
2070 # This way when selected_mix list is analyzed:
2071 # Node data will be appended even though it doesn't meet requirements.
2072 if output_type
!= 'SHADER' and mode
== 'MIX':
2073 output_type
= 'RGBA'
2075 if output_type
== type and valid_mode
:
2076 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2078 for (type, types_list
, dst
) in (
2079 ('SHADER', ('MIX', 'ADD'), selected_shader
),
2080 ('MIX', [t
[0] for t
in blend_types
], selected_mix
),
2081 ('MATH', [t
[0] for t
in operations
], selected_math
),
2082 ('ZCOMBINE', ('MIX', ), selected_z
),
2083 ('ALPHAOVER', ('MIX', ), selected_alphaover
),
2085 if merge_type
== type and mode
in types_list
:
2086 dst
.append([i
, node
.location
.x
, node
.location
.y
, node
.dimensions
.x
, node
.hide
])
2087 # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
2088 # use only 'Mix' nodes for merging.
2089 # For that we add selected_math list to selected_mix list and clear selected_math.
2090 if selected_mix
and selected_math
and merge_type
== 'AUTO':
2091 selected_mix
+= selected_math
2094 for nodes_list
in [selected_mix
, selected_shader
, selected_math
, selected_z
, selected_alphaover
]:
2096 count_before
= len(nodes
)
2097 # sort list by loc_x - reversed
2098 nodes_list
.sort(key
=lambda k
: k
[1], reverse
=True)
2100 loc_x
= nodes_list
[0][1] + nodes_list
[0][3] + 70
2101 nodes_list
.sort(key
=lambda k
: k
[2], reverse
=True)
2102 if merge_position
== 'CENTER':
2103 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)
2104 if nodes_list
[len(nodes_list
) - 1][-1] == True: # if last node is hidden, mix should be shifted up a bit
2110 loc_y
= nodes_list
[len(nodes_list
) - 1][2]
2114 if nodes_list
== selected_shader
and not do_hide_shader
:
2116 the_range
= len(nodes_list
) - 1
2117 if len(nodes_list
) == 1:
2119 for i
in range(the_range
):
2120 if nodes_list
== selected_mix
:
2121 add_type
= node_type
+ 'MixRGB'
2122 add
= nodes
.new(add_type
)
2123 add
.blend_type
= mode
2125 add
.inputs
[0].default_value
= 1.0
2126 add
.show_preview
= False
2132 add
.width_hidden
= 100.0
2133 elif nodes_list
== selected_math
:
2134 add_type
= node_type
+ 'Math'
2135 add
= nodes
.new(add_type
)
2136 add
.operation
= mode
2142 add
.width_hidden
= 100.0
2143 elif nodes_list
== selected_shader
:
2145 add_type
= node_type
+ 'MixShader'
2146 add
= nodes
.new(add_type
)
2147 add
.hide
= do_hide_shader
2152 add
.width_hidden
= 100.0
2154 add_type
= node_type
+ 'AddShader'
2155 add
= nodes
.new(add_type
)
2156 add
.hide
= do_hide_shader
2161 add
.width_hidden
= 100.0
2162 elif nodes_list
== selected_z
:
2163 add
= nodes
.new('CompositorNodeZcombine')
2164 add
.show_preview
= False
2170 add
.width_hidden
= 100.0
2171 elif nodes_list
== selected_alphaover
:
2172 add
= nodes
.new('CompositorNodeAlphaOver')
2173 add
.show_preview
= False
2179 add
.width_hidden
= 100.0
2180 add
.location
= loc_x
, loc_y
2184 count_after
= len(nodes
)
2185 index
= count_after
- 1
2186 first_selected
= nodes
[nodes_list
[0][0]]
2187 # "last" node has been added as first, so its index is count_before.
2188 last_add
= nodes
[count_before
]
2190 # Two nodes were selected and first selected has no output links, second selected has output links.
2191 # Then add links from last add to all links 'to_socket' of out links of second selected.
2192 if len(nodes_list
) == 2:
2193 if not first_selected
.outputs
[0].links
:
2194 second_selected
= nodes
[nodes_list
[1][0]]
2195 for ss_link
in second_selected
.outputs
[0].links
:
2196 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2197 # Create list of invalid indexes.
2198 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2199 # Link only if "to_node" index not in invalid indexes list.
2200 if ss_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2201 links
.new(last_add
.outputs
[0], ss_link
.to_socket
)
2202 # add links from last_add to all links 'to_socket' of out links of first selected.
2203 for fs_link
in first_selected
.outputs
[0].links
:
2204 # Prevent cyclic dependencies when nodes to be marged are linked to one another.
2205 # Create list of invalid indexes.
2206 invalid_i
= [n
[0] for n
in (selected_mix
+ selected_math
+ selected_shader
+ selected_z
)]
2207 # Link only if "to_node" index not in invalid indexes list.
2208 if fs_link
.to_node
not in [nodes
[i
] for i
in invalid_i
]:
2209 links
.new(last_add
.outputs
[0], fs_link
.to_socket
)
2210 # add link from "first" selected and "first" add node
2211 node_to
= nodes
[count_after
- 1]
2212 links
.new(first_selected
.outputs
[0], node_to
.inputs
[first
])
2213 if node_to
.type == 'ZCOMBINE':
2214 for fs_out
in first_selected
.outputs
:
2215 if fs_out
!= first_selected
.outputs
[0] and fs_out
.name
in ('Z', 'Depth'):
2216 links
.new(fs_out
, node_to
.inputs
[1])
2218 # add links between added ADD nodes and between selected and ADD nodes
2219 for i
in range(count_adds
):
2220 if i
< count_adds
- 1:
2221 node_from
= nodes
[index
]
2222 node_to
= nodes
[index
- 1]
2223 node_to_input_i
= first
2224 node_to_z_i
= 1 # if z combine - link z to first z input
2225 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2226 if node_to
.type == 'ZCOMBINE':
2227 for from_out
in node_from
.outputs
:
2228 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2229 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2230 if len(nodes_list
) > 1:
2231 node_from
= nodes
[nodes_list
[i
+ 1][0]]
2232 node_to
= nodes
[index
]
2233 node_to_input_i
= second
2234 node_to_z_i
= 3 # if z combine - link z to second z input
2235 links
.new(node_from
.outputs
[0], node_to
.inputs
[node_to_input_i
])
2236 if node_to
.type == 'ZCOMBINE':
2237 for from_out
in node_from
.outputs
:
2238 if from_out
!= node_from
.outputs
[0] and from_out
.name
in ('Z', 'Depth'):
2239 links
.new(from_out
, node_to
.inputs
[node_to_z_i
])
2241 # set "last" of added nodes as active
2242 nodes
.active
= last_add
2243 for i
, x
, y
, dx
, h
in nodes_list
:
2244 nodes
[i
].select
= False
2249 class NWBatchChangeNodes(Operator
, NWBase
):
2250 bl_idname
= "node.nw_batch_change"
2251 bl_label
= "Batch Change"
2252 bl_description
= "Batch Change Blend Type and Math Operation"
2253 bl_options
= {'REGISTER', 'UNDO'}
2255 blend_type
= EnumProperty(
2257 items
=blend_types
+ navs
,
2259 operation
= EnumProperty(
2261 items
=operations
+ navs
,
2264 def execute(self
, context
):
2266 nodes
, links
= get_nodes_links(context
)
2267 blend_type
= self
.blend_type
2268 operation
= self
.operation
2269 for node
in context
.selected_nodes
:
2270 if node
.type == 'MIX_RGB':
2271 if not blend_type
in [nav
[0] for nav
in navs
]:
2272 node
.blend_type
= blend_type
2274 if blend_type
== 'NEXT':
2275 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2276 #index = blend_types.index(node.blend_type)
2277 if index
== len(blend_types
) - 1:
2278 node
.blend_type
= blend_types
[0][0]
2280 node
.blend_type
= blend_types
[index
+ 1][0]
2282 if blend_type
== 'PREV':
2283 index
= [i
for i
, entry
in enumerate(blend_types
) if node
.blend_type
in entry
][0]
2285 node
.blend_type
= blend_types
[len(blend_types
) - 1][0]
2287 node
.blend_type
= blend_types
[index
- 1][0]
2289 if node
.type == 'MATH':
2290 if not operation
in [nav
[0] for nav
in navs
]:
2291 node
.operation
= operation
2293 if operation
== 'NEXT':
2294 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2295 #index = operations.index(node.operation)
2296 if index
== len(operations
) - 1:
2297 node
.operation
= operations
[0][0]
2299 node
.operation
= operations
[index
+ 1][0]
2301 if operation
== 'PREV':
2302 index
= [i
for i
, entry
in enumerate(operations
) if node
.operation
in entry
][0]
2303 #index = operations.index(node.operation)
2305 node
.operation
= operations
[len(operations
) - 1][0]
2307 node
.operation
= operations
[index
- 1][0]
2312 class NWChangeMixFactor(Operator
, NWBase
):
2313 bl_idname
= "node.nw_factor"
2314 bl_label
= "Change Factor"
2315 bl_description
= "Change Factors of Mix Nodes and Mix Shader Nodes"
2316 bl_options
= {'REGISTER', 'UNDO'}
2318 # option: Change factor.
2319 # If option is 1.0 or 0.0 - set to 1.0 or 0.0
2320 # Else - change factor by option value.
2321 option
= FloatProperty()
2323 def execute(self
, context
):
2324 nodes
, links
= get_nodes_links(context
)
2325 option
= self
.option
2326 selected
= [] # entry = index
2327 for si
, node
in enumerate(nodes
):
2329 if node
.type in {'MIX_RGB', 'MIX_SHADER'}:
2333 fac
= nodes
[si
].inputs
[0]
2334 nodes
[si
].hide
= False
2335 if option
in {0.0, 1.0}:
2336 fac
.default_value
= option
2338 fac
.default_value
+= option
2343 class NWCopySettings(Operator
, NWBase
):
2344 bl_idname
= "node.nw_copy_settings"
2345 bl_label
= "Copy Settings"
2346 bl_description
= "Copy Settings of Active Node to Selected Nodes"
2347 bl_options
= {'REGISTER', 'UNDO'}
2350 def poll(cls
, context
):
2352 if nw_check(context
):
2353 if context
.active_node
is not None and context
.active_node
.type is not 'FRAME':
2357 def execute(self
, context
):
2358 node_active
= context
.active_node
2359 node_selected
= context
.selected_nodes
2362 if not (len(node_selected
) > 1):
2363 self
.report({'ERROR'}, "2 nodes must be selected at least")
2364 return {'CANCELLED'}
2366 # Check if active node is in the selection
2367 selected_node_names
= [n
.name
for n
in node_selected
]
2368 if node_active
.name
not in selected_node_names
:
2369 self
.report({'ERROR'}, "No active node")
2370 return {'CANCELLED'}
2372 # Get nodes in selection by type
2373 valid_nodes
= [n
for n
in node_selected
if n
.type == node_active
.type]
2375 if not (len(valid_nodes
) > 1) and node_active
:
2376 self
.report({'ERROR'}, "Selected nodes are not of the same type as {}".format(node_active
.name
))
2377 return {'CANCELLED'}
2379 if len(valid_nodes
) != len(node_selected
):
2380 # Report nodes that are not valid
2381 valid_node_names
= [n
.name
for n
in valid_nodes
]
2382 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
2383 self
.report({'INFO'}, "Ignored {} (not of the same type as {})".format(", ".join(not_valid_names
), node_active
.name
))
2385 # Reference original
2387 #node_selected_names = [n.name for n in node_selected]
2392 # Deselect all nodes
2393 for i
in node_selected
:
2396 # Code by zeffii from http://blender.stackexchange.com/a/42338/3710
2397 # Run through all other nodes
2398 for node
in valid_nodes
[1:]:
2400 # Check for frame node
2401 parent
= node
.parent
if node
.parent
else None
2402 node_loc
= [node
.location
.x
, node
.location
.y
]
2404 # Select original to duplicate
2407 # Duplicate selected node
2408 bpy
.ops
.node
.duplicate()
2409 new_node
= context
.selected_nodes
[0]
2412 new_node
.select
= False
2414 # Properties to copy
2415 node_tree
= node
.id_data
2416 props_to_copy
= 'bl_idname name location height width'.split(' ')
2420 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
2421 for i
in (i
for i
in mappings
if i
.is_linked
):
2423 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
2426 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
2427 props_to_copy
.pop(0)
2429 for prop
in props_to_copy
:
2430 setattr(new_node
, prop
, props
[prop
])
2432 # Get the node tree to remove the old node
2433 nodes
= node_tree
.nodes
2435 new_node
.name
= props
['name']
2438 new_node
.parent
= parent
2439 new_node
.location
= node_loc
2441 for str_from
, str_to
in reconnections
:
2442 node_tree
.links
.new(eval(str_from
), eval(str_to
))
2444 success_names
.append(new_node
.name
)
2447 node_tree
.nodes
.active
= orig
2448 self
.report({'INFO'}, "Successfully copied attributes from {} to: {}".format(orig
.name
, ", ".join(success_names
)))
2452 class NWCopyLabel(Operator
, NWBase
):
2453 bl_idname
= "node.nw_copy_label"
2454 bl_label
= "Copy Label"
2455 bl_options
= {'REGISTER', 'UNDO'}
2457 option
= EnumProperty(
2459 description
="Source of name of label",
2461 ('FROM_ACTIVE', 'from active', 'from active node',),
2462 ('FROM_NODE', 'from node', 'from node linked to selected node'),
2463 ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
2467 def execute(self
, context
):
2468 nodes
, links
= get_nodes_links(context
)
2469 option
= self
.option
2470 active
= nodes
.active
2471 if option
== 'FROM_ACTIVE':
2473 src_label
= active
.label
2474 for node
in [n
for n
in nodes
if n
.select
and nodes
.active
!= n
]:
2475 node
.label
= src_label
2476 elif option
== 'FROM_NODE':
2477 selected
= [n
for n
in nodes
if n
.select
]
2478 for node
in selected
:
2479 for input in node
.inputs
:
2481 src
= input.links
[0].from_node
2482 node
.label
= src
.label
2484 elif option
== 'FROM_SOCKET':
2485 selected
= [n
for n
in nodes
if n
.select
]
2486 for node
in selected
:
2487 for input in node
.inputs
:
2489 src
= input.links
[0].from_socket
2490 node
.label
= src
.name
2496 class NWClearLabel(Operator
, NWBase
):
2497 bl_idname
= "node.nw_clear_label"
2498 bl_label
= "Clear Label"
2499 bl_options
= {'REGISTER', 'UNDO'}
2501 option
= BoolProperty()
2503 def execute(self
, context
):
2504 nodes
, links
= get_nodes_links(context
)
2505 for node
in [n
for n
in nodes
if n
.select
]:
2510 def invoke(self
, context
, event
):
2512 return self
.execute(context
)
2514 return context
.window_manager
.invoke_confirm(self
, event
)
2517 class NWModifyLabels(Operator
, NWBase
):
2518 """Modify Labels of all selected nodes"""
2519 bl_idname
= "node.nw_modify_labels"
2520 bl_label
= "Modify Labels"
2521 bl_options
= {'REGISTER', 'UNDO'}
2523 prepend
= StringProperty(
2524 name
="Add to Beginning"
2526 append
= StringProperty(
2529 replace_from
= StringProperty(
2530 name
="Text to Replace"
2532 replace_to
= StringProperty(
2536 def execute(self
, context
):
2537 nodes
, links
= get_nodes_links(context
)
2538 for node
in [n
for n
in nodes
if n
.select
]:
2539 node
.label
= self
.prepend
+ node
.label
.replace(self
.replace_from
, self
.replace_to
) + self
.append
2543 def invoke(self
, context
, event
):
2547 return context
.window_manager
.invoke_props_dialog(self
)
2550 class NWAddTextureSetup(Operator
, NWBase
):
2551 bl_idname
= "node.nw_add_texture"
2552 bl_label
= "Texture Setup"
2553 bl_description
= "Add Texture Node Setup to Selected Shaders"
2554 bl_options
= {'REGISTER', 'UNDO'}
2556 add_mapping
= BoolProperty(name
="Add Mapping Nodes", description
="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default
=True)
2559 def poll(cls
, context
):
2561 if nw_check(context
):
2562 space
= context
.space_data
2563 if space
.tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
2567 def execute(self
, context
):
2568 nodes
, links
= get_nodes_links(context
)
2569 shader_types
= [x
[1] for x
in shaders_shader_nodes_props
if x
[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
2570 texture_types
= [x
[1] for x
in shaders_texture_nodes_props
]
2571 selected_nodes
= [n
for n
in nodes
if n
.select
]
2572 for t_node
in selected_nodes
:
2576 for index
, i
in enumerate(t_node
.inputs
):
2582 locx
= t_node
.location
.x
2583 locy
= t_node
.location
.y
- t_node
.dimensions
.y
/2
2585 xoffset
= [500, 700]
2587 if t_node
.type in texture_types
+ ['MAPPING']:
2588 xoffset
= [290, 500]
2592 image_type
= 'ShaderNodeTexImage'
2594 if (t_node
.type in texture_types
and t_node
.type != 'TEX_IMAGE') or (t_node
.type == 'BACKGROUND'):
2595 coordout
= 0 # image texture uses UVs, procedural textures and Background shader use Generated
2596 if t_node
.type == 'BACKGROUND':
2597 image_type
= 'ShaderNodeTexEnvironment'
2600 tex
= nodes
.new(image_type
)
2601 tex
.location
= [locx
- 200, locy
+ 112]
2603 links
.new(tex
.outputs
[0], t_node
.inputs
[input_index
])
2605 t_node
.select
= False
2606 if self
.add_mapping
or is_texture
:
2607 if t_node
.type != 'MAPPING':
2608 m
= nodes
.new('ShaderNodeMapping')
2609 m
.location
= [locx
- xoffset
[0], locy
+ 141]
2613 coord
= nodes
.new('ShaderNodeTexCoord')
2614 coord
.location
= [locx
- (200 if t_node
.type == 'MAPPING' else xoffset
[1]), locy
+ 124]
2617 links
.new(m
.outputs
[0], tex
.inputs
[0])
2618 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2621 links
.new(m
.outputs
[0], t_node
.inputs
[input_index
])
2622 links
.new(coord
.outputs
[coordout
], m
.inputs
[0])
2624 self
.report({'WARNING'}, "No free inputs for node: "+t_node
.name
)
2628 class NWAddPrincipledSetup(Operator
, NWBase
, ImportHelper
):
2629 bl_idname
= "node.nw_add_textures_for_principled"
2630 bl_label
= "Principled Texture Setup"
2631 bl_description
= "Add Texture Node Setup for Principled BSDF"
2632 bl_options
= {'REGISTER', 'UNDO'}
2634 directory
= StringProperty(
2638 description
='Folder to search in for image files')
2639 files
= CollectionProperty(
2640 type=bpy
.types
.OperatorFileListElement
,
2641 options
={'HIDDEN', 'SKIP_SAVE'})
2649 def poll(cls
, context
):
2651 if nw_check(context
):
2652 space
= context
.space_data
2653 if space
.tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
2657 def execute(self
, context
):
2658 # Check if everything is ok
2659 if not self
.directory
:
2660 self
.report({'INFO'}, 'No Folder Selected')
2661 return {'CANCELLED'}
2662 if not self
.files
[:]:
2663 self
.report({'INFO'}, 'No Files Selected')
2664 return {'CANCELLED'}
2666 nodes
, links
= get_nodes_links(context
)
2667 active_node
= nodes
.active
2668 if not active_node
.bl_idname
== 'ShaderNodeBsdfPrincipled':
2669 self
.report({'INFO'}, 'Select Principled BSDF')
2670 return {'CANCELLED'}
2673 def split_into__components(fname
):
2674 # Split filename into components
2675 # 'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
2677 fname
= path
.splitext(fname
)[0]
2679 fname
= ''.join(i
for i
in fname
if not i
.isdigit())
2680 # Seperate CamelCase by space
2681 fname
= re
.sub("([a-z])([A-Z])","\g<1> \g<2>",fname
)
2682 # Replace common separators with SPACE
2683 seperators
= ['_', '.', '-', '__', '--', '#']
2684 for sep
in seperators
:
2685 fname
= fname
.replace(sep
, ' ')
2687 components
= fname
.split(' ')
2688 components
= [c
.lower() for c
in components
]
2691 # Filter textures names for texturetypes in filenames
2692 # [Socket Name, [abbreviations and keyword list], Filename placeholder]
2693 tags
= context
.user_preferences
.addons
[__name__
].preferences
.principled_tags
2694 normal_abbr
= tags
.normal
.split(' ')
2695 bump_abbr
= tags
.bump
.split(' ')
2696 gloss_abbr
= tags
.gloss
.split(' ')
2697 rough_abbr
= tags
.rough
.split(' ')
2699 ['Displacement', tags
.displacement
.split(' '), None],
2700 ['Base Color', tags
.base_color
.split(' '), None],
2701 ['Subsurface Color', tags
.sss_color
.split(' '), None],
2702 ['Metallic', tags
.metallic
.split(' '), None],
2703 ['Specular', tags
.specular
.split(' '), None],
2704 ['Roughness', rough_abbr
+ gloss_abbr
, None],
2705 ['Normal', normal_abbr
+ bump_abbr
, None],
2708 # Look through texture_types and set value as filename of first matched file
2709 def match_files_to_socket_names():
2710 for sname
in socketnames
:
2711 for file in self
.files
:
2713 filenamecomponents
= split_into__components(fname
)
2714 matches
= set(sname
[1]).intersection(set(filenamecomponents
))
2715 # TODO: ignore basename (if texture is named "fancy_metal_nor", it will be detected as metallic map, not normal map)
2720 match_files_to_socket_names()
2721 # Remove socketnames without found files
2722 socketnames
= [s
for s
in socketnames
if s
[2]
2723 and path
.exists(self
.directory
+s
[2])]
2725 self
.report({'INFO'}, 'No matching images found')
2726 print('No matching images found')
2727 return {'CANCELLED'}
2730 print('\nMatched Textures:')
2734 roughness_node
= None
2735 for i
, sname
in enumerate(socketnames
):
2736 print(i
, sname
[0], sname
[2])
2738 # DISPLACEMENT NODES
2739 if sname
[0] == 'Displacement':
2740 disp_texture
= nodes
.new(type='ShaderNodeTexImage')
2741 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2742 disp_texture
.image
= img
2743 disp_texture
.label
= 'Displacement'
2744 disp_texture
.color_space
= 'NONE'
2746 # Add displacement offset nodes
2747 math_sub
= nodes
.new(type='ShaderNodeMath')
2748 math_sub
.operation
= 'SUBTRACT'
2749 math_sub
.label
= 'Offset'
2750 math_sub
.location
= active_node
.location
+ Vector((0, -560))
2751 math_mul
= nodes
.new(type='ShaderNodeMath')
2752 math_mul
.operation
= 'MULTIPLY'
2753 math_mul
.label
= 'Strength'
2754 math_mul
.location
= math_sub
.location
+ Vector((200, 0))
2755 link
= links
.new(math_mul
.inputs
[0], math_sub
.outputs
[0])
2756 link
= links
.new(math_sub
.inputs
[0], disp_texture
.outputs
[0])
2758 # Turn on true displacement in the material
2759 # Too complicated for now
2762 # Frame. Does not update immediatly
2763 # Seems to need an editor redraw
2764 frame = nodes.new(type='NodeFrame')
2765 frame.label = 'Displacement'
2766 math_sub.parent = frame
2767 math_mul.parent = frame
2772 output_node
= [n
for n
in nodes
if n
.bl_idname
== 'ShaderNodeOutputMaterial']
2774 if not output_node
[0].inputs
[2].is_linked
:
2775 link
= links
.new(output_node
[0].inputs
[2], math_mul
.outputs
[0])
2779 if not active_node
.inputs
[sname
[0]].is_linked
:
2780 # No texture node connected -> add texture node with new image
2781 texture_node
= nodes
.new(type='ShaderNodeTexImage')
2782 img
= bpy
.data
.images
.load(self
.directory
+sname
[2])
2783 texture_node
.image
= img
2786 if sname
[0] == 'Normal':
2787 # Test if new texture node is normal or bump map
2788 fname_components
= split_into__components(sname
[2])
2789 match_normal
= set(normal_abbr
).intersection(set(fname_components
))
2790 match_bump
= set(bump_abbr
).intersection(set(fname_components
))
2792 # If Normal add normal node in between
2793 normal_node
= nodes
.new(type='ShaderNodeNormalMap')
2794 link
= links
.new(normal_node
.inputs
[1], texture_node
.outputs
[0])
2796 # If Bump add bump node in between
2797 normal_node
= nodes
.new(type='ShaderNodeBump')
2798 link
= links
.new(normal_node
.inputs
[2], texture_node
.outputs
[0])
2800 link
= links
.new(active_node
.inputs
[sname
[0]], normal_node
.outputs
[0])
2801 normal_node_texture
= texture_node
2803 elif sname
[0] == 'Roughness':
2804 # Test if glossy or roughness map
2805 fname_components
= split_into__components(sname
[2])
2806 match_rough
= set(rough_abbr
).intersection(set(fname_components
))
2807 match_gloss
= set(gloss_abbr
).intersection(set(fname_components
))
2810 # If Roughness nothing to to
2811 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2814 # If Gloss Map add invert node
2815 invert_node
= nodes
.new(type='ShaderNodeInvert')
2816 link
= links
.new(invert_node
.inputs
[1], texture_node
.outputs
[0])
2818 link
= links
.new(active_node
.inputs
[sname
[0]], invert_node
.outputs
[0])
2819 roughness_node
= texture_node
2822 # This is a simple connection Texture --> Input slot
2823 link
= links
.new(active_node
.inputs
[sname
[0]], texture_node
.outputs
[0])
2825 # Use non-color for all but 'Base Color' Textures
2826 if not sname
[0] in ['Base Color']:
2827 texture_node
.color_space
= 'NONE'
2830 # If already texture connected. add to node list for alignment
2831 texture_node
= active_node
.inputs
[sname
[0]].links
[0].from_node
2833 # This are all connected texture nodes
2834 texture_nodes
.append(texture_node
)
2835 texture_node
.label
= sname
[0]
2838 texture_nodes
.append(disp_texture
)
2841 for i
, texture_node
in enumerate(texture_nodes
):
2842 offset
= Vector((-400, (i
* -260) + 200))
2843 texture_node
.location
= active_node
.location
+ offset
2846 # Extra alignment if normal node was added
2847 normal_node
.location
= normal_node_texture
.location
+ Vector((200, 0))
2850 # Alignment of invert node if glossy map
2851 invert_node
.location
= roughness_node
.location
+ Vector((200, 0))
2853 # Add texture input + mapping
2854 mapping
= nodes
.new(type='ShaderNodeMapping')
2855 mapping
.location
= active_node
.location
+ Vector((-900, 0))
2856 if len(texture_nodes
) > 1:
2857 # If more than one texture add reroute node in between
2858 reroute
= nodes
.new(type='NodeReroute')
2859 tex_coords
= Vector((texture_nodes
[0].location
.x
, sum(n
.location
.y
for n
in texture_nodes
)/len(texture_nodes
)))
2860 reroute
.location
= tex_coords
+ Vector((-50, -120))
2861 for texture_node
in texture_nodes
:
2862 link
= links
.new(texture_node
.inputs
[0], reroute
.outputs
[0])
2863 link
= links
.new(reroute
.inputs
[0], mapping
.outputs
[0])
2865 link
= links
.new(texture_nodes
[0].inputs
[0], mapping
.outputs
[0])
2867 # Connect texture_coordiantes to mapping node
2868 texture_input
= nodes
.new(type='ShaderNodeTexCoord')
2869 texture_input
.location
= mapping
.location
+ Vector((-200, 0))
2870 link
= links
.new(mapping
.inputs
[0], texture_input
.outputs
[2])
2873 active_node
.select
= False
2876 force_update(context
)
2880 class NWAddReroutes(Operator
, NWBase
):
2881 """Add Reroute Nodes and link them to outputs of selected nodes"""
2882 bl_idname
= "node.nw_add_reroutes"
2883 bl_label
= "Add Reroutes"
2884 bl_description
= "Add Reroutes to Outputs"
2885 bl_options
= {'REGISTER', 'UNDO'}
2887 option
= EnumProperty(
2890 ('ALL', 'to all', 'Add to all outputs'),
2891 ('LOOSE', 'to loose', 'Add only to loose outputs'),
2892 ('LINKED', 'to linked', 'Add only to linked outputs'),
2896 def execute(self
, context
):
2897 tree_type
= context
.space_data
.node_tree
.type
2898 option
= self
.option
2899 nodes
, links
= get_nodes_links(context
)
2900 # output valid when option is 'all' or when 'loose' output has no links
2902 post_select
= [] # nodes to be selected after execution
2903 # create reroutes and recreate links
2904 for node
in [n
for n
in nodes
if n
.select
]:
2909 # unhide 'REROUTE' nodes to avoid issues with location.y
2910 if node
.type == 'REROUTE':
2912 # When node is hidden - width_hidden not usable.
2913 # Hack needed to calculate real width
2915 bpy
.ops
.node
.select_all(action
='DESELECT')
2916 helper
= nodes
.new('NodeReroute')
2917 helper
.select
= True
2919 # resize node and helper to zero. Then check locations to calculate width
2920 bpy
.ops
.transform
.resize(value
=(0.0, 0.0, 0.0))
2921 width
= 2.0 * (helper
.location
.x
- node
.location
.x
)
2922 # restore node location
2923 node
.location
= x
, y
2926 # only helper is selected now
2927 bpy
.ops
.node
.delete()
2928 x
= node
.location
.x
+ width
+ 20.0
2929 if node
.type != 'REROUTE':
2933 reroutes_count
= 0 # will be used when aligning reroutes added to hidden nodes
2934 for out_i
, output
in enumerate(node
.outputs
):
2935 pass_used
= False # initial value to be analyzed if 'R_LAYERS'
2936 # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
2937 if node
.type != 'R_LAYERS':
2939 else: # if 'R_LAYERS' check if output represent used render pass
2940 node_scene
= node
.scene
2941 node_layer
= node
.layer
2942 # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
2943 if output
.name
== 'Alpha':
2946 # check entries in global 'rl_outputs' variable
2947 #for render_pass, output_name, exr_name, in_internal, in_cycles in rl_outputs:
2948 for rlo
in rl_outputs
:
2949 if output
.name
== rlo
.output_name
or output
.name
== rlo
.exr_output_name
:
2950 pass_used
= getattr(node_scene
.render
.layers
[node_layer
], rlo
.render_pass
)
2953 valid
= ((option
== 'ALL') or
2954 (option
== 'LOOSE' and not output
.links
) or
2955 (option
== 'LINKED' and output
.links
))
2956 # Add reroutes only if valid, but offset location in all cases.
2958 n
= nodes
.new('NodeReroute')
2960 for link
in output
.links
:
2961 links
.new(n
.outputs
[0], link
.to_socket
)
2962 links
.new(output
, n
.inputs
[0])
2964 post_select
.append(n
)
2968 # disselect the node so that after execution of script only newly created nodes are selected
2970 # nicer reroutes distribution along y when node.hide
2972 y_translate
= reroutes_count
* y_offset
/ 2.0 - y_offset
- 35.0
2973 for reroute
in [r
for r
in nodes
if r
.select
]:
2974 reroute
.location
.y
-= y_translate
2975 for node
in post_select
:
2981 class NWLinkActiveToSelected(Operator
, NWBase
):
2982 """Link active node to selected nodes basing on various criteria"""
2983 bl_idname
= "node.nw_link_active_to_selected"
2984 bl_label
= "Link Active Node to Selected"
2985 bl_options
= {'REGISTER', 'UNDO'}
2987 replace
= BoolProperty()
2988 use_node_name
= BoolProperty()
2989 use_outputs_names
= BoolProperty()
2992 def poll(cls
, context
):
2994 if nw_check(context
):
2995 if context
.active_node
is not None:
2996 if context
.active_node
.select
:
3000 def execute(self
, context
):
3001 nodes
, links
= get_nodes_links(context
)
3002 replace
= self
.replace
3003 use_node_name
= self
.use_node_name
3004 use_outputs_names
= self
.use_outputs_names
3005 active
= nodes
.active
3006 selected
= [node
for node
in nodes
if node
.select
and node
!= active
]
3007 outputs
= [] # Only usable outputs of active nodes will be stored here.
3008 for out
in active
.outputs
:
3009 if active
.type != 'R_LAYERS':
3012 # 'R_LAYERS' node type needs special handling.
3013 # outputs of 'R_LAYERS' are callable even if not seen in UI.
3014 # Only outputs that represent used passes should be taken into account
3015 # Check if pass represented by output is used.
3016 # global 'rl_outputs' list will be used for that
3017 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3018 pass_used
= False # initial value. Will be set to True if pass is used
3019 if out
.name
== 'Alpha':
3020 # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
3022 elif out
.name
== out_name
:
3023 # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
3024 pass_used
= getattr(active
.scene
.render
.layers
[active
.layer
], render_pass
)
3028 doit
= True # Will be changed to False when links successfully added to previous output.
3031 for node
in selected
:
3032 dst_name
= node
.name
# Will be compared with src_name if needed.
3033 # When node has label - use it as dst_name
3035 dst_name
= node
.label
3036 valid
= True # Initial value. Will be changed to False if names don't match.
3037 src_name
= dst_name
# If names not used - this asignment will keep valid = True.
3039 # Set src_name to source node name or label
3040 src_name
= active
.name
3042 src_name
= active
.label
3043 elif use_outputs_names
:
3044 src_name
= (out
.name
, )
3045 for render_pass
, out_name
, exr_name
, in_internal
, in_cycles
in rl_outputs
:
3046 if out
.name
in {out_name
, exr_name
}:
3047 src_name
= (out_name
, exr_name
)
3048 if dst_name
not in src_name
:
3051 for input in node
.inputs
:
3052 if input.type == out
.type or node
.type == 'REROUTE':
3053 if replace
or not input.is_linked
:
3054 links
.new(out
, input)
3055 if not use_node_name
and not use_outputs_names
:
3062 class NWAlignNodes(Operator
, NWBase
):
3063 '''Align the selected nodes neatly in a row/column'''
3064 bl_idname
= "node.nw_align_nodes"
3065 bl_label
= "Align Nodes"
3066 bl_options
= {'REGISTER', 'UNDO'}
3067 margin
= IntProperty(name
='Margin', default
=50, description
='The amount of space between nodes')
3069 def execute(self
, context
):
3070 nodes
, links
= get_nodes_links(context
)
3071 margin
= self
.margin
3075 if node
.select
and node
.type != 'FRAME':
3076 selection
.append(node
)
3078 # If no nodes are selected, align all nodes
3082 elif nodes
.active
in selection
:
3083 active_loc
= copy(nodes
.active
.location
) # make a copy, not a reference
3085 # Check if nodes should be layed out horizontally or vertically
3086 x_locs
= [n
.location
.x
+ (n
.dimensions
.x
/ 2) for n
in selection
] # use dimension to get center of node, not corner
3087 y_locs
= [n
.location
.y
- (n
.dimensions
.y
/ 2) for n
in selection
]
3088 x_range
= max(x_locs
) - min(x_locs
)
3089 y_range
= max(y_locs
) - min(y_locs
)
3090 mid_x
= (max(x_locs
) + min(x_locs
)) / 2
3091 mid_y
= (max(y_locs
) + min(y_locs
)) / 2
3092 horizontal
= x_range
> y_range
3094 # Sort selection by location of node mid-point
3096 selection
= sorted(selection
, key
=lambda n
: n
.location
.x
+ (n
.dimensions
.x
/ 2))
3098 selection
= sorted(selection
, key
=lambda n
: n
.location
.y
- (n
.dimensions
.y
/ 2), reverse
=True)
3102 for node
in selection
:
3103 current_margin
= margin
3104 current_margin
= current_margin
* 0.5 if node
.hide
else current_margin
# use a smaller margin for hidden nodes
3107 node
.location
.x
= current_pos
3108 current_pos
+= current_margin
+ node
.dimensions
.x
3109 node
.location
.y
= mid_y
+ (node
.dimensions
.y
/ 2)
3111 node
.location
.y
= current_pos
3112 current_pos
-= (current_margin
* 0.3) + node
.dimensions
.y
# use half-margin for vertical alignment
3113 node
.location
.x
= mid_x
- (node
.dimensions
.x
/ 2)
3115 # If active node is selected, center nodes around it
3116 if active_loc
is not None:
3117 active_loc_diff
= active_loc
- nodes
.active
.location
3118 for node
in selection
:
3119 node
.location
+= active_loc_diff
3120 else: # Position nodes centered around where they used to be
3121 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
])
3122 new_mid
= (max(locs
) + min(locs
)) / 2
3123 for node
in selection
:
3125 node
.location
.x
+= (mid_x
- new_mid
)
3127 node
.location
.y
+= (mid_y
- new_mid
)
3132 class NWSelectParentChildren(Operator
, NWBase
):
3133 bl_idname
= "node.nw_select_parent_child"
3134 bl_label
= "Select Parent or Children"
3135 bl_options
= {'REGISTER', 'UNDO'}
3137 option
= EnumProperty(
3140 ('PARENT', 'Select Parent', 'Select Parent Frame'),
3141 ('CHILD', 'Select Children', 'Select members of selected frame'),
3145 def execute(self
, context
):
3146 nodes
, links
= get_nodes_links(context
)
3147 option
= self
.option
3148 selected
= [node
for node
in nodes
if node
.select
]
3149 if option
== 'PARENT':
3150 for sel
in selected
:
3153 parent
.select
= True
3154 else: # option == 'CHILD'
3155 for sel
in selected
:
3156 children
= [node
for node
in nodes
if node
.parent
== sel
]
3157 for kid
in children
:
3163 class NWDetachOutputs(Operator
, NWBase
):
3164 """Detach outputs of selected node leaving inluts liked"""
3165 bl_idname
= "node.nw_detach_outputs"
3166 bl_label
= "Detach Outputs"
3167 bl_options
= {'REGISTER', 'UNDO'}
3169 def execute(self
, context
):
3170 nodes
, links
= get_nodes_links(context
)
3171 selected
= context
.selected_nodes
3172 bpy
.ops
.node
.duplicate_move_keep_inputs()
3173 new_nodes
= context
.selected_nodes
3174 bpy
.ops
.node
.select_all(action
="DESELECT")
3175 for node
in selected
:
3177 bpy
.ops
.node
.delete_reconnect()
3178 for new_node
in new_nodes
:
3179 new_node
.select
= True
3180 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
3185 class NWLinkToOutputNode(Operator
, NWBase
):
3186 """Link to Composite node or Material Output node"""
3187 bl_idname
= "node.nw_link_out"
3188 bl_label
= "Connect to Output"
3189 bl_options
= {'REGISTER', 'UNDO'}
3192 def poll(cls
, context
):
3194 if nw_check(context
):
3195 if context
.active_node
is not None:
3196 for out
in context
.active_node
.outputs
:
3202 def execute(self
, context
):
3203 nodes
, links
= get_nodes_links(context
)
3204 active
= nodes
.active
3207 tree_type
= context
.space_data
.tree_type
3208 output_types_shaders
= [x
[1] for x
in shaders_output_nodes_props
]
3209 output_types_compo
= ['COMPOSITE']
3210 output_types_blender_mat
= ['OUTPUT']
3211 output_types_textures
= ['OUTPUT']
3212 output_types
= output_types_shaders
+ output_types_compo
+ output_types_blender_mat
3214 if node
.type in output_types
:
3218 bpy
.ops
.node
.select_all(action
="DESELECT")
3219 if tree_type
== 'ShaderNodeTree':
3220 if context
.scene
.render
.engine
== 'CYCLES':
3221 output_node
= nodes
.new('ShaderNodeOutputMaterial')
3223 output_node
= nodes
.new('ShaderNodeOutput')
3224 elif tree_type
== 'CompositorNodeTree':
3225 output_node
= nodes
.new('CompositorNodeComposite')
3226 elif tree_type
== 'TextureNodeTree':
3227 output_node
= nodes
.new('TextureNodeOutput')
3228 output_node
.location
.x
= active
.location
.x
+ active
.dimensions
.x
+ 80
3229 output_node
.location
.y
= active
.location
.y
3230 if (output_node
and active
.outputs
):
3231 for i
, output
in enumerate(active
.outputs
):
3235 for i
, output
in enumerate(active
.outputs
):
3236 if output
.type == output_node
.inputs
[0].type and not output
.hide
:
3241 if tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
3242 if active
.outputs
[output_index
].name
== 'Volume':
3244 elif active
.outputs
[output_index
].type != 'SHADER': # connect to displacement if not a shader
3246 links
.new(active
.outputs
[output_index
], output_node
.inputs
[out_input_index
])
3248 force_update(context
) # viewport render does not update
3253 class NWMakeLink(Operator
, NWBase
):
3254 """Make a link from one socket to another"""
3255 bl_idname
= 'node.nw_make_link'
3256 bl_label
= 'Make Link'
3257 bl_options
= {'REGISTER', 'UNDO'}
3258 from_socket
= IntProperty()
3259 to_socket
= IntProperty()
3261 def execute(self
, context
):
3262 nodes
, links
= get_nodes_links(context
)
3264 n1
= nodes
[context
.scene
.NWLazySource
]
3265 n2
= nodes
[context
.scene
.NWLazyTarget
]
3267 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[self
.to_socket
])
3269 force_update(context
)
3274 class NWCallInputsMenu(Operator
, NWBase
):
3275 """Link from this output"""
3276 bl_idname
= 'node.nw_call_inputs_menu'
3277 bl_label
= 'Make Link'
3278 bl_options
= {'REGISTER', 'UNDO'}
3279 from_socket
= IntProperty()
3281 def execute(self
, context
):
3282 nodes
, links
= get_nodes_links(context
)
3284 context
.scene
.NWSourceSocket
= self
.from_socket
3286 n1
= nodes
[context
.scene
.NWLazySource
]
3287 n2
= nodes
[context
.scene
.NWLazyTarget
]
3288 if len(n2
.inputs
) > 1:
3289 bpy
.ops
.wm
.call_menu("INVOKE_DEFAULT", name
=NWConnectionListInputs
.bl_idname
)
3290 elif len(n2
.inputs
) == 1:
3291 links
.new(n1
.outputs
[self
.from_socket
], n2
.inputs
[0])
3295 class NWAddSequence(Operator
, ImportHelper
):
3296 """Add an Image Sequence"""
3297 bl_idname
= 'node.nw_add_sequence'
3298 bl_label
= 'Import Image Sequence'
3299 bl_options
= {'REGISTER', 'UNDO'}
3300 directory
= StringProperty(subtype
="DIR_PATH")
3301 filename
= StringProperty(subtype
="FILE_NAME")
3302 files
= CollectionProperty(type=bpy
.types
.OperatorFileListElement
, options
={'HIDDEN', 'SKIP_SAVE'})
3304 def execute(self
, context
):
3305 nodes
, links
= get_nodes_links(context
)
3306 directory
= self
.directory
3307 filename
= self
.filename
3309 tree
= context
.space_data
.node_tree
3312 # print ("\nDIR:", directory)
3313 # print ("FN:", filename)
3314 # print ("Fs:", list(f.name for f in files), '\n')
3316 if tree
.type == 'SHADER':
3317 node_type
= "ShaderNodeTexImage"
3318 elif tree
.type == 'COMPOSITING':
3319 node_type
= "CompositorNodeImage"
3321 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3322 return {'CANCELLED'}
3324 if not files
[0].name
and not filename
:
3325 self
.report({'ERROR'}, "No file chosen")
3326 return {'CANCELLED'}
3327 elif files
[0].name
and (not filename
or not path
.exists(directory
+filename
)):
3328 # User has selected multiple files without an active one, or the active one is non-existant
3329 filename
= files
[0].name
3331 if not path
.exists(directory
+filename
):
3332 self
.report({'ERROR'}, filename
+" does not exist!")
3333 return {'CANCELLED'}
3335 without_ext
= '.'.join(filename
.split('.')[:-1])
3337 # if last digit isn't a number, it's not a sequence
3338 if not without_ext
[-1].isdigit():
3339 self
.report({'ERROR'}, filename
+" does not seem to be part of a sequence")
3340 return {'CANCELLED'}
3343 extension
= filename
.split('.')[-1]
3344 reverse
= without_ext
[::-1] # reverse string
3347 for char
in reverse
:
3353 without_num
= without_ext
[:count_numbers
*-1]
3355 files
= sorted(glob(directory
+ without_num
+ "[0-9]"*count_numbers
+ "." + extension
))
3357 num_frames
= len(files
)
3359 nodes_list
= [node
for node
in nodes
]
3361 nodes_list
.sort(key
=lambda k
: k
.location
.x
)
3362 xloc
= nodes_list
[0].location
.x
- 220 # place new nodes at far left
3366 yloc
+= node_mid_pt(node
, 'y')
3367 yloc
= yloc
/len(nodes
)
3372 name_with_hashes
= without_num
+ "#"*count_numbers
+ '.' + extension
3374 bpy
.ops
.node
.add_node('INVOKE_DEFAULT', use_transform
=True, type=node_type
)
3376 node
.label
= name_with_hashes
3378 img
= bpy
.data
.images
.load(directory
+(without_ext
+'.'+extension
))
3379 img
.source
= 'SEQUENCE'
3380 img
.name
= name_with_hashes
3382 image_user
= node
.image_user
if tree
.type == 'SHADER' else node
3383 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
3384 image_user
.frame_duration
= num_frames
3389 class NWAddMultipleImages(Operator
, ImportHelper
):
3390 """Add multiple images at once"""
3391 bl_idname
= 'node.nw_add_multiple_images'
3392 bl_label
= 'Open Selected Images'
3393 bl_options
= {'REGISTER', 'UNDO'}
3394 directory
= StringProperty(subtype
="DIR_PATH")
3395 files
= CollectionProperty(type=bpy
.types
.OperatorFileListElement
, options
={'HIDDEN', 'SKIP_SAVE'})
3397 def execute(self
, context
):
3398 nodes
, links
= get_nodes_links(context
)
3400 xloc
, yloc
= context
.region
.view2d
.region_to_view(context
.area
.width
/2, context
.area
.height
/2)
3402 if context
.space_data
.node_tree
.type == 'SHADER':
3403 node_type
= "ShaderNodeTexImage"
3404 elif context
.space_data
.node_tree
.type == 'COMPOSITING':
3405 node_type
= "CompositorNodeImage"
3407 self
.report({'ERROR'}, "Unsupported Node Tree type!")
3408 return {'CANCELLED'}
3411 for f
in self
.files
:
3414 node
= nodes
.new(node_type
)
3415 new_nodes
.append(node
)
3418 node
.width_hidden
= 100
3419 node
.location
.x
= xloc
3420 node
.location
.y
= yloc
3423 img
= bpy
.data
.images
.load(self
.directory
+fname
)
3426 # shift new nodes up to center of tree
3427 list_size
= new_nodes
[0].location
.y
- new_nodes
[-1].location
.y
3429 if node
in new_nodes
:
3431 node
.location
.y
+= (list_size
/2)
3437 class NWViewerFocus(bpy
.types
.Operator
):
3438 """Set the viewer tile center to the mouse position"""
3439 bl_idname
= "node.nw_viewer_focus"
3440 bl_label
= "Viewer Focus"
3442 x
= bpy
.props
.IntProperty()
3443 y
= bpy
.props
.IntProperty()
3446 def poll(cls
, context
):
3447 return nw_check(context
) and context
.space_data
.tree_type
== 'CompositorNodeTree'
3449 def execute(self
, context
):
3452 def invoke(self
, context
, event
):
3453 render
= context
.scene
.render
3454 space
= context
.space_data
3455 percent
= render
.resolution_percentage
*0.01
3457 nodes
, links
= get_nodes_links(context
)
3458 viewers
= [n
for n
in nodes
if n
.type == 'VIEWER']
3461 mlocx
= event
.mouse_region_x
3462 mlocy
= event
.mouse_region_y
3463 select_node
= bpy
.ops
.node
.select(mouse_x
=mlocx
, mouse_y
=mlocy
, extend
=False)
3465 if not 'FINISHED' in select_node
: # only run if we're not clicking on a node
3466 region_x
= context
.region
.width
3467 region_y
= context
.region
.height
3469 region_center_x
= context
.region
.width
/ 2
3470 region_center_y
= context
.region
.height
/ 2
3472 bd_x
= render
.resolution_x
* percent
* space
.backdrop_zoom
3473 bd_y
= render
.resolution_y
* percent
* space
.backdrop_zoom
3475 backdrop_center_x
= (bd_x
/ 2) - space
.backdrop_x
3476 backdrop_center_y
= (bd_y
/ 2) - space
.backdrop_y
3478 margin_x
= region_center_x
- backdrop_center_x
3479 margin_y
= region_center_y
- backdrop_center_y
3481 abs_mouse_x
= (mlocx
- margin_x
) / bd_x
3482 abs_mouse_y
= (mlocy
- margin_y
) / bd_y
3484 for node
in viewers
:
3485 node
.center_x
= abs_mouse_x
3486 node
.center_y
= abs_mouse_y
3488 return {'PASS_THROUGH'}
3490 return self
.execute(context
)
3493 class NWSaveViewer(bpy
.types
.Operator
, ExportHelper
):
3494 """Save the current viewer node to an image file"""
3495 bl_idname
= "node.nw_save_viewer"
3496 bl_label
= "Save This Image"
3497 filepath
= StringProperty(subtype
="FILE_PATH")
3498 filename_ext
= EnumProperty(
3500 description
="Choose the file format to save to",
3501 items
=(('.bmp', "PNG", ""),
3502 ('.rgb', 'IRIS', ""),
3503 ('.png', 'PNG', ""),
3504 ('.jpg', 'JPEG', ""),
3505 ('.jp2', 'JPEG2000', ""),
3506 ('.tga', 'TARGA', ""),
3507 ('.cin', 'CINEON', ""),
3508 ('.dpx', 'DPX', ""),
3509 ('.exr', 'OPEN_EXR', ""),
3510 ('.hdr', 'HDR', ""),
3511 ('.tif', 'TIFF', "")),
3516 def poll(cls
, context
):
3518 if nw_check(context
):
3519 if context
.space_data
.tree_type
== 'CompositorNodeTree':
3520 if "Viewer Node" in [i
.name
for i
in bpy
.data
.images
]:
3521 if sum(bpy
.data
.images
["Viewer Node"].size
) > 0: # False if not connected or connected but no image
3525 def execute(self
, context
):
3542 basename
, ext
= path
.splitext(fp
)
3543 old_render_format
= context
.scene
.render
.image_settings
.file_format
3544 context
.scene
.render
.image_settings
.file_format
= formats
[self
.filename_ext
]
3545 context
.area
.type = "IMAGE_EDITOR"
3546 context
.area
.spaces
[0].image
= bpy
.data
.images
['Viewer Node']
3547 context
.area
.spaces
[0].image
.save_render(fp
)
3548 context
.area
.type = "NODE_EDITOR"
3549 context
.scene
.render
.image_settings
.file_format
= old_render_format
3553 class NWResetNodes(bpy
.types
.Operator
):
3554 """Reset Nodes in Selection"""
3555 bl_idname
= "node.nw_reset_nodes"
3556 bl_label
= "Reset Nodes"
3557 bl_options
= {'REGISTER', 'UNDO'}
3560 def poll(cls
, context
):
3561 space
= context
.space_data
3562 return space
.type == 'NODE_EDITOR'
3564 def execute(self
, context
):
3565 node_active
= context
.active_node
3566 node_selected
= context
.selected_nodes
3567 node_ignore
= ["FRAME","REROUTE", "GROUP"]
3569 # Check if one node is selected at least
3570 if not (len(node_selected
) > 0):
3571 self
.report({'ERROR'}, "1 node must be selected at least")
3572 return {'CANCELLED'}
3574 active_node_name
= node_active
.name
if node_active
.select
else None
3575 valid_nodes
= [n
for n
in node_selected
if n
.type not in node_ignore
]
3577 # Create output lists
3578 selected_node_names
= [n
.name
for n
in node_selected
]
3581 # Reset all valid children in a frame
3582 node_active_is_frame
= False
3583 if len(node_selected
) == 1 and node_active
.type == "FRAME":
3584 node_tree
= node_active
.id_data
3585 children
= [n
for n
in node_tree
.nodes
if n
.parent
== node_active
]
3587 valid_nodes
= [n
for n
in children
if n
.type not in node_ignore
]
3588 selected_node_names
= [n
.name
for n
in children
if n
.type not in node_ignore
]
3589 node_active_is_frame
= True
3591 # Check if valid nodes in selection
3592 if not (len(valid_nodes
) > 0):
3593 # Check for frames only
3594 frames_selected
= [n
for n
in node_selected
if n
.type == "FRAME"]
3595 if (len(frames_selected
) > 1 and len(frames_selected
) == len(node_selected
)):
3596 self
.report({'ERROR'}, "Please select only 1 frame to reset")
3598 self
.report({'ERROR'}, "No valid node(s) in selection")
3599 return {'CANCELLED'}
3601 # Report nodes that are not valid
3602 if len(valid_nodes
) != len(node_selected
) and node_active_is_frame
is False:
3603 valid_node_names
= [n
.name
for n
in valid_nodes
]
3604 not_valid_names
= list(set(selected_node_names
) - set(valid_node_names
))
3605 self
.report({'INFO'}, "Ignored {}".format(", ".join(not_valid_names
)))
3607 # Deselect all nodes
3608 for i
in node_selected
:
3611 # Run through all valid nodes
3612 for node
in valid_nodes
:
3614 parent
= node
.parent
if node
.parent
else None
3615 node_loc
= [node
.location
.x
, node
.location
.y
]
3617 node_tree
= node
.id_data
3618 props_to_copy
= 'bl_idname name location height width'.split(' ')
3621 mappings
= chain
.from_iterable([node
.inputs
, node
.outputs
])
3622 for i
in (i
for i
in mappings
if i
.is_linked
):
3624 reconnections
.append([L
.from_socket
.path_from_id(), L
.to_socket
.path_from_id()])
3626 props
= {j
: getattr(node
, j
) for j
in props_to_copy
}
3628 new_node
= node_tree
.nodes
.new(props
['bl_idname'])
3629 props_to_copy
.pop(0)
3631 for prop
in props_to_copy
:
3632 setattr(new_node
, prop
, props
[prop
])
3634 nodes
= node_tree
.nodes
3636 new_node
.name
= props
['name']
3639 new_node
.parent
= parent
3640 new_node
.location
= node_loc
3642 for str_from
, str_to
in reconnections
:
3643 node_tree
.links
.new(eval(str_from
), eval(str_to
))
3645 new_node
.select
= False
3646 success_names
.append(new_node
.name
)
3648 # Reselect all nodes
3649 if selected_node_names
and node_active_is_frame
is False:
3650 for i
in selected_node_names
:
3651 node_tree
.nodes
[i
].select
= True
3653 if active_node_name
is not None:
3654 node_tree
.nodes
[active_node_name
].select
= True
3655 node_tree
.nodes
.active
= node_tree
.nodes
[active_node_name
]
3657 self
.report({'INFO'}, "Successfully reset {}".format(", ".join(success_names
)))
3665 def drawlayout(context
, layout
, mode
='non-panel'):
3666 tree_type
= context
.space_data
.tree_type
3668 col
= layout
.column(align
=True)
3669 col
.menu(NWMergeNodesMenu
.bl_idname
)
3672 col
= layout
.column(align
=True)
3673 col
.menu(NWSwitchNodeTypeMenu
.bl_idname
, text
="Switch Node Type")
3676 if tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
3677 col
= layout
.column(align
=True)
3678 col
.operator(NWAddTextureSetup
.bl_idname
, text
="Add Texture Setup", icon
='NODE_SEL')
3679 col
.operator(NWAddPrincipledSetup
.bl_idname
, text
="Add Principled Setup", icon
='NODE_SEL')
3682 col
= layout
.column(align
=True)
3683 col
.operator(NWDetachOutputs
.bl_idname
, icon
='UNLINKED')
3684 col
.operator(NWSwapLinks
.bl_idname
)
3685 col
.menu(NWAddReroutesMenu
.bl_idname
, text
="Add Reroutes", icon
='LAYER_USED')
3688 col
= layout
.column(align
=True)
3689 col
.menu(NWLinkActiveToSelectedMenu
.bl_idname
, text
="Link Active To Selected", icon
='LINKED')
3690 col
.operator(NWLinkToOutputNode
.bl_idname
, icon
='DRIVER')
3693 col
= layout
.column(align
=True)
3695 row
= col
.row(align
=True)
3696 row
.operator(NWClearLabel
.bl_idname
).option
= True
3697 row
.operator(NWModifyLabels
.bl_idname
)
3699 col
.operator(NWClearLabel
.bl_idname
).option
= True
3700 col
.operator(NWModifyLabels
.bl_idname
)
3701 col
.menu(NWBatchChangeNodesMenu
.bl_idname
, text
="Batch Change")
3703 col
.menu(NWCopyToSelectedMenu
.bl_idname
, text
="Copy to Selected")
3706 col
= layout
.column(align
=True)
3707 if tree_type
== 'CompositorNodeTree':
3708 col
.operator(NWResetBG
.bl_idname
, icon
='ZOOM_PREVIOUS')
3709 col
.operator(NWReloadImages
.bl_idname
, icon
='FILE_REFRESH')
3712 col
= layout
.column(align
=True)
3713 col
.operator(NWFrameSelected
.bl_idname
, icon
='STICKY_UVS_LOC')
3716 col
= layout
.column(align
=True)
3717 col
.operator(NWAlignNodes
.bl_idname
, icon
='ALIGN')
3720 col
= layout
.column(align
=True)
3721 col
.operator(NWDeleteUnused
.bl_idname
, icon
='CANCEL')
3725 class NodeWranglerPanel(Panel
, NWBase
):
3726 bl_idname
= "NODE_PT_nw_node_wrangler"
3727 bl_space_type
= 'NODE_EDITOR'
3728 bl_label
= "Node Wrangler"
3729 bl_region_type
= "TOOLS"
3730 bl_category
= "Node Wrangler"
3732 prepend
= StringProperty(
3735 append
= StringProperty()
3736 remove
= StringProperty()
3738 def draw(self
, context
):
3739 self
.layout
.label(text
="(Quick access: Ctrl+Space)")
3740 drawlayout(context
, self
.layout
, mode
='panel')
3746 class NodeWranglerMenu(Menu
, NWBase
):
3747 bl_idname
= "NODE_MT_nw_node_wrangler_menu"
3748 bl_label
= "Node Wrangler"
3750 def draw(self
, context
):
3751 drawlayout(context
, self
.layout
)
3754 class NWMergeNodesMenu(Menu
, NWBase
):
3755 bl_idname
= "NODE_MT_nw_merge_nodes_menu"
3756 bl_label
= "Merge Selected Nodes"
3758 def draw(self
, context
):
3759 type = context
.space_data
.tree_type
3760 layout
= self
.layout
3761 if type == 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES':
3762 layout
.menu(NWMergeShadersMenu
.bl_idname
, text
="Use Shaders")
3763 layout
.menu(NWMergeMixMenu
.bl_idname
, text
="Use Mix Nodes")
3764 layout
.menu(NWMergeMathMenu
.bl_idname
, text
="Use Math Nodes")
3765 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Z-Combine Nodes")
3767 props
.merge_type
= 'ZCOMBINE'
3768 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
="Use Alpha Over Nodes")
3770 props
.merge_type
= 'ALPHAOVER'
3773 class NWMergeShadersMenu(Menu
, NWBase
):
3774 bl_idname
= "NODE_MT_nw_merge_shaders_menu"
3775 bl_label
= "Merge Selected Nodes using Shaders"
3777 def draw(self
, context
):
3778 layout
= self
.layout
3779 for type in ('MIX', 'ADD'):
3780 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=type)
3782 props
.merge_type
= 'SHADER'
3785 class NWMergeMixMenu(Menu
, NWBase
):
3786 bl_idname
= "NODE_MT_nw_merge_mix_menu"
3787 bl_label
= "Merge Selected Nodes using Mix"
3789 def draw(self
, context
):
3790 layout
= self
.layout
3791 for type, name
, description
in blend_types
:
3792 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3794 props
.merge_type
= 'MIX'
3797 class NWConnectionListOutputs(Menu
, NWBase
):
3798 bl_idname
= "NODE_MT_nw_connection_list_out"
3801 def draw(self
, context
):
3802 layout
= self
.layout
3803 nodes
, links
= get_nodes_links(context
)
3805 n1
= nodes
[context
.scene
.NWLazySource
]
3807 if n1
.type == "R_LAYERS":
3809 for o
in n1
.outputs
:
3810 if o
.enabled
: # Check which passes the render layer has enabled
3811 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3815 for o
in n1
.outputs
:
3816 layout
.operator(NWCallInputsMenu
.bl_idname
, text
=o
.name
, icon
="RADIOBUT_OFF").from_socket
=index
3820 class NWConnectionListInputs(Menu
, NWBase
):
3821 bl_idname
= "NODE_MT_nw_connection_list_in"
3824 def draw(self
, context
):
3825 layout
= self
.layout
3826 nodes
, links
= get_nodes_links(context
)
3828 n2
= nodes
[context
.scene
.NWLazyTarget
]
3832 op
= layout
.operator(NWMakeLink
.bl_idname
, text
=i
.name
, icon
="FORWARD")
3833 op
.from_socket
= context
.scene
.NWSourceSocket
3834 op
.to_socket
= index
3838 class NWMergeMathMenu(Menu
, NWBase
):
3839 bl_idname
= "NODE_MT_nw_merge_math_menu"
3840 bl_label
= "Merge Selected Nodes using Math"
3842 def draw(self
, context
):
3843 layout
= self
.layout
3844 for type, name
, description
in operations
:
3845 props
= layout
.operator(NWMergeNodes
.bl_idname
, text
=name
)
3847 props
.merge_type
= 'MATH'
3850 class NWBatchChangeNodesMenu(Menu
, NWBase
):
3851 bl_idname
= "NODE_MT_nw_batch_change_nodes_menu"
3852 bl_label
= "Batch Change Selected Nodes"
3854 def draw(self
, context
):
3855 layout
= self
.layout
3856 layout
.menu(NWBatchChangeBlendTypeMenu
.bl_idname
)
3857 layout
.menu(NWBatchChangeOperationMenu
.bl_idname
)
3860 class NWBatchChangeBlendTypeMenu(Menu
, NWBase
):
3861 bl_idname
= "NODE_MT_nw_batch_change_blend_type_menu"
3862 bl_label
= "Batch Change Blend Type"
3864 def draw(self
, context
):
3865 layout
= self
.layout
3866 for type, name
, description
in blend_types
:
3867 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3868 props
.blend_type
= type
3869 props
.operation
= 'CURRENT'
3872 class NWBatchChangeOperationMenu(Menu
, NWBase
):
3873 bl_idname
= "NODE_MT_nw_batch_change_operation_menu"
3874 bl_label
= "Batch Change Math Operation"
3876 def draw(self
, context
):
3877 layout
= self
.layout
3878 for type, name
, description
in operations
:
3879 props
= layout
.operator(NWBatchChangeNodes
.bl_idname
, text
=name
)
3880 props
.blend_type
= 'CURRENT'
3881 props
.operation
= type
3884 class NWCopyToSelectedMenu(Menu
, NWBase
):
3885 bl_idname
= "NODE_MT_nw_copy_node_properties_menu"
3886 bl_label
= "Copy to Selected"
3888 def draw(self
, context
):
3889 layout
= self
.layout
3890 layout
.operator(NWCopySettings
.bl_idname
, text
="Settings from Active")
3891 layout
.menu(NWCopyLabelMenu
.bl_idname
)
3894 class NWCopyLabelMenu(Menu
, NWBase
):
3895 bl_idname
= "NODE_MT_nw_copy_label_menu"
3896 bl_label
= "Copy Label"
3898 def draw(self
, context
):
3899 layout
= self
.layout
3900 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Active Node's Label").option
= 'FROM_ACTIVE'
3901 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Node's Label").option
= 'FROM_NODE'
3902 layout
.operator(NWCopyLabel
.bl_idname
, text
="from Linked Output's Name").option
= 'FROM_SOCKET'
3905 class NWAddReroutesMenu(Menu
, NWBase
):
3906 bl_idname
= "NODE_MT_nw_add_reroutes_menu"
3907 bl_label
= "Add Reroutes"
3908 bl_description
= "Add Reroute Nodes to Selected Nodes' Outputs"
3910 def draw(self
, context
):
3911 layout
= self
.layout
3912 layout
.operator(NWAddReroutes
.bl_idname
, text
="to All Outputs").option
= 'ALL'
3913 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Loose Outputs").option
= 'LOOSE'
3914 layout
.operator(NWAddReroutes
.bl_idname
, text
="to Linked Outputs").option
= 'LINKED'
3917 class NWLinkActiveToSelectedMenu(Menu
, NWBase
):
3918 bl_idname
= "NODE_MT_nw_link_active_to_selected_menu"
3919 bl_label
= "Link Active to Selected"
3921 def draw(self
, context
):
3922 layout
= self
.layout
3923 layout
.menu(NWLinkStandardMenu
.bl_idname
)
3924 layout
.menu(NWLinkUseNodeNameMenu
.bl_idname
)
3925 layout
.menu(NWLinkUseOutputsNamesMenu
.bl_idname
)
3928 class NWLinkStandardMenu(Menu
, NWBase
):
3929 bl_idname
= "NODE_MT_nw_link_standard_menu"
3930 bl_label
= "To All Selected"
3932 def draw(self
, context
):
3933 layout
= self
.layout
3934 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3935 props
.replace
= False
3936 props
.use_node_name
= False
3937 props
.use_outputs_names
= False
3938 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3939 props
.replace
= True
3940 props
.use_node_name
= False
3941 props
.use_outputs_names
= False
3944 class NWLinkUseNodeNameMenu(Menu
, NWBase
):
3945 bl_idname
= "NODE_MT_nw_link_use_node_name_menu"
3946 bl_label
= "Use Node Name/Label"
3948 def draw(self
, context
):
3949 layout
= self
.layout
3950 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3951 props
.replace
= False
3952 props
.use_node_name
= True
3953 props
.use_outputs_names
= False
3954 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3955 props
.replace
= True
3956 props
.use_node_name
= True
3957 props
.use_outputs_names
= False
3960 class NWLinkUseOutputsNamesMenu(Menu
, NWBase
):
3961 bl_idname
= "NODE_MT_nw_link_use_outputs_names_menu"
3962 bl_label
= "Use Outputs Names"
3964 def draw(self
, context
):
3965 layout
= self
.layout
3966 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Don't Replace Links")
3967 props
.replace
= False
3968 props
.use_node_name
= False
3969 props
.use_outputs_names
= True
3970 props
= layout
.operator(NWLinkActiveToSelected
.bl_idname
, text
="Replace Links")
3971 props
.replace
= True
3972 props
.use_node_name
= False
3973 props
.use_outputs_names
= True
3976 class NWVertColMenu(bpy
.types
.Menu
):
3977 bl_idname
= "NODE_MT_nw_node_vertex_color_menu"
3978 bl_label
= "Vertex Colors"
3981 def poll(cls
, context
):
3983 if nw_check(context
):
3984 snode
= context
.space_data
3985 valid
= snode
.tree_type
== 'ShaderNodeTree' and context
.scene
.render
.engine
== 'CYCLES'
3988 def draw(self
, context
):
3990 nodes
, links
= get_nodes_links(context
)
3991 mat
= context
.object.active_material
3994 for obj
in bpy
.data
.objects
:
3995 for slot
in obj
.material_slots
:
3996 if slot
.material
== mat
:
4000 if obj
.data
.vertex_colors
:
4001 for vcol
in obj
.data
.vertex_colors
:
4002 vcols
.append(vcol
.name
)
4003 vcols
= list(set(vcols
)) # get a unique list
4007 l
.operator(NWAddAttrNode
.bl_idname
, text
=vcol
).attr_name
= vcol
4009 l
.label("No Vertex Color layers on objects with this material")
4012 class NWSwitchNodeTypeMenu(Menu
, NWBase
):
4013 bl_idname
= "NODE_MT_nw_switch_node_type_menu"
4014 bl_label
= "Switch Type to..."
4016 def draw(self
, context
):
4017 layout
= self
.layout
4018 tree
= context
.space_data
.node_tree
4019 if tree
.type == 'SHADER':
4020 if context
.scene
.render
.engine
== 'CYCLES':
4021 layout
.menu(NWSwitchShadersInputSubmenu
.bl_idname
)
4022 layout
.menu(NWSwitchShadersOutputSubmenu
.bl_idname
)
4023 layout
.menu(NWSwitchShadersShaderSubmenu
.bl_idname
)
4024 layout
.menu(NWSwitchShadersTextureSubmenu
.bl_idname
)
4025 layout
.menu(NWSwitchShadersColorSubmenu
.bl_idname
)
4026 layout
.menu(NWSwitchShadersVectorSubmenu
.bl_idname
)
4027 layout
.menu(NWSwitchShadersConverterSubmenu
.bl_idname
)
4028 layout
.menu(NWSwitchShadersLayoutSubmenu
.bl_idname
)
4029 if context
.scene
.render
.engine
!= 'CYCLES':
4030 layout
.menu(NWSwitchMatInputSubmenu
.bl_idname
)
4031 layout
.menu(NWSwitchMatOutputSubmenu
.bl_idname
)
4032 layout
.menu(NWSwitchMatColorSubmenu
.bl_idname
)
4033 layout
.menu(NWSwitchMatVectorSubmenu
.bl_idname
)
4034 layout
.menu(NWSwitchMatConverterSubmenu
.bl_idname
)
4035 layout
.menu(NWSwitchMatLayoutSubmenu
.bl_idname
)
4036 if tree
.type == 'COMPOSITING':
4037 layout
.menu(NWSwitchCompoInputSubmenu
.bl_idname
)
4038 layout
.menu(NWSwitchCompoOutputSubmenu
.bl_idname
)
4039 layout
.menu(NWSwitchCompoColorSubmenu
.bl_idname
)
4040 layout
.menu(NWSwitchCompoConverterSubmenu
.bl_idname
)
4041 layout
.menu(NWSwitchCompoFilterSubmenu
.bl_idname
)
4042 layout
.menu(NWSwitchCompoVectorSubmenu
.bl_idname
)
4043 layout
.menu(NWSwitchCompoMatteSubmenu
.bl_idname
)
4044 layout
.menu(NWSwitchCompoDistortSubmenu
.bl_idname
)
4045 layout
.menu(NWSwitchCompoLayoutSubmenu
.bl_idname
)
4046 if tree
.type == 'TEXTURE':
4047 layout
.menu(NWSwitchTexInputSubmenu
.bl_idname
)
4048 layout
.menu(NWSwitchTexOutputSubmenu
.bl_idname
)
4049 layout
.menu(NWSwitchTexColorSubmenu
.bl_idname
)
4050 layout
.menu(NWSwitchTexPatternSubmenu
.bl_idname
)
4051 layout
.menu(NWSwitchTexTexturesSubmenu
.bl_idname
)
4052 layout
.menu(NWSwitchTexConverterSubmenu
.bl_idname
)
4053 layout
.menu(NWSwitchTexDistortSubmenu
.bl_idname
)
4054 layout
.menu(NWSwitchTexLayoutSubmenu
.bl_idname
)
4057 class NWSwitchShadersInputSubmenu(Menu
, NWBase
):
4058 bl_idname
= "NODE_MT_nw_switch_shaders_input_submenu"
4061 def draw(self
, context
):
4062 layout
= self
.layout
4063 for ident
, node_type
, rna_name
in sorted(shaders_input_nodes_props
, key
=lambda k
: k
[2]):
4064 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4065 props
.to_type
= ident
4068 class NWSwitchShadersOutputSubmenu(Menu
, NWBase
):
4069 bl_idname
= "NODE_MT_nw_switch_shaders_output_submenu"
4072 def draw(self
, context
):
4073 layout
= self
.layout
4074 for ident
, node_type
, rna_name
in sorted(shaders_output_nodes_props
, key
=lambda k
: k
[2]):
4075 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4076 props
.to_type
= ident
4079 class NWSwitchShadersShaderSubmenu(Menu
, NWBase
):
4080 bl_idname
= "NODE_MT_nw_switch_shaders_shader_submenu"
4083 def draw(self
, context
):
4084 layout
= self
.layout
4085 for ident
, node_type
, rna_name
in sorted(shaders_shader_nodes_props
, key
=lambda k
: k
[2]):
4086 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4087 props
.to_type
= ident
4090 class NWSwitchShadersTextureSubmenu(Menu
, NWBase
):
4091 bl_idname
= "NODE_MT_nw_switch_shaders_texture_submenu"
4092 bl_label
= "Texture"
4094 def draw(self
, context
):
4095 layout
= self
.layout
4096 for ident
, node_type
, rna_name
in sorted(shaders_texture_nodes_props
, key
=lambda k
: k
[2]):
4097 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4098 props
.to_type
= ident
4101 class NWSwitchShadersColorSubmenu(Menu
, NWBase
):
4102 bl_idname
= "NODE_MT_nw_switch_shaders_color_submenu"
4105 def draw(self
, context
):
4106 layout
= self
.layout
4107 for ident
, node_type
, rna_name
in sorted(shaders_color_nodes_props
, key
=lambda k
: k
[2]):
4108 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4109 props
.to_type
= ident
4112 class NWSwitchShadersVectorSubmenu(Menu
, NWBase
):
4113 bl_idname
= "NODE_MT_nw_switch_shaders_vector_submenu"
4116 def draw(self
, context
):
4117 layout
= self
.layout
4118 for ident
, node_type
, rna_name
in sorted(shaders_vector_nodes_props
, key
=lambda k
: k
[2]):
4119 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4120 props
.to_type
= ident
4123 class NWSwitchShadersConverterSubmenu(Menu
, NWBase
):
4124 bl_idname
= "NODE_MT_nw_switch_shaders_converter_submenu"
4125 bl_label
= "Converter"
4127 def draw(self
, context
):
4128 layout
= self
.layout
4129 for ident
, node_type
, rna_name
in sorted(shaders_converter_nodes_props
, key
=lambda k
: k
[2]):
4130 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4131 props
.to_type
= ident
4134 class NWSwitchShadersLayoutSubmenu(Menu
, NWBase
):
4135 bl_idname
= "NODE_MT_nw_switch_shaders_layout_submenu"
4138 def draw(self
, context
):
4139 layout
= self
.layout
4140 for ident
, node_type
, rna_name
in sorted(shaders_layout_nodes_props
, key
=lambda k
: k
[2]):
4141 if node_type
!= 'FRAME':
4142 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4143 props
.to_type
= ident
4146 class NWSwitchCompoInputSubmenu(Menu
, NWBase
):
4147 bl_idname
= "NODE_MT_nw_switch_compo_input_submenu"
4150 def draw(self
, context
):
4151 layout
= self
.layout
4152 for ident
, node_type
, rna_name
in sorted(compo_input_nodes_props
, key
=lambda k
: k
[2]):
4153 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4154 props
.to_type
= ident
4157 class NWSwitchCompoOutputSubmenu(Menu
, NWBase
):
4158 bl_idname
= "NODE_MT_nw_switch_compo_output_submenu"
4161 def draw(self
, context
):
4162 layout
= self
.layout
4163 for ident
, node_type
, rna_name
in sorted(compo_output_nodes_props
, key
=lambda k
: k
[2]):
4164 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4165 props
.to_type
= ident
4168 class NWSwitchCompoColorSubmenu(Menu
, NWBase
):
4169 bl_idname
= "NODE_MT_nw_switch_compo_color_submenu"
4172 def draw(self
, context
):
4173 layout
= self
.layout
4174 for ident
, node_type
, rna_name
in sorted(compo_color_nodes_props
, key
=lambda k
: k
[2]):
4175 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4176 props
.to_type
= ident
4179 class NWSwitchCompoConverterSubmenu(Menu
, NWBase
):
4180 bl_idname
= "NODE_MT_nw_switch_compo_converter_submenu"
4181 bl_label
= "Converter"
4183 def draw(self
, context
):
4184 layout
= self
.layout
4185 for ident
, node_type
, rna_name
in sorted(compo_converter_nodes_props
, key
=lambda k
: k
[2]):
4186 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4187 props
.to_type
= ident
4190 class NWSwitchCompoFilterSubmenu(Menu
, NWBase
):
4191 bl_idname
= "NODE_MT_nw_switch_compo_filter_submenu"
4194 def draw(self
, context
):
4195 layout
= self
.layout
4196 for ident
, node_type
, rna_name
in sorted(compo_filter_nodes_props
, key
=lambda k
: k
[2]):
4197 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4198 props
.to_type
= ident
4201 class NWSwitchCompoVectorSubmenu(Menu
, NWBase
):
4202 bl_idname
= "NODE_MT_nw_switch_compo_vector_submenu"
4205 def draw(self
, context
):
4206 layout
= self
.layout
4207 for ident
, node_type
, rna_name
in sorted(compo_vector_nodes_props
, key
=lambda k
: k
[2]):
4208 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4209 props
.to_type
= ident
4212 class NWSwitchCompoMatteSubmenu(Menu
, NWBase
):
4213 bl_idname
= "NODE_MT_nw_switch_compo_matte_submenu"
4216 def draw(self
, context
):
4217 layout
= self
.layout
4218 for ident
, node_type
, rna_name
in sorted(compo_matte_nodes_props
, key
=lambda k
: k
[2]):
4219 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4220 props
.to_type
= ident
4223 class NWSwitchCompoDistortSubmenu(Menu
, NWBase
):
4224 bl_idname
= "NODE_MT_nw_switch_compo_distort_submenu"
4225 bl_label
= "Distort"
4227 def draw(self
, context
):
4228 layout
= self
.layout
4229 for ident
, node_type
, rna_name
in sorted(compo_distort_nodes_props
, key
=lambda k
: k
[2]):
4230 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4231 props
.to_type
= ident
4234 class NWSwitchCompoLayoutSubmenu(Menu
, NWBase
):
4235 bl_idname
= "NODE_MT_nw_switch_compo_layout_submenu"
4238 def draw(self
, context
):
4239 layout
= self
.layout
4240 for ident
, node_type
, rna_name
in sorted(compo_layout_nodes_props
, key
=lambda k
: k
[2]):
4241 if node_type
!= 'FRAME':
4242 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4243 props
.to_type
= ident
4246 class NWSwitchMatInputSubmenu(Menu
, NWBase
):
4247 bl_idname
= "NODE_MT_nw_switch_mat_input_submenu"
4250 def draw(self
, context
):
4251 layout
= self
.layout
4252 for ident
, node_type
, rna_name
in sorted(blender_mat_input_nodes_props
, key
=lambda k
: k
[2]):
4253 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4254 props
.to_type
= ident
4257 class NWSwitchMatOutputSubmenu(Menu
, NWBase
):
4258 bl_idname
= "NODE_MT_nw_switch_mat_output_submenu"
4261 def draw(self
, context
):
4262 layout
= self
.layout
4263 for ident
, node_type
, rna_name
in sorted(blender_mat_output_nodes_props
, key
=lambda k
: k
[2]):
4264 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4265 props
.to_type
= ident
4268 class NWSwitchMatColorSubmenu(Menu
, NWBase
):
4269 bl_idname
= "NODE_MT_nw_switch_mat_color_submenu"
4272 def draw(self
, context
):
4273 layout
= self
.layout
4274 for ident
, node_type
, rna_name
in sorted(blender_mat_color_nodes_props
, key
=lambda k
: k
[2]):
4275 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4276 props
.to_type
= ident
4279 class NWSwitchMatVectorSubmenu(Menu
, NWBase
):
4280 bl_idname
= "NODE_MT_nw_switch_mat_vector_submenu"
4283 def draw(self
, context
):
4284 layout
= self
.layout
4285 for ident
, node_type
, rna_name
in sorted(blender_mat_vector_nodes_props
, key
=lambda k
: k
[2]):
4286 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4287 props
.to_type
= ident
4290 class NWSwitchMatConverterSubmenu(Menu
, NWBase
):
4291 bl_idname
= "NODE_MT_nw_switch_mat_converter_submenu"
4292 bl_label
= "Converter"
4294 def draw(self
, context
):
4295 layout
= self
.layout
4296 for ident
, node_type
, rna_name
in sorted(blender_mat_converter_nodes_props
, key
=lambda k
: k
[2]):
4297 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4298 props
.to_type
= ident
4301 class NWSwitchMatLayoutSubmenu(Menu
, NWBase
):
4302 bl_idname
= "NODE_MT_nw_switch_mat_layout_submenu"
4305 def draw(self
, context
):
4306 layout
= self
.layout
4307 for ident
, node_type
, rna_name
in sorted(blender_mat_layout_nodes_props
, key
=lambda k
: k
[2]):
4308 if node_type
!= 'FRAME':
4309 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4310 props
.to_type
= ident
4313 class NWSwitchTexInputSubmenu(Menu
, NWBase
):
4314 bl_idname
= "NODE_MT_nw_switch_tex_input_submenu"
4317 def draw(self
, context
):
4318 layout
= self
.layout
4319 for ident
, node_type
, rna_name
in sorted(texture_input_nodes_props
, key
=lambda k
: k
[2]):
4320 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4321 props
.to_type
= ident
4324 class NWSwitchTexOutputSubmenu(Menu
, NWBase
):
4325 bl_idname
= "NODE_MT_nw_switch_tex_output_submenu"
4328 def draw(self
, context
):
4329 layout
= self
.layout
4330 for ident
, node_type
, rna_name
in sorted(texture_output_nodes_props
, key
=lambda k
: k
[2]):
4331 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4332 props
.to_type
= ident
4335 class NWSwitchTexColorSubmenu(Menu
, NWBase
):
4336 bl_idname
= "NODE_MT_nw_switch_tex_color_submenu"
4339 def draw(self
, context
):
4340 layout
= self
.layout
4341 for ident
, node_type
, rna_name
in sorted(texture_color_nodes_props
, key
=lambda k
: k
[2]):
4342 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4343 props
.to_type
= ident
4346 class NWSwitchTexPatternSubmenu(Menu
, NWBase
):
4347 bl_idname
= "NODE_MT_nw_switch_tex_pattern_submenu"
4348 bl_label
= "Pattern"
4350 def draw(self
, context
):
4351 layout
= self
.layout
4352 for ident
, node_type
, rna_name
in sorted(texture_pattern_nodes_props
, key
=lambda k
: k
[2]):
4353 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4354 props
.to_type
= ident
4357 class NWSwitchTexTexturesSubmenu(Menu
, NWBase
):
4358 bl_idname
= "NODE_MT_nw_switch_tex_textures_submenu"
4359 bl_label
= "Textures"
4361 def draw(self
, context
):
4362 layout
= self
.layout
4363 for ident
, node_type
, rna_name
in sorted(texture_textures_nodes_props
, key
=lambda k
: k
[2]):
4364 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4365 props
.to_type
= ident
4368 class NWSwitchTexConverterSubmenu(Menu
, NWBase
):
4369 bl_idname
= "NODE_MT_nw_switch_tex_converter_submenu"
4370 bl_label
= "Converter"
4372 def draw(self
, context
):
4373 layout
= self
.layout
4374 for ident
, node_type
, rna_name
in sorted(texture_converter_nodes_props
, key
=lambda k
: k
[2]):
4375 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4376 props
.to_type
= ident
4379 class NWSwitchTexDistortSubmenu(Menu
, NWBase
):
4380 bl_idname
= "NODE_MT_nw_switch_tex_distort_submenu"
4381 bl_label
= "Distort"
4383 def draw(self
, context
):
4384 layout
= self
.layout
4385 for ident
, node_type
, rna_name
in sorted(texture_distort_nodes_props
, key
=lambda k
: k
[2]):
4386 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4387 props
.to_type
= ident
4390 class NWSwitchTexLayoutSubmenu(Menu
, NWBase
):
4391 bl_idname
= "NODE_MT_nw_switch_tex_layout_submenu"
4394 def draw(self
, context
):
4395 layout
= self
.layout
4396 for ident
, node_type
, rna_name
in sorted(texture_layout_nodes_props
, key
=lambda k
: k
[2]):
4397 if node_type
!= 'FRAME':
4398 props
= layout
.operator(NWSwitchNodeType
.bl_idname
, text
=rna_name
)
4399 props
.to_type
= ident
4403 # APPENDAGES TO EXISTING UI
4407 def select_parent_children_buttons(self
, context
):
4408 layout
= self
.layout
4409 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select frame's members (children)").option
= 'CHILD'
4410 layout
.operator(NWSelectParentChildren
.bl_idname
, text
="Select parent frame").option
= 'PARENT'
4413 def attr_nodes_menu_func(self
, context
):
4414 col
= self
.layout
.column(align
=True)
4415 col
.menu("NODE_MT_nw_node_vertex_color_menu")
4419 def multipleimages_menu_func(self
, context
):
4420 col
= self
.layout
.column(align
=True)
4421 col
.operator(NWAddMultipleImages
.bl_idname
, text
="Multiple Images")
4422 col
.operator(NWAddSequence
.bl_idname
, text
="Image Sequence")
4426 def bgreset_menu_func(self
, context
):
4427 self
.layout
.operator(NWResetBG
.bl_idname
)
4430 def save_viewer_menu_func(self
, context
):
4431 if nw_check(context
):
4432 if context
.space_data
.tree_type
== 'CompositorNodeTree':
4433 if context
.scene
.node_tree
.nodes
.active
:
4434 if context
.scene
.node_tree
.nodes
.active
.type == "VIEWER":
4435 self
.layout
.operator(NWSaveViewer
.bl_idname
, icon
='FILE_IMAGE')
4438 def reset_nodes_button(self
, context
):
4439 node_active
= context
.active_node
4440 node_selected
= context
.selected_nodes
4441 node_ignore
= ["FRAME","REROUTE", "GROUP"]
4443 # Check if active node is in the selection and respective type
4444 if (len(node_selected
) == 1) and node_active
.select
and node_active
.type not in node_ignore
:
4445 row
= self
.layout
.row()
4446 row
.operator("node.nw_reset_nodes", text
="Reset Node", icon
="FILE_REFRESH")
4447 self
.layout
.separator()
4449 elif (len(node_selected
) == 1) and node_active
.select
and node_active
.type == "FRAME":
4450 row
= self
.layout
.row()
4451 row
.operator("node.nw_reset_nodes", text
="Reset Nodes in Frame", icon
="FILE_REFRESH")
4452 self
.layout
.separator()
4456 # REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
4460 # kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
4461 # props entry: (property name, property value)
4464 # NWMergeNodes with Ctrl (AUTO).
4465 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, False,
4466 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4467 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, False,
4468 (('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
4469 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, False,
4470 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4471 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, False,
4472 (('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
4473 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
4474 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4475 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, False,
4476 (('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
4477 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, False,
4478 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4479 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, False,
4480 (('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
4481 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, False,
4482 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4483 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, False,
4484 (('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
4485 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, False, False,
4486 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
4487 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, False, False,
4488 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
4489 (NWMergeNodes
.bl_idname
, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
4490 (('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
4491 # NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
4492 (NWMergeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', True, False, True,
4493 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4494 (NWMergeNodes
.bl_idname
, 'ZERO', 'PRESS', True, False, True,
4495 (('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
4496 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, False, True,
4497 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4498 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, False, True,
4499 (('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
4500 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
4501 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4502 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, False, True,
4503 (('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
4504 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, False, True,
4505 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4506 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, False, True,
4507 (('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
4508 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, False, True,
4509 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4510 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, False, True,
4511 (('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
4512 # NWMergeNodes with Ctrl Shift (MATH)
4513 (NWMergeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', True, True, False,
4514 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4515 (NWMergeNodes
.bl_idname
, 'EQUAL', 'PRESS', True, True, False,
4516 (('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
4517 (NWMergeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
4518 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4519 (NWMergeNodes
.bl_idname
, 'EIGHT', 'PRESS', True, True, False,
4520 (('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
4521 (NWMergeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', True, True, False,
4522 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4523 (NWMergeNodes
.bl_idname
, 'MINUS', 'PRESS', True, True, False,
4524 (('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
4525 (NWMergeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', True, True, False,
4526 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4527 (NWMergeNodes
.bl_idname
, 'SLASH', 'PRESS', True, True, False,
4528 (('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
4529 (NWMergeNodes
.bl_idname
, 'COMMA', 'PRESS', True, True, False,
4530 (('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
4531 (NWMergeNodes
.bl_idname
, 'PERIOD', 'PRESS', True, True, False,
4532 (('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
4533 # BATCH CHANGE NODES
4534 # NWBatchChangeNodes with Alt
4535 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_0', 'PRESS', False, False, True,
4536 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4537 (NWBatchChangeNodes
.bl_idname
, 'ZERO', 'PRESS', False, False, True,
4538 (('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
4539 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_PLUS', 'PRESS', False, False, True,
4540 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4541 (NWBatchChangeNodes
.bl_idname
, 'EQUAL', 'PRESS', False, False, True,
4542 (('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
4543 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
4544 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4545 (NWBatchChangeNodes
.bl_idname
, 'EIGHT', 'PRESS', False, False, True,
4546 (('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
4547 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_MINUS', 'PRESS', False, False, True,
4548 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4549 (NWBatchChangeNodes
.bl_idname
, 'MINUS', 'PRESS', False, False, True,
4550 (('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
4551 (NWBatchChangeNodes
.bl_idname
, 'NUMPAD_SLASH', 'PRESS', False, False, True,
4552 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4553 (NWBatchChangeNodes
.bl_idname
, 'SLASH', 'PRESS', False, False, True,
4554 (('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
4555 (NWBatchChangeNodes
.bl_idname
, 'COMMA', 'PRESS', False, False, True,
4556 (('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
4557 (NWBatchChangeNodes
.bl_idname
, 'PERIOD', 'PRESS', False, False, True,
4558 (('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
4559 (NWBatchChangeNodes
.bl_idname
, 'DOWN_ARROW', 'PRESS', False, False, True,
4560 (('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
4561 (NWBatchChangeNodes
.bl_idname
, 'UP_ARROW', 'PRESS', False, False, True,
4562 (('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
4563 # LINK ACTIVE TO SELECTED
4564 # Don't use names, don't replace links (K)
4565 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, False, False,
4566 (('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
4567 # Don't use names, replace links (Shift K)
4568 (NWLinkActiveToSelected
.bl_idname
, 'K', 'PRESS', False, True, False,
4569 (('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
4570 # Use node name, don't replace links (')
4571 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, False, False,
4572 (('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
4573 # Use node name, replace links (Shift ')
4574 (NWLinkActiveToSelected
.bl_idname
, 'QUOTE', 'PRESS', False, True, False,
4575 (('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
4576 # Don't use names, don't replace links (;)
4577 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, False, False,
4578 (('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
4579 # Don't use names, replace links (')
4580 (NWLinkActiveToSelected
.bl_idname
, 'SEMI_COLON', 'PRESS', False, True, False,
4581 (('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
4583 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
4584 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
4585 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', False, True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
4586 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', False, True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
4587 (NWChangeMixFactor
.bl_idname
, 'LEFT_ARROW', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4588 (NWChangeMixFactor
.bl_idname
, 'RIGHT_ARROW', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4589 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_0', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4590 (NWChangeMixFactor
.bl_idname
, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
4591 (NWChangeMixFactor
.bl_idname
, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
4592 (NWChangeMixFactor
.bl_idname
, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
4593 # CLEAR LABEL (Alt L)
4594 (NWClearLabel
.bl_idname
, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
4595 # MODIFY LABEL (Alt Shift L)
4596 (NWModifyLabels
.bl_idname
, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
4597 # Copy Label from active to selected
4598 (NWCopyLabel
.bl_idname
, 'V', 'PRESS', False, True, False, (('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
4599 # DETACH OUTPUTS (Alt Shift D)
4600 (NWDetachOutputs
.bl_idname
, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
4601 # LINK TO OUTPUT NODE (O)
4602 (NWLinkToOutputNode
.bl_idname
, 'O', 'PRESS', False, False, False, None, "Link to output node"),
4603 # SELECT PARENT/CHILDREN
4605 (NWSelectParentChildren
.bl_idname
, 'RIGHT_BRACKET', 'PRESS', False, False, False, (('option', 'CHILD'),), "Select children"),
4607 (NWSelectParentChildren
.bl_idname
, 'LEFT_BRACKET', 'PRESS', False, False, False, (('option', 'PARENT'),), "Select Parent"),
4609 (NWAddTextureSetup
.bl_idname
, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
4610 # Add Principled BSDF Texture Setup
4611 (NWAddPrincipledSetup
.bl_idname
, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
4613 (NWResetBG
.bl_idname
, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
4615 (NWDeleteUnused
.bl_idname
, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
4617 (NWFrameSelected
.bl_idname
, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
4619 (NWSwapLinks
.bl_idname
, 'S', 'PRESS', False, False, True, None, "Swap Outputs"),
4621 (NWEmissionViewer
.bl_idname
, 'LEFTMOUSE', 'PRESS', True, True, False, None, "Connect to Cycles Viewer node"),
4623 (NWReloadImages
.bl_idname
, 'R', 'PRESS', False, False, True, None, "Reload images"),
4625 (NWLazyMix
.bl_idname
, 'RIGHTMOUSE', 'PRESS', False, False, True, None, "Lazy Mix"),
4627 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, False, False, None, "Lazy Connect"),
4628 # Lazy Connect with Menu
4629 (NWLazyConnect
.bl_idname
, 'RIGHTMOUSE', 'PRESS', True, True, False, (('with_menu', True),), "Lazy Connect with Socket Menu"),
4630 # Viewer Tile Center
4631 (NWViewerFocus
.bl_idname
, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
4633 (NWAlignNodes
.bl_idname
, 'EQUAL', 'PRESS', False, True, False, None, "Align selected nodes neatly in a row/column"),
4634 # Reset Nodes (Back Space)
4635 (NWResetNodes
.bl_idname
, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
4637 ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu
.bl_idname
),), "Node Wranger menu"),
4638 ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4639 ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu
.bl_idname
),), "Add Reroutes menu"),
4640 ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu
.bl_idname
),), "Link active to selected (menu)"),
4641 ('wm.call_menu', 'C', 'PRESS', False, True, False, (('name', NWCopyToSelectedMenu
.bl_idname
),), "Copy to selected (menu)"),
4642 ('wm.call_menu', 'S', 'PRESS', False, True, False, (('name', NWSwitchNodeTypeMenu
.bl_idname
),), "Switch node type menu"),
4648 bpy
.types
.Scene
.NWBusyDrawing
= StringProperty(
4649 name
="Busy Drawing!",
4651 description
="An internal property used to store only the first mouse position")
4652 bpy
.types
.Scene
.NWLazySource
= StringProperty(
4653 name
="Lazy Source!",
4655 description
="An internal property used to store the first node in a Lazy Connect operation")
4656 bpy
.types
.Scene
.NWLazyTarget
= StringProperty(
4657 name
="Lazy Target!",
4659 description
="An internal property used to store the last node in a Lazy Connect operation")
4660 bpy
.types
.Scene
.NWSourceSocket
= IntProperty(
4661 name
="Source Socket!",
4663 description
="An internal property used to store the source socket in a Lazy Connect operation")
4665 bpy
.utils
.register_module(__name__
)
4668 addon_keymaps
.clear()
4669 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
4671 km
= kc
.keymaps
.new(name
='Node Editor', space_type
="NODE_EDITOR")
4672 for (identifier
, key
, action
, CTRL
, SHIFT
, ALT
, props
, nicename
) in kmi_defs
:
4673 kmi
= km
.keymap_items
.new(identifier
, key
, action
, ctrl
=CTRL
, shift
=SHIFT
, alt
=ALT
)
4675 for prop
, value
in props
:
4676 setattr(kmi
.properties
, prop
, value
)
4677 addon_keymaps
.append((km
, kmi
))
4680 bpy
.types
.NODE_MT_select
.append(select_parent_children_buttons
)
4681 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4682 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.prepend(attr_nodes_menu_func
)
4683 bpy
.types
.NODE_PT_backdrop
.append(bgreset_menu_func
)
4684 bpy
.types
.NODE_PT_active_node_generic
.append(save_viewer_menu_func
)
4685 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4686 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.prepend(multipleimages_menu_func
)
4687 bpy
.types
.NODE_MT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4688 bpy
.types
.NODE_PT_category_CMP_INPUT
.prepend(multipleimages_menu_func
)
4689 bpy
.types
.NODE_PT_active_node_generic
.prepend(reset_nodes_button
)
4690 bpy
.types
.NODE_MT_node
.prepend(reset_nodes_button
)
4695 del bpy
.types
.Scene
.NWBusyDrawing
4696 del bpy
.types
.Scene
.NWLazySource
4697 del bpy
.types
.Scene
.NWLazyTarget
4698 del bpy
.types
.Scene
.NWSourceSocket
4701 for km
, kmi
in addon_keymaps
:
4702 km
.keymap_items
.remove(kmi
)
4703 addon_keymaps
.clear()
4706 bpy
.types
.NODE_MT_select
.remove(select_parent_children_buttons
)
4707 bpy
.types
.NODE_MT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4708 bpy
.types
.NODE_PT_category_SH_NEW_INPUT
.remove(attr_nodes_menu_func
)
4709 bpy
.types
.NODE_PT_backdrop
.remove(bgreset_menu_func
)
4710 bpy
.types
.NODE_PT_active_node_generic
.remove(save_viewer_menu_func
)
4711 bpy
.types
.NODE_MT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4712 bpy
.types
.NODE_PT_category_SH_NEW_TEXTURE
.remove(multipleimages_menu_func
)
4713 bpy
.types
.NODE_MT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4714 bpy
.types
.NODE_PT_category_CMP_INPUT
.remove(multipleimages_menu_func
)
4715 bpy
.types
.NODE_PT_active_node_generic
.remove(reset_nodes_button
)
4716 bpy
.types
.NODE_MT_node
.remove(reset_nodes_button
)
4718 bpy
.utils
.unregister_module(__name__
)
4720 if __name__
== "__main__":