Skip to content
Snippets Groups Projects
node_wrangler.py 197 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 Ctrl-Space",
    
        "description": "Various tools to enhance and speed up node-based workflow",
        "warning": "",
        "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
                    "Scripts/Nodes/Nodes_Efficiency_Tools",
        "category": "Node",
    
    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
    
    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_internal, in_cycles)
    
    RL_entry = namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_internal', 'in_cycles'])
    
        RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
        RL_entry('use_pass_color', 'Color', 'Color', True, False),
        RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
        RL_entry('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
        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', True, False),
        RL_entry('use_pass_environment', 'Environment', 'Env', True, 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', True, False),
        RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
        RL_entry('use_pass_mist', 'Mist', 'Mist', True, False),
        RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
        RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
        RL_entry('use_pass_reflection', 'Reflect', 'Reflect', True, False),
        RL_entry('use_pass_refraction', 'Refract', 'Refract', True, False),
        RL_entry('use_pass_shadow', 'Shadow', 'Shadow', True, True),
        RL_entry('use_pass_specular', 'Specular', 'Spec', True, False),
        RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', False, True),
        RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', False, 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', True, 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.
    shaders_input_nodes_props = (
        ('ShaderNodeTexCoord', 'TEX_COORD', 'Texture Coordinate'),
        ('ShaderNodeAttribute', 'ATTRIBUTE', 'Attribute'),
        ('ShaderNodeLightPath', 'LIGHT_PATH', 'Light Path'),
        ('ShaderNodeFresnel', 'FRESNEL', 'Fresnel'),
        ('ShaderNodeLayerWeight', 'LAYER_WEIGHT', 'Layer Weight'),
        ('ShaderNodeRGB', 'RGB', 'RGB'),
        ('ShaderNodeValue', 'VALUE', 'Value'),
        ('ShaderNodeTangent', 'TANGENT', 'Tangent'),
        ('ShaderNodeNewGeometry', 'NEW_GEOMETRY', 'Geometry'),
        ('ShaderNodeWireframe', 'WIREFRAME', 'Wireframe'),
        ('ShaderNodeObjectInfo', 'OBJECT_INFO', 'Object Info'),
        ('ShaderNodeHairInfo', 'HAIR_INFO', 'Hair Info'),
        ('ShaderNodeParticleInfo', 'PARTICLE_INFO', 'Particle Info'),
        ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
    
        ('ShaderNodeUVMap', 'UVMAP', 'UV Map'),
    
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    shaders_output_nodes_props = (
        ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
        ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp 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.
    shaders_shader_nodes_props = (
        ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
        ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
        ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
        ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
        ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
        ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
        ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
        ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
        ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
        ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
        ('ShaderNodeBsdfToon', 'BSDF_TOON', 'Toon BSDF'),
        ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
        ('ShaderNodeEmission', 'EMISSION', 'Emission'),
    
        ('ShaderNodeBsdfHair', 'BSDF_HAIR', 'Hair BSDF'),
    
        ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
        ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
        ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
    
        ('ShaderNodeVolumeAbsorption', 'VOLUME_ABSORPTION', 'Volume Absorption'),
        ('ShaderNodeVolumeScatter', 'VOLUME_SCATTER', 'Volume Scatter'),
    
        ('ShaderNodeBsdfPrincipled', 'BSDF_PRINCIPLED', 'Principled BSDF'),
    
    )
    # (rna_type.identifier, type, rna_type.name)
    # 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'),
        ('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'),
    
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    shaders_color_nodes_props = (
        ('ShaderNodeMixRGB', 'MIX_RGB', 'MixRGB'),
        ('ShaderNodeRGBCurve', 'CURVE_RGB', 'RGB Curves'),
        ('ShaderNodeInvert', 'INVERT', 'Invert'),
        ('ShaderNodeLightFalloff', 'LIGHT_FALLOFF', 'Light Falloff'),
        ('ShaderNodeHueSaturation', 'HUE_SAT', 'Hue/Saturation'),
        ('ShaderNodeGamma', 'GAMMA', 'Gamma'),
        ('ShaderNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright Contrast'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    shaders_vector_nodes_props = (
        ('ShaderNodeMapping', 'MAPPING', 'Mapping'),
        ('ShaderNodeBump', 'BUMP', 'Bump'),
        ('ShaderNodeNormalMap', 'NORMAL_MAP', 'Normal Map'),
        ('ShaderNodeNormal', 'NORMAL', 'Normal'),
        ('ShaderNodeVectorCurve', 'CURVE_VEC', 'Vector Curves'),
        ('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.
    shaders_converter_nodes_props = (
        ('ShaderNodeMath', 'MATH', 'Math'),
        ('ShaderNodeValToRGB', 'VALTORGB', 'ColorRamp'),
        ('ShaderNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
        ('ShaderNodeVectorMath', 'VECT_MATH', 'Vector Math'),
        ('ShaderNodeSeparateRGB', 'SEPRGB', 'Separate RGB'),
        ('ShaderNodeCombineRGB', 'COMBRGB', 'Combine RGB'),
    
        ('ShaderNodeSeparateXYZ', 'SEPXYZ', 'Separate XYZ'),
        ('ShaderNodeCombineXYZ', 'COMBXYZ', 'Combine XYZ'),
    
        ('ShaderNodeSeparateHSV', 'SEPHSV', 'Separate HSV'),
        ('ShaderNodeCombineHSV', 'COMBHSV', 'Combine HSV'),
        ('ShaderNodeWavelength', 'WAVELENGTH', 'Wavelength'),
        ('ShaderNodeBlackbody', 'BLACKBODY', 'Blackbody'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    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.
    compo_input_nodes_props = (
        ('CompositorNodeRLayers', 'R_LAYERS', 'Render Layers'),
        ('CompositorNodeImage', 'IMAGE', 'Image'),
        ('CompositorNodeMovieClip', 'MOVIECLIP', 'Movie Clip'),
        ('CompositorNodeMask', 'MASK', 'Mask'),
        ('CompositorNodeRGB', 'RGB', 'RGB'),
        ('CompositorNodeValue', 'VALUE', 'Value'),
        ('CompositorNodeTexture', 'TEXTURE', 'Texture'),
        ('CompositorNodeBokehImage', 'BOKEHIMAGE', 'Bokeh Image'),
        ('CompositorNodeTime', 'TIME', 'Time'),
        ('CompositorNodeTrackPos', 'TRACKPOS', 'Track Position'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    compo_output_nodes_props = (
        ('CompositorNodeComposite', 'COMPOSITE', 'Composite'),
        ('CompositorNodeViewer', 'VIEWER', 'Viewer'),
        ('CompositorNodeSplitViewer', 'SPLITVIEWER', 'Split Viewer'),
        ('CompositorNodeOutputFile', 'OUTPUT_FILE', 'File Output'),
        ('CompositorNodeLevels', 'LEVELS', 'Levels'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    compo_color_nodes_props = (
        ('CompositorNodeMixRGB', 'MIX_RGB', 'Mix'),
        ('CompositorNodeAlphaOver', 'ALPHAOVER', 'Alpha Over'),
        ('CompositorNodeInvert', 'INVERT', 'Invert'),
        ('CompositorNodeCurveRGB', 'CURVE_RGB', 'RGB Curves'),
        ('CompositorNodeHueSat', 'HUE_SAT', 'Hue Saturation Value'),
        ('CompositorNodeColorBalance', 'COLORBALANCE', 'Color Balance'),
        ('CompositorNodeHueCorrect', 'HUECORRECT', 'Hue Correct'),
        ('CompositorNodeBrightContrast', 'BRIGHTCONTRAST', 'Bright/Contrast'),
        ('CompositorNodeGamma', 'GAMMA', 'Gamma'),
        ('CompositorNodeColorCorrection', 'COLORCORRECTION', 'Color Correction'),
        ('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.
    compo_converter_nodes_props = (
        ('CompositorNodeMath', 'MATH', 'Math'),
        ('CompositorNodeValToRGB', 'VALTORGB', 'ColorRamp'),
        ('CompositorNodeSetAlpha', 'SETALPHA', 'Set Alpha'),
        ('CompositorNodePremulKey', 'PREMULKEY', 'Alpha Convert'),
        ('CompositorNodeIDMask', 'ID_MASK', 'ID Mask'),
        ('CompositorNodeRGBToBW', 'RGBTOBW', 'RGB to BW'),
        ('CompositorNodeSepRGBA', 'SEPRGBA', 'Separate RGBA'),
        ('CompositorNodeCombRGBA', 'COMBRGBA', 'Combine RGBA'),
        ('CompositorNodeSepHSVA', 'SEPHSVA', 'Separate HSVA'),
        ('CompositorNodeCombHSVA', 'COMBHSVA', 'Combine HSVA'),
        ('CompositorNodeSepYUVA', 'SEPYUVA', 'Separate YUVA'),
        ('CompositorNodeCombYUVA', 'COMBYUVA', 'Combine YUVA'),
        ('CompositorNodeSepYCCA', 'SEPYCCA', 'Separate YCbCrA'),
        ('CompositorNodeCombYCCA', 'COMBYCCA', 'Combine YCbCrA'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    compo_filter_nodes_props = (
        ('CompositorNodeBlur', 'BLUR', 'Blur'),
        ('CompositorNodeBilateralblur', 'BILATERALBLUR', 'Bilateral Blur'),
        ('CompositorNodeDilateErode', 'DILATEERODE', 'Dilate/Erode'),
        ('CompositorNodeDespeckle', 'DESPECKLE', 'Despeckle'),
        ('CompositorNodeFilter', 'FILTER', 'Filter'),
        ('CompositorNodeBokehBlur', 'BOKEHBLUR', 'Bokeh Blur'),
        ('CompositorNodeVecBlur', 'VECBLUR', 'Vector Blur'),
        ('CompositorNodeDefocus', 'DEFOCUS', 'Defocus'),
        ('CompositorNodeGlare', 'GLARE', 'Glare'),
        ('CompositorNodeInpaint', 'INPAINT', 'Inpaint'),
        ('CompositorNodeDBlur', 'DBLUR', 'Directional Blur'),
        ('CompositorNodePixelate', 'PIXELATE', 'Pixelate'),
    
        ('CompositorNodeSunBeams', 'SUNBEAMS', 'Sun Beams'),
    
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    compo_vector_nodes_props = (
        ('CompositorNodeNormal', 'NORMAL', 'Normal'),
        ('CompositorNodeMapValue', 'MAP_VALUE', 'Map Value'),
        ('CompositorNodeMapRange', 'MAP_RANGE', 'Map Range'),
        ('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.
    compo_matte_nodes_props = (
        ('CompositorNodeKeying', 'KEYING', 'Keying'),
        ('CompositorNodeKeyingScreen', 'KEYINGSCREEN', 'Keying Screen'),
        ('CompositorNodeChannelMatte', 'CHANNEL_MATTE', 'Channel Key'),
        ('CompositorNodeColorSpill', 'COLOR_SPILL', 'Color Spill'),
        ('CompositorNodeBoxMask', 'BOXMASK', 'Box Mask'),
        ('CompositorNodeEllipseMask', 'ELLIPSEMASK', 'Ellipse Mask'),
        ('CompositorNodeLumaMatte', 'LUMA_MATTE', 'Luminance Key'),
        ('CompositorNodeDiffMatte', 'DIFF_MATTE', 'Difference Key'),
        ('CompositorNodeDistanceMatte', 'DISTANCE_MATTE', 'Distance Key'),
        ('CompositorNodeChromaMatte', 'CHROMA_MATTE', 'Chroma Key'),
        ('CompositorNodeColorMatte', 'COLOR_MATTE', 'Color Key'),
        ('CompositorNodeDoubleEdgeMask', 'DOUBLEEDGEMASK', 'Double Edge Mask'),
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    compo_distort_nodes_props = (
        ('CompositorNodeScale', 'SCALE', 'Scale'),
        ('CompositorNodeLensdist', 'LENSDIST', 'Lens Distortion'),
        ('CompositorNodeMovieDistortion', 'MOVIEDISTORTION', 'Movie Distortion'),
        ('CompositorNodeTranslate', 'TRANSLATE', 'Translate'),
        ('CompositorNodeRotate', 'ROTATE', 'Rotate'),
        ('CompositorNodeFlip', 'FLIP', 'Flip'),
        ('CompositorNodeCrop', 'CROP', 'Crop'),
        ('CompositorNodeDisplace', 'DISPLACE', 'Displace'),
        ('CompositorNodeMapUV', 'MAP_UV', 'Map UV'),
        ('CompositorNodeTransform', 'TRANSFORM', 'Transform'),
        ('CompositorNodeStabilize', 'STABILIZE2D', 'Stabilize 2D'),
        ('CompositorNodePlaneTrackDeform', 'PLANETRACKDEFORM', 'Plane Track Deform'),
    
        ('CompositorNodeCornerPin', 'CORNERPIN', 'Corner Pin'),
    
    )
    # (rna_type.identifier, type, rna_type.name)
    # Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
    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'),
        ('ShaderNodeLampData', 'LAMP', 'Lamp 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'),
        ('SINE', 'Sine', 'Sine Mode'),
        ('COSINE', 'Cosine', 'Cosine Mode'),
        ('TANGENT', 'Tangent', 'Tangent Mode'),
        ('ARCSINE', 'Arcsine', 'Arcsine Mode'),
        ('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
        ('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
        ('POWER', 'Power', 'Power Mode'),
        ('LOGARITHM', 'Logatithm', 'Logarithm Mode'),
        ('MINIMUM', 'Minimum', 'Minimum Mode'),
        ('MAXIMUM', 'Maximum', 'Maximum Mode'),
        ('ROUND', 'Round', 'Round Mode'),
    
        ('LESS_THAN', 'Less Than', 'Less Than Mode'),
    
        ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
    
        ('MODULO', 'Modulo', 'Modulo Mode'),
        ('ABSOLUTE', 'Absolute', 'Absolute 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 nice_hotkey_name(punc):
        # convert the ugly string name into the actual character
        pairs = (
            ('LEFTMOUSE', "LMB"),
            ('MIDDLEMOUSE', "MMB"),
            ('RIGHTMOUSE', "RMB"),
            ('SELECTMOUSE', "Select"),
            ('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.user_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]):
    
        shademodel_state = bgl.Buffer(bgl.GL_INT, 1)
        bgl.glGetIntegerv(bgl.GL_SHADE_MODEL, shademodel_state)
    
    
        bgl.glLineWidth(size * dpifac())
    
        bgl.glShadeModel(bgl.GL_SMOOTH)
    
        bgl.glEnable(bgl.GL_LINE_SMOOTH)
    
    
        bgl.glBegin(bgl.GL_LINE_STRIP)
        try:
    
            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)
    
            bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
    
            bgl.glVertex2f(x2, y2)
        except:
            pass
        bgl.glEnd()
    
    
        bgl.glShadeModel(shademodel_state[0])
    
        bgl.glDisable(bgl.GL_LINE_SMOOTH)
    
    
    
    def draw_circle(mx, my, radius, colour=[1.0, 1.0, 1.0, 0.7]):
    
        bgl.glEnable(bgl.GL_LINE_SMOOTH)
    
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
        bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
    
        radius = radius * dpifac()
        sides = 12
    
        for i in range(sides + 1):
            cosine = radius * cos(i * 2 * pi / sides) + mx
            sine = radius * sin(i * 2 * pi / sides) + my
            bgl.glVertex2f(cosine, sine)
        bgl.glEnd()
    
        bgl.glDisable(bgl.GL_LINE_SMOOTH)
    
    def draw_rounded_node_border(node, radius=8, colour=[1.0, 1.0, 1.0, 0.7]):
        bgl.glEnable(bgl.GL_BLEND)
    
        bgl.glEnable(bgl.GL_LINE_SMOOTH)
    
    
        area_width = bpy.context.area.width - (16*dpifac()) - 1
        bottom_bar = (16*dpifac()) + 1
    
        radius = radius*dpifac()
    
        bgl.glColor4f(colour[0], colour[1], colour[2], colour[3])
    
        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
    
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
    
        bgl.glVertex2f(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
                    bgl.glVertex2f(cosine, sine)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Top right corner
    
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
    
        bgl.glVertex2f(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
                    bgl.glVertex2f(cosine, sine)
        bgl.glEnd()
    
    Greg Zaal's avatar
    Greg Zaal committed
    
        # Bottom left corner
    
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
    
        bgl.glVertex2f(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
                    bgl.glVertex2f(cosine, sine)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Bottom right corner
    
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
    
        mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
    
        bgl.glVertex2f(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
                    bgl.glVertex2f(cosine, sine)
        bgl.glEnd()
    
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Left edge
    
        bgl.glBegin(bgl.GL_QUADS)
    
        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)
        m1y = max(m1y, bottom_bar)
        m2y = max(m2y, bottom_bar)
        if m1x < area_width and m2x < area_width:
    
            bgl.glVertex2f(m2x-radius,m2y)  # draw order is important, start with bottom left and go anti-clockwise
            bgl.glVertex2f(m2x,m2y)
            bgl.glVertex2f(m1x,m1y)
            bgl.glVertex2f(m1x-radius,m1y)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Top edge
    
        bgl.glBegin(bgl.GL_QUADS)
    
        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:
    
            bgl.glVertex2f(m1x,m2y)  # draw order is important, start with bottom left and go anti-clockwise
            bgl.glVertex2f(m2x,m2y)
            bgl.glVertex2f(m2x,m1y+radius)
            bgl.glVertex2f(m1x,m1y+radius)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Right edge
    
        bgl.glBegin(bgl.GL_QUADS)
    
        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:
    
            bgl.glVertex2f(m2x,m2y)  # draw order is important, start with bottom left and go anti-clockwise
            bgl.glVertex2f(m2x+radius,m2y)
            bgl.glVertex2f(m1x+radius,m1y)
            bgl.glVertex2f(m1x,m1y)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
        # Bottom edge
    
        bgl.glBegin(bgl.GL_QUADS)
    
        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:
    
            bgl.glVertex2f(m1x,m2y)  # draw order is important, start with bottom left and go anti-clockwise
            bgl.glVertex2f(m2x,m2y)
            bgl.glVertex2f(m2x,m1y-radius)
            bgl.glVertex2f(m1x,m1y-radius)
        bgl.glEnd()
    
    
    Greg Zaal's avatar
    Greg Zaal committed
    
        # Restore defaults
    
        bgl.glDisable(bgl.GL_BLEND)
    
        bgl.glDisable(bgl.GL_LINE_SMOOTH)
    
    def draw_callback_nodeoutline(self, context, mode):
    
            nodes, links = get_nodes_links(context)
    
            bgl.glEnable(bgl.GL_LINE_SMOOTH)
    
            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]
    
    
            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]
    
            if n1 == n2:
                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(n1, radius=6, colour=col_outer)  # outline
            draw_rounded_node_border(n1, radius=5, colour=col_inner)  # inner
            draw_rounded_node_border(n2, radius=6, colour=col_outer)  # outline
            draw_rounded_node_border(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(m1x, m1y, 7, col_outer)
            draw_circle(m2x, m2y, 7, col_outer)
    
            draw_circle(m1x, m1y, 5, col_circle_inner)
            draw_circle(m2x, m2y, 5, col_circle_inner)
    
    
            # restore opengl defaults
            bgl.glLineWidth(1)
            bgl.glDisable(bgl.GL_BLEND)
            bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
    
            bgl.glDisable(bgl.GL_LINE_SMOOTH)
    
    
    def get_nodes_links(context):
    
        tree = context.space_data.node_tree
    
        # Get nodes from currently edited tree.
        # If user is editing a group, space_data.node_tree is still the base level (outside group).
        # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
        # the same as context.active_node, the user is in a group.
        # Check recursively until we find the real active node_tree:
        if tree.nodes.active:
            while tree.nodes.active != context.active_node:
                tree = tree.nodes.active.node_tree
    
        return tree.nodes, tree.links
    
    # Principled prefs
    class NWPrincipledPreferences(bpy.types.PropertyGroup):
        base_color = StringProperty(
            name='Base Color',
            default='diffuse diff albedo base col color',
            description='Naming Components for Base Color maps')
        sss_color = StringProperty(
            name='Subsurface Color',