Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
"name": "Node Wrangler",
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
"version": (3, 40),
"location": "Node Editor Toolbar or Shift-W",
"description": "Various tools to enhance and speed up node-based workflow",
"warning": "",
"doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_wrangler.html",
"category": "Node",
import bpy, blf, bgl
from bpy.types import Operator, Panel, Menu
from bpy.props import (
FloatProperty,
EnumProperty,
BoolProperty,
IntProperty,
StringProperty,
FloatVectorProperty,
CollectionProperty,
)
from bpy_extras.io_utils import ImportHelper, ExportHelper
from gpu_extras.batch import batch_for_shader
from nodeitems_utils import node_categories_iter, NodeItemCustom
from math import cos, sin, pi, hypot
from os import path
from copy import copy
from collections import namedtuple
#################
# rl_outputs:
# list of outputs of Input Render Layer
# with attributes determining if pass is used,
# and MultiLayer EXR outputs names and corresponding render engines
#
# rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_eevee, in_cycles)
RL_entry = namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_eevee', 'in_cycles'])
RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
RL_entry('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
RL_entry('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
RL_entry('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
RL_entry('use_pass_emit', 'Emit', 'Emit', False, True),
RL_entry('use_pass_environment', 'Environment', 'Env', False, False),
RL_entry('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
RL_entry('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
RL_entry('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
RL_entry('use_pass_indirect', 'Indirect', 'Indirect', False, False),
RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', False, True),
RL_entry('use_pass_mist', 'Mist', 'Mist', True, True),
RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', False, True),
RL_entry('use_pass_shadow', 'Shadow', 'Shadow', False, True),
RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', True, True),
RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', True, True),
RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
RL_entry('use_pass_uv', 'UV', 'UV', True, True),
RL_entry('use_pass_vector', 'Speed', 'Vector', False, True),
RL_entry('use_pass_z', 'Z', 'Depth', True, True),
)
# shader nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_input_nodes_props = (
('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
('ShaderNodeBevel', 'BEVEL', 'Bevel'),
('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
('ShaderNodeRGB', 'RGB', 'RGB'),
('ShaderNodeTangent', 'TANGENT', 'Tangent'),
('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
('ShaderNodeValue', 'VALUE', 'Value'),
('ShaderNodeVertexColor', 'VERTEX_COLOR', 'Vertex Color'),
('ShaderNodeVolumeInfo', 'VOLUME_INFO', 'Volume Info'),
('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_output_nodes_props = (
('ShaderNodeOutputAOV', 'OUTPUT_AOV', 'AOV Output'),
('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_shader_nodes_props = (
('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
('ShaderNodeEmission', 'EMISSION', 'Emission'),
('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
('ShaderNodeBsdfHairPrincipled', 'BSDF_HAIR_PRINCIPLED', 'Principled Hair BSDF'),
('ShaderNodeVolumePrincipled', 'PRINCIPLED_VOLUME', 'Principled Volume'),
('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
('ShaderNodeBackground', 'BACKGROUND', 'Background'),
('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping things in alphabetical order so we don't need to sort later.
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
shaders_texture_nodes_props = (
('ShaderNodeTexBrick', 'TEX_BRICK', 'Brick Texture'),
('ShaderNodeTexChecker', 'TEX_CHECKER', 'Checker Texture'),
('ShaderNodeTexEnvironment', 'TEX_ENVIRONMENT', 'Environment Texture'),
('ShaderNodeTexGradient', 'TEX_GRADIENT', 'Gradient Texture'),
('ShaderNodeTexIES', 'TEX_IES', 'IES Texture'),
('ShaderNodeTexImage', 'TEX_IMAGE', 'Image Texture'),
('ShaderNodeTexMagic', 'TEX_MAGIC', 'Magic Texture'),
('ShaderNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave Texture'),
('ShaderNodeTexNoise', 'TEX_NOISE', 'Noise Texture'),
('ShaderNodeTexPointDensity', 'TEX_POINTDENSITY', 'Point Density'),
('ShaderNodeTexSky', 'TEX_SKY', 'Sky Texture'),
('ShaderNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi Texture'),
('ShaderNodeTexWave', 'TEX_WAVE', 'Wave Texture'),
('ShaderNodeTexWhiteNoise', 'TEX_WHITE_NOISE', 'White Noise'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_color_nodes_props = (
('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
('ShaderNodeGamma', 'GAMMA', 'Gamma'),
('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
('ShaderNodeInvert', 'INVERT', 'Invert'),
('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_vector_nodes_props = (
('ShaderNodeBump', 'BUMP', 'Bump'),
('ShaderNodeDisplacement', 'DISPLACEMENT', 'Displacement'),
('ShaderNodeMapping', 'MAPPING', 'Mapping'),
('ShaderNodeNormal', 'NORMAL', 'Normal'),
('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
('ShaderNodeVectorDisplacement', 'VECTOR_DISPLACEMENT', 'Vector Displacement'),
('ShaderNodeVectorTransform', 'VECT_TRANSFORM', 'Vector Transform'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_converter_nodes_props = (
('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
('ShaderNodeClamp', 'CLAMP', 'Clamp'),
('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
('ShaderNodeMapRange', 'MAP_RANGE', 'Map Range'),
('ShaderNodeMath', 'MATH', 'Math'),
('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
shaders_layout_nodes_props = (
('NodeFrame', 'FRAME', 'Frame'),
('NodeReroute', 'REROUTE', 'Reroute'),
)
# compositing nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_input_nodes_props = (
('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
('CompositorNodeImage', 'IMAGE', 'Image'),
('CompositorNodeMask', 'MASK', 'Mask'),
('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
('CompositorNodeRGB', 'RGB', 'RGB'),
('CompositorNodeTexture', 'TEXTURE', 'Texture'),
('CompositorNodeTime', 'TIME', 'Time'),
('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
('CompositorNodeValue', 'VALUE', 'Value'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_output_nodes_props = (
('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
('CompositorNodeLevels', 'LEVELS', 'Levels'),
('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
('CompositorNodeViewer', 'VIEWER', 'Viewer'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_color_nodes_props = (
('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
('CompositorNodeGamma', 'GAMMA', 'Gamma'),
('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
('CompositorNodeInvert', 'INVERT', 'Invert'),
('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
('CompositorNodeTonemap', 'TONEMAP', 'Tonemap'),
('CompositorNodeZcombine', 'ZCOMBINE', 'Z Combine'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_converter_nodes_props = (
('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
('CompositorNodeMath', 'MATH', 'Math'),
('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
('CompositorNodeSwitchView', 'VIEWSWITCH', 'View Switch'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_filter_nodes_props = (
('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
('CompositorNodeBlur', 'BLUR', 'Blur'),
('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
('CompositorNodeDenoise', 'DENOISE', 'Denoise'),
('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
('CompositorNodeFilter', 'FILTER', 'Filter'),
('CompositorNodeGlare', 'GLARE', 'Glare'),
('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_vector_nodes_props = (
('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
('CompositorNodeNormal', 'NORMAL', 'Normal'),
('CompositorNodeNormalize', 'NORMALIZE', 'Normalize'),
('CompositorNodeCurveVec', 'CURVE_VEC', 'Vector Curves'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_matte_nodes_props = (
('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
('CompositorNodeCryptomatte', 'CRYPTOMATTE', 'Cryptomatte'),
('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
('CompositorNodeKeying', 'KEYING', 'Keying'),
('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_distort_nodes_props = (
('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
('CompositorNodeCrop', 'CROP', 'Crop'),
('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
('CompositorNodeFlip', 'FLIP', 'Flip'),
('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
('CompositorNodeRotate', 'ROTATE', 'Rotate'),
('CompositorNodeScale', 'SCALE', 'Scale'),
('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
# Keeping things in alphabetical order so we don't need to sort later.
compo_layout_nodes_props = (
('NodeFrame', 'FRAME', 'Frame'),
('NodeReroute', 'REROUTE', 'Reroute'),
('CompositorNodeSwitch', 'SWITCH', 'Switch'),
)
# Blender Render material nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_input_nodes_props = (
('ShaderNodeMaterial', 'MATERIAL', 'Material'),
('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
('ShaderNodeLightData', 'LIGHT', 'Light Data'),
('ShaderNodeValue', 'VALUE', 'Value'),
('ShaderNodeRGB', 'RGB', 'RGB'),
('ShaderNodeTexture', 'TEXTURE', 'Texture'),
('ShaderNodeGeometry', 'GEOMETRY', 'Geometry'),
('ShaderNodeExtendedMaterial', 'MATERIAL_EXT', 'Extended Material'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_output_nodes_props = (
('ShaderNodeOutput', 'OUTPUT', 'Output'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_color_nodes_props = (
('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
('ShaderNodeInvert', 'INVERT', 'Invert'),
('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_vector_nodes_props = (
('ShaderNodeNormal', 'NORMAL', 'Normal'),
('ShaderNodeMapping', 'MAPPING', 'Mapping'),
('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_converter_nodes_props = (
('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
('ShaderNodeMath', 'MATH', 'Math'),
('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
('ShaderNodeSqueeze', 'SQUEEZE', 'Squeeze Value'),
('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
blender_mat_layout_nodes_props = (
('NodeReroute', 'REROUTE', 'Reroute'),
)
# Texture Nodes
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_input_nodes_props = (
('TextureNodeCurveTime', 'CURVE_TIME', 'Curve Time'),
('TextureNodeCoordinates', 'COORD', 'Coordinates'),
('TextureNodeTexture', 'TEXTURE', 'Texture'),
('TextureNodeImage', 'IMAGE', 'Image'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_output_nodes_props = (
('TextureNodeOutput', 'OUTPUT', 'Output'),
('TextureNodeViewer', 'VIEWER', 'Viewer'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_color_nodes_props = (
('TextureNodeMixRGB', 'MIX_RGB', 'Mix RGB'),
('TextureNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
('TextureNodeInvert', 'INVERT', 'Invert'),
('TextureNodeHueSaturation', 'HUE_SAT', 'Hue Saturation Value'),
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
('TextureNodeCompose', 'COMPOSE', 'Combine RGBA'),
('TextureNodeDecompose', 'DECOMPOSE', 'Separate RGBA'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_pattern_nodes_props = (
('TextureNodeChecker', 'CHECKER', 'Checker'),
('TextureNodeBricks', 'BRICKS', 'Bricks'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_textures_nodes_props = (
('TextureNodeTexNoise', 'TEX_NOISE', 'Noise'),
('TextureNodeTexDistNoise', 'TEX_DISTNOISE', 'Distorted Noise'),
('TextureNodeTexClouds', 'TEX_CLOUDS', 'Clouds'),
('TextureNodeTexBlend', 'TEX_BLEND', 'Blend'),
('TextureNodeTexVoronoi', 'TEX_VORONOI', 'Voronoi'),
('TextureNodeTexMagic', 'TEX_MAGIC', 'Magic'),
('TextureNodeTexMarble', 'TEX_MARBLE', 'Marble'),
('TextureNodeTexWood', 'TEX_WOOD', 'Wood'),
('TextureNodeTexMusgrave', 'TEX_MUSGRAVE', 'Musgrave'),
('TextureNodeTexStucci', 'TEX_STUCCI', 'Stucci'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_converter_nodes_props = (
('TextureNodeMath', 'MATH', 'Math'),
('TextureNodeValToRGB', 'VALTORGB', 'ColorRamp'),
('TextureNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
('TextureNodeValToNor', 'VALTONOR', 'Value to Normal'),
('TextureNodeDistance', 'DISTANCE', 'Distance'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_distort_nodes_props = (
('TextureNodeScale', 'SCALE', 'Scale'),
('TextureNodeTranslate', 'TRANSLATE', 'Translate'),
('TextureNodeRotate', 'ROTATE', 'Rotate'),
('TextureNodeAt', 'AT', 'At'),
)
# (rna_type.identifier, type, rna_type.name)
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
texture_layout_nodes_props = (
('NodeReroute', 'REROUTE', 'Reroute'),
)
# list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
# used list, not tuple for easy merging with other lists.
blend_types = [
('MIX', 'Mix', 'Mix Mode'),
('ADD', 'Add', 'Add Mode'),
('MULTIPLY', 'Multiply', 'Multiply Mode'),
('SUBTRACT', 'Subtract', 'Subtract Mode'),
('SCREEN', 'Screen', 'Screen Mode'),
('DIVIDE', 'Divide', 'Divide Mode'),
('DIFFERENCE', 'Difference', 'Difference Mode'),
('DARKEN', 'Darken', 'Darken Mode'),
('LIGHTEN', 'Lighten', 'Lighten Mode'),
('OVERLAY', 'Overlay', 'Overlay Mode'),
('DODGE', 'Dodge', 'Dodge Mode'),
('BURN', 'Burn', 'Burn Mode'),
('HUE', 'Hue', 'Hue Mode'),
('SATURATION', 'Saturation', 'Saturation Mode'),
('VALUE', 'Value', 'Value Mode'),
('COLOR', 'Color', 'Color Mode'),
('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
# list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
# used list, not tuple for easy merging with other lists.
operations = [
('ADD', 'Add', 'Add Mode'),
('SUBTRACT', 'Subtract', 'Subtract Mode'),
('MULTIPLY', 'Multiply', 'Multiply Mode'),
('DIVIDE', 'Divide', 'Divide Mode'),
('MULTIPLY_ADD', 'Multiply Add', 'Multiply Add Mode'),
('SINE', 'Sine', 'Sine Mode'),
('COSINE', 'Cosine', 'Cosine Mode'),
('TANGENT', 'Tangent', 'Tangent Mode'),
('ARCSINE', 'Arcsine', 'Arcsine Mode'),
('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
('ARCTAN2', 'Arctan2', 'Arctan2 Mode'),
('SINH', 'Hyperbolic Sine', 'Hyperbolic Sine Mode'),
('COSH', 'Hyperbolic Cosine', 'Hyperbolic Cosine Mode'),
('TANH', 'Hyperbolic Tangent', 'Hyperbolic Tangent Mode'),
('POWER', 'Power', 'Power Mode'),
('LOGARITHM', 'Logarithm', 'Logarithm Mode'),
('SQRT', 'Square Root', 'Square Root Mode'),
('INVERSE_SQRT', 'Inverse Square Root', 'Inverse Square Root Mode'),
('EXPONENT', 'Exponent', 'Exponent Mode'),
('MINIMUM', 'Minimum', 'Minimum Mode'),
('MAXIMUM', 'Maximum', 'Maximum Mode'),
('LESS_THAN', 'Less Than', 'Less Than Mode'),
('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
('SIGN', 'Sign', 'Sign Mode'),
('COMPARE', 'Compare', 'Compare Mode'),
('SMOOTH_MIN', 'Smooth Minimum', 'Smooth Minimum Mode'),
('SMOOTH_MAX', 'Smooth Maximum', 'Smooth Maximum Mode'),
('FRACT', 'Fraction', 'Fraction Mode'),
('MODULO', 'Modulo', 'Modulo Mode'),
('SNAP', 'Snap', 'Snap Mode'),
('WRAP', 'Wrap', 'Wrap Mode'),
('PINGPONG', 'Pingpong', 'Pingpong Mode'),
('ABSOLUTE', 'Absolute', 'Absolute Mode'),
('ROUND', 'Round', 'Round Mode'),
('FLOOR', 'Floor', 'Floor Mode'),
('CEIL', 'Ceil', 'Ceil Mode'),
('TRUNCATE', 'Truncate', 'Truncate Mode'),
('RADIANS', 'To Radians', 'To Radians Mode'),
('DEGREES', 'To Degrees', 'To Degrees Mode'),
# Operations used by the geometry boolean node and join geometry node
geo_combine_operations = [
('JOIN', 'Join Geometry', 'Join Geometry Mode'),
('INTERSECT', 'Intersect', 'Intersect Mode'),
('UNION', 'Union', 'Union Mode'),
('DIFFERENCE', 'Difference', 'Difference Mode'),
]
# in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
# used list, not tuple for easy merging with other lists.
navs = [
('CURRENT', 'Current', 'Leave at current state'),
('NEXT', 'Next', 'Next blend type/operation'),
('PREV', 'Prev', 'Previous blend type/operation'),
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
]
draw_color_sets = {
"red_white": (
(1.0, 1.0, 1.0, 0.7),
(1.0, 0.0, 0.0, 0.7),
(0.8, 0.2, 0.2, 1.0)
),
"green": (
(0.0, 0.0, 0.0, 1.0),
(0.38, 0.77, 0.38, 1.0),
(0.38, 0.77, 0.38, 1.0)
),
"yellow": (
(0.0, 0.0, 0.0, 1.0),
(0.77, 0.77, 0.16, 1.0),
(0.77, 0.77, 0.16, 1.0)
),
"purple": (
(0.0, 0.0, 0.0, 1.0),
(0.38, 0.38, 0.77, 1.0),
(0.38, 0.38, 0.77, 1.0)
),
"grey": (
(0.0, 0.0, 0.0, 1.0),
(0.63, 0.63, 0.63, 1.0),
(0.63, 0.63, 0.63, 1.0)
),
"black": (
(1.0, 1.0, 1.0, 0.7),
(0.0, 0.0, 0.0, 0.7),
(0.2, 0.2, 0.2, 1.0)
viewer_socket_name = "tmp_viewer"
def get_nodes_from_category(category_name, context):
for category in node_categories_iter(context):
if category.name == category_name:
return sorted(category.items(context), key=lambda node: node.label)
def is_visible_socket(socket):
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
def nice_hotkey_name(punc):
# convert the ugly string name into the actual character
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
nice_name = {
'LEFTMOUSE': "LMB",
'MIDDLEMOUSE': "MMB",
'RIGHTMOUSE': "RMB",
'WHEELUPMOUSE': "Wheel Up",
'WHEELDOWNMOUSE': "Wheel Down",
'WHEELINMOUSE': "Wheel In",
'WHEELOUTMOUSE': "Wheel Out",
'ZERO': "0",
'ONE': "1",
'TWO': "2",
'THREE': "3",
'FOUR': "4",
'FIVE': "5",
'SIX': "6",
'SEVEN': "7",
'EIGHT': "8",
'NINE': "9",
'OSKEY': "Super",
'RET': "Enter",
'LINE_FEED': "Enter",
'SEMI_COLON': ";",
'PERIOD': ".",
'COMMA': ",",
'QUOTE': '"',
'MINUS': "-",
'SLASH': "/",
'BACK_SLASH': "\\",
'EQUAL': "=",
'NUMPAD_1': "Numpad 1",
'NUMPAD_2': "Numpad 2",
'NUMPAD_3': "Numpad 3",
'NUMPAD_4': "Numpad 4",
'NUMPAD_5': "Numpad 5",
'NUMPAD_6': "Numpad 6",
'NUMPAD_7': "Numpad 7",
'NUMPAD_8': "Numpad 8",
'NUMPAD_9': "Numpad 9",
'NUMPAD_0': "Numpad 0",
'NUMPAD_PERIOD': "Numpad .",
'NUMPAD_SLASH': "Numpad /",
'NUMPAD_ASTERIX': "Numpad *",
'NUMPAD_MINUS': "Numpad -",
'NUMPAD_ENTER': "Numpad Enter",
'NUMPAD_PLUS': "Numpad +",
}
try:
return nice_name[punc]
except KeyError:
return punc.replace("_", " ").title()
def force_update(context):
context.space_data.node_tree.update_tag()
prefs = bpy.context.preferences.system
Brecht Van Lommel
committed
return prefs.dpi * prefs.pixel_size / 72
def node_mid_pt(node, axis):
if axis == 'x':
d = node.location.x + (node.dimensions.x / 2)
elif axis == 'y':
d = node.location.y - (node.dimensions.y / 2)
else:
d = 0
return d
def autolink(node1, node2, links):
link_made = False
available_inputs = [inp for inp in node2.inputs if inp.enabled]
available_outputs = [outp for outp in node1.outputs if outp.enabled]
for outp in available_outputs:
for inp in available_inputs:
if not inp.is_linked and inp.name == outp.name:
link_made = True
links.new(outp, inp)
return True
for outp in available_outputs:
for inp in available_inputs:
if not inp.is_linked and inp.type == outp.type:
link_made = True
links.new(outp, inp)
return True
# force some connection even if the type doesn't match
if available_outputs:
for inp in available_inputs:
if not inp.is_linked:
link_made = True
links.new(available_outputs[0], inp)
return True
# even if no sockets are open, force one of matching type
for outp in available_outputs:
for inp in available_inputs:
if inp.type == outp.type:
link_made = True
links.new(outp, inp)
return True
# do something!
for outp in available_outputs:
for inp in available_inputs:
link_made = True
links.new(outp, inp)
return True
print("Could not make a link from " + node1.name + " to " + node2.name)
return link_made
def abs_node_location(node):
abs_location = node.location
if node.parent is None:
return abs_location
return abs_location + abs_node_location(node.parent)
def node_at_pos(nodes, context, event):
nodes_under_mouse = []
target_node = None
store_mouse_cursor(context, event)
x, y = context.space_data.cursor_location
# Make a list of each corner (and middle of border) for each node.
# Will be sorted to find nearest point and thus nearest node
node_points_with_dist = []
for node in nodes:
skipnode = False
if node.type != 'FRAME': # no point trying to link to a frame node
dimx = node.dimensions.x/dpifac()
dimy = node.dimensions.y/dpifac()
locx, locy = abs_node_location(node)
node_points_with_dist.append([node, hypot(x - locx, y - locy)]) # Top Left
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - locy)]) # Top Right
node_points_with_dist.append([node, hypot(x - locx, y - (locy - dimy))]) # Bottom Left
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - dimy))]) # Bottom Right
node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - locy)]) # Mid Top
node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - (locy - dimy))]) # Mid Bottom
node_points_with_dist.append([node, hypot(x - locx, y - (locy - (dimy / 2)))]) # Mid Left
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - (dimy / 2)))]) # Mid Right
nearest_node = sorted(node_points_with_dist, key=lambda k: k[1])[0][0]
for node in nodes:
if node.type != 'FRAME' and skipnode == False:
locx, locy = abs_node_location(node)
dimx = node.dimensions.x/dpifac()
dimy = node.dimensions.y/dpifac()
if (locx <= x <= locx + dimx) and \
(locy - dimy <= y <= locy):
nodes_under_mouse.append(node)
if len(nodes_under_mouse) == 1:
if nodes_under_mouse[0] != nearest_node:
target_node = nodes_under_mouse[0] # use the node under the mouse if there is one and only one
else:
target_node = nearest_node # else use the nearest node
return target_node
def store_mouse_cursor(context, event):
space = context.space_data
v2d = context.region.view2d
tree = space.edit_tree
# convert mouse position to the View2D for later node placement
if context.region.type == 'WINDOW':
space.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
else:
space.cursor_location = tree.view_center
def draw_line(x1, y1, x2, y2, size, colour=(1.0, 1.0, 1.0, 0.7)):
shader = gpu.shader.from_builtin('2D_SMOOTH_COLOR')
vertices = ((x1, y1), (x2, y2))
vertex_colors = ((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),
colour)
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices, "color": vertex_colors})
shader.bind()
batch.draw(shader)
def draw_circle_2d_filled(shader, mx, my, radius, colour=(1.0, 1.0, 1.0, 0.7)):
radius = radius * dpifac()
sides = 12
vertices = [(radius * cos(i * 2 * pi / sides) + mx,
radius * sin(i * 2 * pi / sides) + my)
for i in range(sides + 1)]
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
def draw_rounded_node_border(shader, node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
area_width = bpy.context.area.width - (16*dpifac()) - 1
bottom_bar = (16*dpifac()) + 1
nlocx, nlocy = abs_node_location(node)
nlocx = (nlocx+1)*dpifac()
nlocy = (nlocy+1)*dpifac()
ndimx = node.dimensions.x
ndimy = node.dimensions.y
if node.hide:
nlocx += -1
nlocy += 5
if node.type == 'REROUTE':
#nlocx += 1
nlocy -= 1
ndimx = 0
ndimy = 0
radius += 6
# Top left corner
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
for i in range(sides+1):
if (4<=i<=8):
if my > bottom_bar and mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
for i in range(sides+1):
if (0<=i<=4):
if my > bottom_bar and mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
for i in range(sides+1):
if (8<=i<=12):
if my > bottom_bar and mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
for i in range(sides+1):
if (12<=i<=16):
if my > bottom_bar and mx < area_width:
cosine = radius * cos(i * 2 * pi / sides) + mx
sine = radius * sin(i * 2 * pi / sides) + my
vertices.append((cosine,sine))
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
# prepare drawing all edges in one batch
vertices = []
indices = []
id_last = 0
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
if m1x < area_width and m2x < area_width:
vertices.extend([(m2x-radius,m2y), (m2x,m2y),
(m1x,m1y), (m1x-radius,m1y)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
id_last += 4
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
m1x = min(m1x, area_width)
m2x = min(m2x, area_width)
if m1y > bottom_bar and m2y > bottom_bar:
vertices.extend([(m1x,m1y), (m2x,m1y),
(m2x,m1y+radius), (m1x,m1y+radius)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
id_last += 4
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
m1y = max(m1y, bottom_bar)
m2y = max(m2y, bottom_bar)
if m1x < area_width and m2x < area_width:
vertices.extend([(m1x,m2y), (m1x+radius,m2y),
(m1x+radius,m1y), (m1x,m1y)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
id_last += 4
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy-ndimy, clip=False)
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy-ndimy, clip=False)
m1x = min(m1x, area_width)
m2x = min(m2x, area_width)
if m1y > bottom_bar and m2y > bottom_bar:
vertices.extend([(m1x,m2y), (m2x,m2y),
(m2x,m1y-radius), (m1x,m1y-radius)])
indices.extend([(id_last, id_last+1, id_last+3),
(id_last+3, id_last+1, id_last+2)])
# now draw all edges in one batch
if len(vertices) != 0:
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
shader.bind()
shader.uniform_float("color", colour)
batch.draw(shader)
def draw_callback_nodeoutline(self, context, mode):
if self.mouse_path:
bgl.glLineWidth(1)
bgl.glEnable(bgl.GL_BLEND)
bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
nodes, links = get_nodes_links(context)
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
col_outer = (1.0, 0.2, 0.2, 0.4)
col_inner = (0.0, 0.0, 0.0, 0.5)
col_circle_inner = (0.3, 0.05, 0.05, 1.0)
col_outer = (0.4, 0.6, 1.0, 0.4)
col_inner = (0.0, 0.0, 0.0, 0.5)
col_circle_inner = (0.08, 0.15, .3, 1.0)
col_outer = (0.2, 1.0, 0.2, 0.4)
col_inner = (0.0, 0.0, 0.0, 0.5)
col_circle_inner = (0.05, 0.3, 0.05, 1.0)
m1x = self.mouse_path[0][0]
m1y = self.mouse_path[0][1]
m2x = self.mouse_path[-1][0]
m2y = self.mouse_path[-1][1]
n1 = nodes[context.scene.NWLazySource]
n2 = nodes[context.scene.NWLazyTarget]
col_outer = (0.4, 0.4, 0.4, 0.4)
col_inner = (0.0, 0.0, 0.0, 0.5)
col_circle_inner = (0.2, 0.2, 0.2, 1.0)
draw_rounded_node_border(shader, n1, radius=6, colour=col_outer) # outline
draw_rounded_node_border(shader, n1, radius=5, colour=col_inner) # inner
draw_rounded_node_border(shader, n2, radius=6, colour=col_outer) # outline
draw_rounded_node_border(shader, n2, radius=5, colour=col_inner) # inner
draw_line(m1x, m1y, m2x, m2y, 5, col_outer) # line outline
draw_line(m1x, m1y, m2x, m2y, 2, col_inner) # line inner
# circle outline
draw_circle_2d_filled(shader, m1x, m1y, 7, col_outer)
draw_circle_2d_filled(shader, m2x, m2y, 7, col_outer)