Skip to content
Snippets Groups Projects
node_efficiency_tools.py 69.1 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': "Nodes Efficiency Tools",
        'author': "Bartek Skorupa",
    
        'blender': (2, 6, 6),
        'location': "Node Editor Properties Panel (Ctrl-SPACE)",
        'description': "Nodes Efficiency Tools",
        'warning': "",
        'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Nodes/Nodes_Efficiency_Tools",
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        'tracker_url': "http://projects.blender.org/tracker/index.php?func=detail&aid=33543&group_id=153&atid=469",
    
        'category': "Node",
        }
    
    import bpy
    from bpy.types import Operator, Panel, Menu
    from bpy.props import FloatProperty, EnumProperty, BoolProperty
    
    #################
    # 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_outputs = (
        ('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
        ('use_pass_color', 'Color', 'Color', True, False),
        ('use_pass_combined', 'Image', 'Combined', True, True),
        ('use_pass_diffuse', 'Diffuse', 'Diffuse', True, False),
        ('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
        ('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
        ('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
        ('use_pass_emit', 'Emit', 'Emit', True, False),
        ('use_pass_environment', 'Environment', 'Env', True, False),
        ('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
        ('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
        ('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
        ('use_pass_indirect', 'Indirect', 'Indirect', True, False),
        ('use_pass_material_index', 'IndexMA', 'IndexMA', True, True),
        ('use_pass_mist', 'Mist', 'Mist', True, False),
        ('use_pass_normal', 'Normal', 'Normal', True, True),
        ('use_pass_object_index', 'IndexOB', 'IndexOB', True, True),
        ('use_pass_reflection', 'Reflect', 'Reflect', True, False),
        ('use_pass_refraction', 'Refract', 'Refract', True, False),
        ('use_pass_shadow', 'Shadow', 'Shadow', True, True),
        ('use_pass_specular', 'Specular', 'Spec', True, False),
        ('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
        ('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
        ('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
        ('use_pass_uv', 'UV', 'UV', True, True),
        ('use_pass_vector', 'Speed', 'Vector', True, True),
        ('use_pass_z', 'Z', 'Depth', True, True),
        )
    # list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
    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.
    operations = [
        ('ADD', 'Add', 'Add Mode'),
        ('MULTIPLY', 'Multiply', 'Multiply Mode'),
        ('SUBTRACT', 'Subtract', 'Subtract 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 Thann Mode'),
        ('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
        ]
    # in BatchChangeNodes additional types/operations in a form that can be used as 'items' for EnumProperty.
    navs = [
        ('CURRENT', 'Current', 'Leave at current state'),
        ('NEXT', 'Next', 'Next blend type/operation'),
        ('PREV', 'Prev', 'Previous blend type/operation'),
        ]
    # list of mixing shaders
    
    # list of regular shaders. Entry: (identified, type, name for humans). Will be used in SwapShaders and menus.
    # Keeping mixed case to avoid having to translate entries when adding new nodes in SwapNodes.
    regular_shaders = (
        ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF'),
        ('ShaderNodeBsdfGlossy', 'BSDF_GLOSSY', 'Glossy BSDF'),
        ('ShaderNodeBsdfGlass', 'BSDF_GLASS', 'Glass BSDF'),
        ('ShaderNodeBsdfDiffuse', 'BSDF_DIFFUSE', 'Diffuse BSDF'),
    
        ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
    
        ('ShaderNodeEmission', 'EMISSION', 'Emission'),
        ('ShaderNodeBsdfVelvet', 'BSDF_VELVET', 'Velvet BSDF'),
        ('ShaderNodeBsdfTranslucent', 'BSDF_TRANSLUCENT', 'Translucent BSDF'),
        ('ShaderNodeAmbientOcclusion', 'AMBIENT_OCCLUSION', 'Ambient Occlusion'),
        ('ShaderNodeBackground', 'BACKGROUND', 'Background'),
        ('ShaderNodeBsdfRefraction', 'BSDF_REFRACTION', 'Refraction BSDF'),
        ('ShaderNodeBsdfAnisotropic', 'BSDF_ANISOTROPIC', 'Anisotropic BSDF'),
        ('ShaderNodeHoldout', 'HOLDOUT', 'Holdout'),
        )
    
    merge_shaders = (
        ('ShaderNodeAddShader', 'ADD_SHADER', 'Add Shader'),
        ('ShaderNodeMixShader', 'MIX_SHADER', 'Mix Shader'),
        )
    
    
    def get_nodes_links(context):
        space = context.space_data
        tree = space.node_tree
        nodes = tree.nodes
        links = tree.links
        active = nodes.active
        context_active = context.active_node
        # check if we are working on regular node tree or node group is currently edited.
        # if group is edited - active node of space_tree is the group
        # if context.active_node != space active node - it means that the group is being edited.
        # in such case we set "nodes" to be nodes of this group, "links" to be links of this group
        # if context.active_node == space.active_node it means that we are not currently editing group
        is_main_tree = True
        if active:
            is_main_tree = context_active == active
        if not is_main_tree:  # if group is currently edited
            tree = active.node_tree
            nodes = tree.nodes
            links = tree.links
    
        return nodes, links
    
    
    class NodeToolBase:
        @classmethod
        def poll(cls, context):
            space = context.space_data
            return space.type == 'NODE_EDITOR' and space.node_tree is not None
    
    
    class MergeNodes(Operator, NodeToolBase):
        bl_idname = "node.merge_nodes"
        bl_label = "Merge Nodes"
        bl_description = "Merge Selected Nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        mode = EnumProperty(
                name="mode",
                description="All possible blend types and math operations",
                items=blend_types + [op for op in operations if op not in blend_types],
                )
        merge_type = EnumProperty(
                name="merge type",
                description="Type of Merge to be used",
                items=(
                    ('AUTO', 'Auto', 'Automatic Output Type Detection'),
                    ('SHADER', 'Shader', 'Merge using ADD or MIX Shader'),
                    ('MIX', 'Mix Node', 'Merge using Mix Nodes'),
                    ('MATH', 'Math Node', 'Merge using Math Nodes'),
                    ),
                )
    
        def execute(self, context):
            tree_type = context.space_data.node_tree.type
            if tree_type == 'COMPOSITING':
                node_type = 'CompositorNode'
            elif tree_type == 'SHADER':
                node_type = 'ShaderNode'
            nodes, links = get_nodes_links(context)
            mode = self.mode
            merge_type = self.merge_type
            selected_mix = []  # entry = [index, loc]
            selected_shader = []  # entry = [index, loc]
            selected_math = []  # entry = [index, loc]
    
            for i, node in enumerate(nodes):
                if node.select and node.outputs:
                    if merge_type == 'AUTO':
                        for (type, types_list, dst) in (
    
                                ('SHADER', merge_shaders_types, selected_shader),
    
                                ('RGBA', [t[0] for t in blend_types], selected_mix),
                                ('VALUE', [t[0] for t in operations], selected_math),
                                ):
                            output_type = node.outputs[0].type
                            valid_mode = mode in types_list
                            # When mode is 'MIX' use mix node for both 'RGBA' and 'VALUE' output types.
                            # Cheat that output type is 'RGBA',
                            # and that 'MIX' exists in math operations list.
                            # This way when selected_mix list is analyzed:
                            # Node data will be appended even though it doesn't meet requirements.
                            if output_type != 'SHADER' and mode == 'MIX':
                                output_type = 'RGBA'
                                valid_mode = True
                            if output_type == type and valid_mode:
                                dst.append([i, node.location.x, node.location.y])
                    else:
                        for (type, types_list, dst) in (
    
                                ('SHADER', merge_shaders_types, selected_shader),
    
                                ('MIX', [t[0] for t in blend_types], selected_mix),
                                ('MATH', [t[0] for t in operations], selected_math),
                                ):
                            if merge_type == type and mode in types_list:
                                dst.append([i, node.location.x, node.location.y])
            # When nodes with output kinds 'RGBA' and 'VALUE' are selected at the same time
            # use only 'Mix' nodes for merging.
            # For that we add selected_math list to selected_mix list and clear selected_math.
            if selected_mix and selected_math and merge_type == 'AUTO':
                selected_mix += selected_math
                selected_math = []
    
            for nodes_list in [selected_mix, selected_shader, selected_math]:
                if nodes_list:
                    count_before = len(nodes)
                    # sort list by loc_x - reversed
                    nodes_list.sort(key=lambda k: k[1], reverse=True)
                    # get maximum loc_x
                    loc_x = nodes_list[0][1] + 350.0
                    nodes_list.sort(key=lambda k: k[2], reverse=True)
                    loc_y = nodes_list[len(nodes_list) - 1][2]
                    offset_y = 40.0
                    if nodes_list == selected_shader:
                        offset_y = 150.0
                    the_range = len(nodes_list) - 1
                    do_hide = True
                    if len(nodes_list) == 1:
                        the_range = 1
                        do_hide = False
                    for i in range(the_range):
                        if nodes_list == selected_mix:
                            add_type = node_type + 'MixRGB'
                            add = nodes.new(add_type)
                            add.blend_type = mode
                            add.show_preview = False
                            add.hide = do_hide
                            first = 1
                            second = 2
                            add.width_hidden = 100.0
                        elif nodes_list == selected_math:
                            add_type = node_type + 'Math'
                            add = nodes.new(add_type)
                            add.operation = mode
                            add.hide = do_hide
                            first = 0
                            second = 1
                            add.width_hidden = 100.0
                        elif nodes_list == selected_shader:
                            if mode == 'MIX':
                                add_type = node_type + 'MixShader'
                                add = nodes.new(add_type)
                                first = 1
                                second = 2
                                add.width_hidden = 100.0
                            elif mode == 'ADD':
                                add_type = node_type + 'AddShader'
                                add = nodes.new(add_type)
                                first = 0
                                second = 1
                                add.width_hidden = 100.0
                        add.location = loc_x, loc_y
                        loc_y += offset_y
                        add.select = True
                    count_adds = i + 1
                    count_after = len(nodes)
                    index = count_after - 1
    
                    first_selected = nodes[nodes_list[0][0]]
                    # "last" node has been added as first, so its index is count_before.
                    last_add = nodes[count_before]
                    # add links from last_add to all links 'to_socket' of out links of first selected.
                    for fs_link in first_selected.outputs[0].links:
                        links.new(last_add.outputs[0], fs_link.to_socket)
    
                    # add link from "first" selected and "first" add node
    
                    links.new(first_selected.outputs[0], nodes[count_after - 1].inputs[first])
    
                    # add links between added ADD nodes and between selected and ADD nodes
                    for i in range(count_adds):
                        if i < count_adds - 1:
                            links.new(nodes[index - 1].inputs[first], nodes[index].outputs[0])
                        if len(nodes_list) > 1:
                            links.new(nodes[index].inputs[second], nodes[nodes_list[i + 1][0]].outputs[0])
                        index -= 1
                    # set "last" of added nodes as active
    
    314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
                    for i, x, y in nodes_list:
                        nodes[i].select = False
    
            return {'FINISHED'}
    
    
    class BatchChangeNodes(Operator, NodeToolBase):
        bl_idname = "node.batch_change"
        bl_label = "Batch Change"
        bl_description = "Batch Change Blend Type and Math Operation"
        bl_options = {'REGISTER', 'UNDO'}
    
        blend_type = EnumProperty(
                name="Blend Type",
                items=blend_types + navs,
                )
        operation = EnumProperty(
                name="Operation",
                items=operations + navs,
                )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            blend_type = self.blend_type
            operation = self.operation
            for node in context.selected_nodes:
                if node.type == 'MIX_RGB':
                    if not blend_type in [nav[0] for nav in navs]:
                        node.blend_type = blend_type
                    else:
                        if blend_type == 'NEXT':
                            index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
                            #index = blend_types.index(node.blend_type)
                            if index == len(blend_types) - 1:
                                node.blend_type = blend_types[0][0]
                            else:
                                node.blend_type = blend_types[index + 1][0]
    
                        if blend_type == 'PREV':
                            index = [i for i, entry in enumerate(blend_types) if node.blend_type in entry][0]
                            if index == 0:
                                node.blend_type = blend_types[len(blend_types) - 1][0]
                            else:
                                node.blend_type = blend_types[index - 1][0]
    
                if node.type == 'MATH':
                    if not operation in [nav[0] for nav in navs]:
                        node.operation = operation
                    else:
                        if operation == 'NEXT':
                            index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
                            #index = operations.index(node.operation)
                            if index == len(operations) - 1:
                                node.operation = operations[0][0]
                            else:
                                node.operation = operations[index + 1][0]
    
                        if operation == 'PREV':
                            index = [i for i, entry in enumerate(operations) if node.operation in entry][0]
                            #index = operations.index(node.operation)
                            if index == 0:
                                node.operation = operations[len(operations) - 1][0]
                            else:
                                node.operation = operations[index - 1][0]
    
            return {'FINISHED'}
    
    
    class ChangeMixFactor(Operator, NodeToolBase):
        bl_idname = "node.factor"
        bl_label = "Change Factor"
        bl_description = "Change Factors of Mix Nodes and Mix Shader Nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        # option: Change factor.
        # If option is 1.0 or 0.0 - set to 1.0 or 0.0
        # Else - change factor by option value.
        option = FloatProperty()
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            option = self.option
            selected = []  # entry = index
            for si, node in enumerate(nodes):
                if node.select:
                    if node.type in {'MIX_RGB', 'MIX_SHADER'}:
                        selected.append(si)
    
            for si in selected:
                fac = nodes[si].inputs[0]
                nodes[si].hide = False
                if option in {0.0, 1.0}:
                    fac.default_value = option
                else:
                    fac.default_value += option
    
            return {'FINISHED'}
    
    
    class NodesCopySettings(Operator):
        bl_idname = "node.copy_settings"
        bl_label = "Copy Settings"
        bl_description = "Copy Settings of Active Node to Selected Nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if (space.type == 'NODE_EDITOR' and
                    space.node_tree is not None and
                    context.active_node is not None and
                    context.active_node.type is not 'FRAME'
                    ):
                valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            selected = [n for n in nodes if n.select]
            reselect = []  # duplicated nodes will be selected after execution
            active = nodes.active
            if active.select:
                reselect.append(active)
    
            for node in selected:
                if node.type == active.type and node != active:
                    # duplicate active, relink links as in 'node', append copy to 'reselect', delete node
                    bpy.ops.node.select_all(action='DESELECT')
                    nodes.active = active
                    active.select = True
                    bpy.ops.node.duplicate()
                    copied = nodes.active
                    # Copied active should however inherit some properties from 'node'
                    attributes = (
                        'hide', 'show_preview', 'mute', 'label',
                        'use_custom_color', 'color', 'width', 'width_hidden',
                        )
                    for attr in attributes:
                        setattr(copied, attr, getattr(node, attr))
                    # Handle scenario when 'node' is in frame. 'copied' is in same frame then.
                    if copied.parent:
                        bpy.ops.node.parent_clear()
                    locx = node.location.x
                    locy = node.location.y
                    # get absolute node location
                    parent = node.parent
                    while parent:
                        locx += parent.location.x
                        locy += parent.location.y
                        parent = parent.parent
                    copied.location = [locx, locy]
                    # reconnect links from node to copied
                    for i, input in enumerate(node.inputs):
                        if input.links:
                            link = input.links[0]
                            links.new(link.from_socket, copied.inputs[i])
                    for out, output in enumerate(node.outputs):
                        if output.links:
                            out_links = output.links
                            for link in out_links:
                                links.new(copied.outputs[out], link.to_socket)
                    bpy.ops.node.select_all(action='DESELECT')
                    node.select = True
                    bpy.ops.node.delete()
                    reselect.append(copied)
                else:  # If selected wasn't copied, need to reselect it afterwards.
                    reselect.append(node)
            # clean up
            bpy.ops.node.select_all(action='DESELECT')
            for node in reselect:
                node.select = True
            nodes.active = active
    
            return {'FINISHED'}
    
    
    class NodesCopyLabel(Operator, NodeToolBase):
        bl_idname = "node.copy_label"
        bl_label = "Copy Label"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = EnumProperty(
                name="option",
                description="Source of name of label",
                items=(
                    ('FROM_ACTIVE', 'from active', 'from active node',),
                    ('FROM_NODE', 'from node', 'from node linked to selected node'),
                    ('FROM_SOCKET', 'from socket', 'from socket linked to selected node'),
                    )
                )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            option = self.option
            active = nodes.active
            if option == 'FROM_ACTIVE':
                if active:
                    src_label = active.label
                    for node in [n for n in nodes if n.select and nodes.active != n]:
                        node.label = src_label
            elif option == 'FROM_NODE':
                selected = [n for n in nodes if n.select]
                for node in selected:
                    for input in node.inputs:
                        if input.links:
                            src = input.links[0].from_node
                            node.label = src.label
                            break
            elif option == 'FROM_SOCKET':
                selected = [n for n in nodes if n.select]
                for node in selected:
                    for input in node.inputs:
                        if input.links:
                            src = input.links[0].from_socket
                            node.label = src.name
                            break
    
            return {'FINISHED'}
    
    
    class NodesClearLabel(Operator, NodeToolBase):
        bl_idname = "node.clear_label"
        bl_label = "Clear Label"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = BoolProperty()
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            for node in [n for n in nodes if n.select]:
                node.label = ''
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            if self.option:
                return self.execute(context)
            else:
                return context.window_manager.invoke_confirm(self, event)
    
    
    class NodesAddTextureSetup(Operator):
        bl_idname = "node.add_texture"
        bl_label = "Texture Setup"
        bl_description = "Add Texture Node Setup to Selected Shaders"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.tree_type == 'ShaderNodeTree' and space.node_tree is not None:
                    valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            active = nodes.active
            valid = False
            if active:
                if active.select:
                    if active.type in {
                            'BSDF_ANISOTROPIC',
                            'BSDF_DIFFUSE',
                            'BSDF_GLOSSY',
                            'BSDF_GLASS',
                            'BSDF_REFRACTION',
                            'BSDF_TRANSLUCENT',
                            'BSDF_TRANSPARENT',
                            'BSDF_VELVET',
                            'EMISSION',
                            'AMBIENT_OCCLUSION',
                            }:
                        if not active.inputs[0].is_linked:
                            valid = True
            if valid:
                locx = active.location.x
                locy = active.location.y
                tex = nodes.new('ShaderNodeTexImage')
                tex.location = [locx - 200.0, locy + 28.0]
                map = nodes.new('ShaderNodeMapping')
                map.location = [locx - 490.0, locy + 80.0]
                coord = nodes.new('ShaderNodeTexCoord')
                coord.location = [locx - 700, locy + 40.0]
                active.select = False
                nodes.active = tex
    
                links.new(tex.outputs[0], active.inputs[0])
                links.new(map.outputs[0], tex.inputs[0])
                links.new(coord.outputs[2], map.inputs[0])
    
            return {'FINISHED'}
    
    
    class NodesAddReroutes(Operator, NodeToolBase):
        bl_idname = "node.add_reroutes"
        bl_label = "Add Reroutes"
        bl_description = "Add Reroutes to Outputs"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = EnumProperty(
                name="option",
                items=[
                    ('ALL', 'to all', 'Add to all outputs'),
                    ('LOOSE', 'to loose', 'Add only to loose outputs'),
                    ('LINKED', 'to linked', 'Add only to linked outputs'),
                    ]
                )
    
        def execute(self, context):
            tree_type = context.space_data.node_tree.type
            option = self.option
            nodes, links = get_nodes_links(context)
            # output valid when option is 'all' or when 'loose' output has no links
            valid = False
            post_select = []  # nodes to be selected after execution
            # create reroutes and recreate links
            for node in [n for n in nodes if n.select]:
                if node.outputs:
                    x = node.location.x
                    y = node.location.y
                    width = node.width
                    # unhide 'REROUTE' nodes to avoid issues with location.y
                    if node.type == 'REROUTE':
                        node.hide = False
                    # When node is hidden - width_hidden not usable.
                    # Hack needed to calculate real width
                    if node.hide:
                        bpy.ops.node.select_all(action='DESELECT')
                        helper = nodes.new('NodeReroute')
                        helper.select = True
                        node.select = True
                        # resize node and helper to zero. Then check locations to calculate width
                        bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
                        width = 2.0 * (helper.location.x - node.location.x)
                        # restore node location
                        node.location = x, y
                        # delete helper
                        node.select = False
                        # only helper is selected now
                        bpy.ops.node.delete()
                    x = node.location.x + width + 20.0
                    if node.type != 'REROUTE':
                        y -= 35.0
                    y_offset = -22.0
                    loc = x, y
                reroutes_count = 0  # will be used when aligning reroutes added to hidden nodes
                for out_i, output in enumerate(node.outputs):
                    pass_used = False  # initial value to be analyzed if 'R_LAYERS'
                    # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
                    if node.type != 'R_LAYERS':
                        pass_used = True
                    else:  # if 'R_LAYERS' check if output represent used render pass
                        node_scene = node.scene
                        node_layer = node.layer
                        # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
                        if output.name == 'Alpha':
                            pass_used = True
                        else:
                            # check entries in global 'rl_outputs' variable
                            for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                                if output.name == out_name:
                                    pass_used = getattr(node_scene.render.layers[node_layer], render_pass)
                                    break
                    if pass_used:
                        valid = ((option == 'ALL') or
                                 (option == 'LOOSE' and not output.links) or
                                 (option == 'LINKED' and output.links))
                        # Add reroutes only if valid, but offset location in all cases.
                        if valid:
                            n = nodes.new('NodeReroute')
                            nodes.active = n
                            for link in output.links:
                                links.new(n.outputs[0], link.to_socket)
                            links.new(output, n.inputs[0])
                            n.location = loc
                            post_select.append(n)
                        reroutes_count += 1
                        y += y_offset
                        loc = x, y
                # disselect the node so that after execution of script only newly created nodes are selected
                node.select = False
                # nicer reroutes distribution along y when node.hide
                if node.hide:
                    y_translate = reroutes_count * y_offset / 2.0 - y_offset - 35.0
                    for reroute in [r for r in nodes if r.select]:
                        reroute.location.y -= y_translate
                for node in post_select:
                    node.select = True
    
            return {'FINISHED'}
    
    
    class NodesSwap(Operator, NodeToolBase):
        bl_idname = "node.swap_nodes"
        bl_label = "Swap Nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = EnumProperty(
                items=[
                    ('CompositorNodeSwitch', 'Switch', 'Switch'),
                    ('NodeReroute', 'Reroute', 'Reroute'),
                    ('NodeMixRGB', 'Mix Node', 'Mix Node'),
                    ('NodeMath', 'Math Node', 'Math Node'),
                    ('CompositorNodeAlphaOver', 'Alpha Over', 'Alpha Over'),
                    ('ShaderNodeBsdfTransparent', 'Transparent BSDF', 'Transparent BSDF'),
                    ('ShaderNodeBsdfGlossy', 'Glossy BSDF', 'Glossy BSDF'),
                    ('ShaderNodeBsdfGlass', 'Glass BSDF', 'Glass BSDF'),
                    ('ShaderNodeBsdfDiffuse', 'Diffuse BSDF', 'Diffuse BSDF'),
    
                    ('ShaderNodeSubsurfaceScattering', 'SUBSURFACE_SCATTERING', 'Subsurface Scattering'),
    
                    ('ShaderNodeEmission', 'Emission', 'Emission'),
                    ('ShaderNodeBsdfVelvet', 'Velvet BSDF', 'Velvet BSDF'),
                    ('ShaderNodeBsdfTranslucent', 'Translucent BSDF', 'Translucent BSDF'),
                    ('ShaderNodeAmbientOcclusion', 'Ambient Occlusion', 'Ambient Occlusion'),
                    ('ShaderNodeBackground', 'Background', 'Background'),
                    ('ShaderNodeBsdfRefraction', 'Refraction BSDF', 'Refraction BSDF'),
                    ('ShaderNodeBsdfAnisotropic', 'Anisotropic BSDF', 'Anisotropic BSDF'),
                    ('ShaderNodeHoldout', 'Holdout', 'Holdout'),
    
                    ('ShaderNodeAddShader', 'Add Shader', 'Add Shader'),
                    ('ShaderNodeMixShader', 'Mix Shader', 'Mix Shader'),
    
                    ]
                )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            tree_type = context.space_data.tree_type
            if tree_type == 'CompositorNodeTree':
                prefix = 'Compositor'
            elif tree_type == 'ShaderNodeTree':
                prefix = 'Shader'
            option = self.option
            selected = [n for n in nodes if n.select]
            reselect = []
            mode = None  # will be used to set proper operation or blend type in new Math or Mix nodes.
            # regular_shaders - global list. Entry: (identifier, type, name for humans)
            # example: ('ShaderNodeBsdfTransparent', 'BSDF_TRANSPARENT', 'Transparent BSDF')
            swap_shaders = option in (s[0] for s in regular_shaders)
    
            swap_merge_shaders = option in (s[0] for s in merge_shaders)
            if swap_shaders or swap_merge_shaders:
    
                # replace_types - list of node types that can be replaced using selected option
    
                shaders = regular_shaders + merge_shaders
                replace_types = [type[1] for type in shaders]
    
                new_type = option
            elif option == 'CompositorNodeSwitch':
                replace_types = ('REROUTE', 'MIX_RGB', 'MATH', 'ALPHAOVER')
                new_type = option
            elif option == 'NodeReroute':
                replace_types = ('SWITCH')
                new_type = option
            elif option == 'NodeMixRGB':
                replace_types = ('REROUTE', 'SWITCH', 'MATH', 'ALPHAOVER')
                new_type = prefix + option
            elif option == 'NodeMath':
                replace_types = ('REROUTE', 'SWITCH', 'MIX_RGB', 'ALPHAOVER')
                new_type = prefix + option
            elif option == 'CompositorNodeAlphaOver':
                replace_types = ('REROUTE', 'SWITCH', 'MATH', 'MIX_RGB')
                new_type = option
            for node in selected:
                if node.type in replace_types:
                    hide = node.hide
                    if node.type == 'REROUTE':
                        hide = True
                    new_node = nodes.new(new_type)
                    # if swap Mix to Math of vice-verca - try to set blend type or operation accordingly
    
                    if new_node.type in {'MIX_RGB', 'ALPHAOVER'}:
                        new_node.inputs[0].default_value = 1.0
    
                        if node.type == 'MATH':
                            if node.operation in [entry[0] for entry in blend_types]:
    
                                if hasattr(new_node, 'blend_type'):
                                    new_node.blend_type = node.operation
                            for i in range(2):
                                new_node.inputs[i+1].default_value = [node.inputs[i].default_value] * 3 + [1.0]
                        elif node.type in {'MIX_RGB', 'ALPHAOVER'}:
                            for i in range(3):
                                new_node.inputs[i].default_value = node.inputs[i].default_value
    
                    elif new_node.type == 'MATH':
    
                        if node.type in {'MIX_RGB', 'ALPHAOVER'}:
                            if hasattr(node, 'blend_type'):
                                if node.blend_type in [entry[0] for entry in operations]:
                                    new_node.operation = node.blend_type
                            for i in range(2):
                                channels = []
                                for c in range(3):
                                    channels.append(node.inputs[i+1].default_value[c])
                                new_node.inputs[i].default_value = max(channels)
    
                    old_inputs_count = len(node.inputs)
                    new_inputs_count = len(new_node.inputs)
    
                    replace = []  # entries - pairs: old input index, new input index.
    
                    if swap_shaders:
                        for old_i, old_input in enumerate(node.inputs):
                            for new_i, new_input in enumerate(new_node.inputs):
                                if old_input.name == new_input.name:
                                    replace.append((old_i, new_i))
    
                                    new_input.default_value = old_input.default_value
    
                    elif option == 'ShaderNodeAddShader':
                        if node.type == 'ADD_SHADER':
                            replace = ((0, 0), (1, 1))
                        elif node.type == 'MIX_SHADER':
                            replace = ((1, 0), (2, 1))
                    elif option == 'ShaderNodeMixShader':
                        if node.type == 'ADD_SHADER':
                            replace = ((0, 1), (1, 2))
                        elif node.type == 'MIX_SHADER':
                            replace = ((1, 1), (2, 2))
    
                    elif new_inputs_count == 1:
    
                    elif new_inputs_count == 2:
                        if old_inputs_count == 1:
                            replace = ((0, 0), )
                        elif old_inputs_count == 2:
                            replace = ((0, 0), (1, 1))
                        elif old_inputs_count == 3:
                            replace = ((1, 0), (2, 1))
                    elif new_inputs_count == 3:
                        if old_inputs_count == 1:
                            replace = ((0, 1), )
                        elif old_inputs_count == 2:
                            replace = ((0, 1), (1, 2))
                        elif old_inputs_count == 3:
                            replace = ((0, 0), (1, 1), (2, 2))
                    if replace:
                        for old_i, new_i in replace:
                            if node.inputs[old_i].links:
                                in_link = node.inputs[old_i].links[0]
                                links.new(in_link.from_socket, new_node.inputs[new_i])
                    for out_link in node.outputs[0].links:
                        links.new(new_node.outputs[0], out_link.to_socket)
    
                    for attr in {'location', 'label', 'mute', 'show_preview', 'width_hidden', 'use_clamp'}:
                        if hasattr(node, attr) and hasattr(new_node, attr):
                            setattr(new_node, attr, getattr(node, attr))
    
                    new_node.hide = hide
                    nodes.active = new_node
                    reselect.append(new_node)
                    bpy.ops.node.select_all(action="DESELECT")
                    node.select = True
                    bpy.ops.node.delete()
                else:
                    reselect.append(node)
            for node in reselect:
                node.select = True
    
            return {'FINISHED'}
    
    
    class NodesLinkActiveToSelected(Operator):
        bl_idname = "node.link_active_to_selected"
        bl_label = "Link Active Node to Selected"
        bl_options = {'REGISTER', 'UNDO'}
    
        replace = BoolProperty()
        use_node_name = BoolProperty()
        use_outputs_names = BoolProperty()
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.node_tree is not None and context.active_node is not None:
                    if context.active_node.select:
                        valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            replace = self.replace
            use_node_name = self.use_node_name
            use_outputs_names = self.use_outputs_names
            active = nodes.active
            selected = [node for node in nodes if node.select and node != active]
            outputs = []  # Only usable outputs of active nodes will be stored here.
            for out in active.outputs:
                if active.type != 'R_LAYERS':
                    outputs.append(out)
                else:
                    # 'R_LAYERS' node type needs special handling.
                    # outputs of 'R_LAYERS' are callable even if not seen in UI.
                    # Only outputs that represent used passes should be taken into account
                    # Check if pass represented by output is used.
                    # global 'rl_outputs' list will be used for that
                    for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                        pass_used = False  # initial value. Will be set to True if pass is used
                        if out.name == 'Alpha':
                            # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
                            pass_used = True
                        elif out.name == out_name:
                            # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
                            pass_used = getattr(active.scene.render.layers[active.layer], render_pass)
                            break
                    if pass_used:
                        outputs.append(out)
            doit = True  # Will be changed to False when links successfully added to previous output.
            for out in outputs:
                if doit:
                    for node in selected:
                        dst_name = node.name  # Will be compared with src_name if needed.
                        # When node has label - use it as dst_name
                        if node.label:
                            dst_name = node.label
                        valid = True  # Initial value. Will be changed to False if names don't match.
                        src_name = dst_name  # If names not used - this asignment will keep valid = True.
                        if use_node_name:
                            # Set src_name to source node name or label
                            src_name = active.name
                            if active.label:
                                src_name = active.label
                        elif use_outputs_names:
                            src_name = (out.name, )
                            for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                                if out.name in {out_name, exr_name}:
                                    src_name = (out_name, exr_name)
                        if dst_name not in src_name:
                            valid = False
                        if valid:
                            for input in node.inputs:
                                if input.type == out.type or node.type == 'REROUTE':
                                    if replace or not input.is_linked:
                                        links.new(out, input)
                                        if not use_node_name and not use_outputs_names:
                                            doit = False
                                        break
    
            return {'FINISHED'}
    
    
    class AlignNodes(Operator, NodeToolBase):
        bl_idname = "node.align_nodes"
        bl_label = "Align nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        # option: 'Vertically', 'Horizontally'
        option = EnumProperty(
                name="option",
                description="Direction",
                items=(
                    ('AXIS_X', "Align Vertically", 'Align Vertically'),
                    ('AXIS_Y', "Aligh Horizontally", 'Aligh Horizontally'),
                    )
                )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            selected = []  # entry = [index, loc.x, loc.y, width, height]
            frames_reselect = []  # entry = frame node. will be used to reselect all selected frames
            active = nodes.active
            for i, node in enumerate(nodes):
                if node.select:
                    if node.type == 'FRAME':
                        node.select = False
                        frames_reselect.append(i)
                    else:
                        locx = node.location.x
                        locy = node.location.y
                        parent = node.parent
                        while parent is not None:
                            locx += parent.location.x
                            locy += parent.location.y
                            parent = parent.parent
                        selected.append([i, locx, locy])
            count = len(selected)
            # add reroute node then scale all to 0.0 and calculate widths and heights of nodes
            if count > 1:  # aligning makes sense only if at least 2 nodes are selected
                helper = nodes.new('NodeReroute')
                helper.select = True
                bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
                # store helper's location for further calculations
                zero_x = helper.location.x
                zero_y = helper.location.y
                nodes.remove(helper)
                # helper is deleted but its location is stored
                # helper's width and height are 0.0.
                # Check loc of other nodes in relation to helper to calculate their dimensions
                # and append them to entries of "selected"
                total_w = 0.0  # total width of all nodes. Will be calculated later.
                total_h = 0.0  # total height of all nodes. Will be calculated later
                for j, [i, x, y] in enumerate(selected):
                    locx = nodes[i].location.x
                    locy = nodes[i].location.y
                    # take node's parent (frame) into account. Get absolute location
                    parent = nodes[i].parent
                    while parent is not None:
                            locx += parent.location.x
                            locy += parent.location.y