Skip to content
Snippets Groups Projects
node_wrangler.py 201 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    bl_info = {
    
        "name": "Node Wrangler",
    
        "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
    
        "blender": (2, 80, 0),
    
        "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",
    
    from bpy.types import Operator, Panel, Menu
    
    Campbell Barton's avatar
    Campbell Barton committed
    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
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
    from mathutils import Vector
    
    from math import cos, sin, pi, hypot
    
    from glob import glob
    
    from itertools import chain
    
    from collections import namedtuple
    
    
    #################
    # rl_outputs:
    # list of outputs of Input Render Layer
    # with attributes determinig 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 orde 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 orde 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 orde 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 orde 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 orde so we don't need to sort later.
    
    shaders_color_nodes_props = (
    
        ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
        ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
        ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
    
        ('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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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 orde 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'),
    )
    
    # (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'),
        ('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', 'Logatithm', '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'),
    
    ]
    
    # 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'),
    
    ]
    
    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)
    
    def is_visible_socket(socket):
        return not socket.hide and socket.enabled
    
    def nice_hotkey_name(punc):
        # convert the ugly string name into the actual character
        pairs = (
            ('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 +"),
    
        nice_punc = False
        for (ugly, nice) in pairs:
            if punc == ugly:
                nice_punc = nice
                break
        if not nice_punc:
            nice_punc = punc.replace("_", " ").title()
        return nice_punc
    
    
    
    def force_update(context):
        context.space_data.node_tree.update_tag()
    
        prefs = bpy.context.preferences.system
    
        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
    
    
        for outp in node1.outputs:
            for inp in node2.inputs:
                if not inp.is_linked and inp.name == outp.name:
                    link_made = True
                    links.new(outp, inp)
                    return True
    
    
        for outp in node1.outputs:
            for inp in node2.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
        for outp in node1.outputs:
            for inp in node2.inputs:
                if not inp.is_linked:
                    link_made = True
                    links.new(outp, inp)
                    return True
    
        # even if no sockets are open, force one of matching type
        for outp in node1.outputs:
            for inp in node2.inputs:
                if inp.type == outp.type:
                    link_made = True
                    links.new(outp, inp)
                    return True
    
        # do something!
        for outp in node1.outputs:
            for inp in node2.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 node_at_pos(nodes, context, event):
        nodes_near_mouse = []
        nodes_under_mouse = []
        target_node = None
    
        store_mouse_cursor(context, event)
        x, y = context.space_data.cursor_location
    
        x = x
        y = y
    
        # 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
                locx = node.location.x
                locy = node.location.y
                dimx = node.dimensions.x/dpifac()
                dimy = node.dimensions.y/dpifac()
                if node.parent:
                    locx += node.parent.location.x
                    locy += node.parent.location.y
                    if node.parent.parent:
                        locx += node.parent.parent.location.x
                        locy += node.parent.parent.location.y
                        if node.parent.parent.parent:
                            locx += node.parent.parent.parent.location.x
                            locy += node.parent.parent.parent.location.y
                            if node.parent.parent.parent.parent:
                                # Support three levels or parenting
                                # There's got to be a better way to do this...
                                skipnode = True
                if not skipnode:
    
                    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]
    
            if node.type != 'FRAME' and skipnode == False:
                locx = node.location.x
                locy = node.location.y
                dimx = node.dimensions.x/dpifac()
                dimy = node.dimensions.y/dpifac()
                if node.parent:
                    locx += node.parent.location.x
                    locy += node.parent.location.y
                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
    
            target_node = 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})
    
        bgl.glLineWidth(size * dpifac())
    
    
        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
    
        radius = radius*dpifac()
    
    
        nlocx = (node.location.x+1)*dpifac()
        nlocy = (node.location.y+1)*dpifac()
        ndimx = node.dimensions.x
        ndimy = node.dimensions.y
    
    Greg Zaal's avatar
    Greg Zaal committed
        # This is a stupid way to do this... TODO use while loop
    
        if node.parent:
            nlocx += node.parent.location.x
            nlocy += node.parent.location.y
            if node.parent.parent:
                nlocx += node.parent.parent.location.x
                nlocy += node.parent.parent.location.y
                if node.parent.parent.parent:
                    nlocx += node.parent.parent.parent.location.x
                    nlocy += node.parent.parent.parent.location.y
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        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)
    
        vertices = [(mx,my)]
    
        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)
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Top right corner
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
    
        vertices = [(mx,my)]
    
        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)
    
    Greg Zaal's avatar
    Greg Zaal committed
    
        # Bottom left corner
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
    
        vertices = [(mx,my)]
    
        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)
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Bottom right corner
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
    
        vertices = [(mx,my)]
    
        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
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Left edge
    
        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
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Top edge
    
        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
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Right edge
    
        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
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Bottom edge
    
        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):
    
    
            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')
    
            if mode == "LINK":
    
                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)
    
            elif mode == "LINKMENU":
    
                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)
    
            elif mode == "MIX":
    
                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)