Skip to content
Snippets Groups Projects
node_efficiency_tools.py 131 KiB
Newer Older
  • Learn to ignore specific revisions
  • 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459
                    self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
            for connection in n2_outputs:
                try:
                    links.new(n1.outputs[connection[0]], connection[1])
                except:
                    self.report({'WARNING'}, "Some connections have been lost due to differing numbers of output sockets")
    
            hack_force_update(context, nodes)
            return {'FINISHED'}
    
    
    class NWResetBG(Operator, NWBase):
        """Reset the zoom and position of the background image"""
        bl_idname = 'node.nw_bg_reset'
        bl_label = 'Reset Backdrop'
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            snode = context.space_data
            return snode.tree_type == 'CompositorNodeTree'
    
        def execute(self, context):
            context.space_data.backdrop_zoom = 1
            context.space_data.backdrop_x = 0
            context.space_data.backdrop_y = 0
            return {'FINISHED'}
    
    
    class NWAddAttrNode(Operator, NWBase):
        """Add an Attribute node with this name"""
        bl_idname = 'node.nw_add_attr_node'
        bl_label = 'Add UV map'
        attr_name = StringProperty()
        bl_options = {'REGISTER', 'UNDO'}
    
        def execute(self, context):
            bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type="ShaderNodeAttribute")
            nodes, links = get_nodes_links(context)
            nodes.active.attribute_name = self.attr_name
            return {'FINISHED'}
    
    
    class NWEmissionViewer(Operator, NWBase):
        bl_idname = "node.nw_emission_viewer"
        bl_label = "Emission Viewer"
        bl_description = "Connect active node to Emission Shader for shadeless previews"
        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 and (context.active_node.type != "OUTPUT_MATERIAL" or context.active_node.type != "OUTPUT_WORLD"):
                    valid = True
            return valid
    
        def invoke(self, context, event):
            shader_type = context.space_data.shader_type
            if shader_type == 'OBJECT':
                shader_output_type = "OUTPUT_MATERIAL"
                shader_output_ident = "ShaderNodeOutputMaterial"
                shader_viewer_ident = "ShaderNodeEmission"
            elif shader_type == 'WORLD':
                shader_output_type = "OUTPUT_WORLD"
                shader_output_ident = "ShaderNodeOutputWorld"
                shader_viewer_ident = "ShaderNodeBackground"
            shader_types = [x[1] for x in shaders_shader_nodes_props]
            mlocx = event.mouse_region_x
            mlocy = event.mouse_region_y
            select_node = bpy.ops.node.select(mouse_x=mlocx, mouse_y=mlocy, extend=False)
            if 'FINISHED' in select_node:  # only run if mouse click is on a node
                nodes, links = get_nodes_links(context)
                in_group = context.active_node != context.space_data.node_tree.nodes.active
                active = nodes.active
                valid = False
                output_types = [x[1] for x in shaders_output_nodes_props]
                if active:
                    if (active.name != "Emission Viewer") and (active.type not in output_types) and not in_group:
                        if active.select:
                            if active.type not in shader_types:
                                valid = True
                if valid:
                    # get material_output node
                    materialout_exists = False
                    materialout = None  # placeholder node
                    for node in nodes:
                        if node.type == shader_output_type:
                            materialout_exists = True
                            materialout = node
                    if not materialout:
                        materialout = nodes.new(shader_output_ident)
                        sorted_by_xloc = (sorted(nodes, key=lambda x: x.location.x))
                        max_xloc_node = sorted_by_xloc[-1]
                        if max_xloc_node.name == 'Emission Viewer':
                            max_xloc_node = sorted_by_xloc[-2]
                        materialout.location.x = max_xloc_node.location.x + max_xloc_node.dimensions.x + 80
                        sum_yloc = 0
                        for node in nodes:
                            sum_yloc += node.location.y
                        materialout.location.y = sum_yloc / len(nodes)  # put material output at average y location
                        materialout.select = False
                    # get Emission Viewer node
                    emission_exists = False
                    emission_placeholder = nodes[0]
                    for node in nodes:
                        if "Emission Viewer" in node.name:
                            emission_exists = True
                            emission_placeholder = node
    
                    position = 0
                    for link in links:  # check if Emission Viewer is already connected to active node
                        if link.from_node.name == active.name and "Emission Viewer" in link.to_node.name and "Emission Viewer" in materialout.inputs[0].links[0].from_node.name:
                            num_outputs = len(link.from_node.outputs)
                            index = 0
                            for output in link.from_node.outputs:
                                if link.from_socket == output:
                                    position = index
                                index = index + 1
                            position = position + 1
                            if position >= num_outputs:
                                position = 0
    
                    # Store selection
                    selection = []
                    for node in nodes:
                        if node.select == True:
                            selection.append(node.name)
    
                    locx = active.location.x
                    locy = active.location.y
                    dimx = active.dimensions.x
                    dimy = active.dimensions.y
                    if not emission_exists:
                        emission = nodes.new(shader_viewer_ident)
                        emission.hide = True
                        emission.location = [materialout.location.x, (materialout.location.y + 40)]
                        emission.label = "Viewer"
                        emission.name = "Emission Viewer"
                        emission.use_custom_color = True
                        emission.color = (0.6, 0.5, 0.4)
                    else:
                        emission = emission_placeholder
    
                    nodes.active = emission
                    links.new(active.outputs[position], emission.inputs[0])
                    bpy.ops.node.nw_link_out()
    
                    # Restore selection
                    emission.select = False
                    nodes.active = active
                    for node in nodes:
                        if node.name in selection:
                            node.select = True
                else:  # if active node is a shader, connect to output
                    if (active.name != "Emission Viewer") and (active.type not in output_types) and not in_group:
                        bpy.ops.node.nw_link_out()
    
                        # ----Delete Emission Viewer----
                        if [x for x in nodes if x.name == 'Emission Viewer']:
                            # Store selection
                            selection = []
                            for node in nodes:
                                if node.select == True:
                                    selection.append(node.name)
                                    node.select = False
                            # Delete it
                            nodes['Emission Viewer'].select = True
                            bpy.ops.node.delete()
                            # Restore selection
                            for node in nodes:
                                if node.name in selection:
                                    node.select = True
    
                return {'FINISHED'}
            else:
                return {'CANCELLED'}
    
    
    class NWFrameSelected(Operator, NWBase):
        bl_idname = "node.nw_frame_selected"
        bl_label = "Frame Selected"
        bl_description = "Add a frame node and parent the selected nodes to it"
        bl_options = {'REGISTER', 'UNDO'}
        label_prop = StringProperty(name='Label', default=' ', description='The visual name of the frame node')
        color_prop = FloatVectorProperty(name="Color", description="The color of the frame node", default=(0.6, 0.6, 0.6),
                                         min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3)
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.node_tree is not None:
                    valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            selected = []
            for node in nodes:
                if node.select == True:
                    selected.append(node)
    
            bpy.ops.node.add_node(type='NodeFrame')
            frm = nodes.active
            frm.label = self.label_prop
            frm.use_custom_color = True
            frm.color = self.color_prop
    
            for node in selected:
                node.parent = frm
    
            return {'FINISHED'}
    
    
    class NWReloadImages(Operator, NWBase):
        bl_idname = "node.nw_reload_images"
        bl_label = "Reload Images"
        bl_description = "Update all the image nodes to match their files on disk"
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.node_tree is not None:
                    valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            image_types = ["IMAGE", "TEX_IMAGE", "TEX_ENVIRONMENT", "TEXTURE"]
            num_reloaded = 0
            for node in nodes:
                if node.type in image_types:
                    if node.type == "TEXTURE":
                        if node.texture:  # node has texture assigned
                            if node.texture.type in ['IMAGE', 'ENVIRONMENT_MAP']:
                                if node.texture.image:  # texture has image assigned
                                    node.texture.image.reload()
                                    num_reloaded += 1
                    else:
                        if node.image:
                            node.image.reload()
                            num_reloaded += 1
    
            if num_reloaded:
                self.report({'INFO'}, "Reloaded images")
                print("Reloaded " + str(num_reloaded) + " images")
                hack_force_update(context, nodes)
                return {'FINISHED'}
            else:
                self.report({'WARNING'}, "No images found to reload in this node tree")
                return {'CANCELLED'}
    
    
    class NWSwitchNodeType(Operator, NWBase):
        """Switch type of selected nodes """
        bl_idname = "node.nw_swtch_node_type"
        bl_label = "Switch Node Type"
        bl_options = {'REGISTER', 'UNDO'}
    
        to_type = EnumProperty(
            name="Switch to type",
            items=list(shaders_input_nodes_props) +
            list(shaders_output_nodes_props) +
            list(shaders_shader_nodes_props) +
            list(shaders_texture_nodes_props) +
            list(shaders_color_nodes_props) +
            list(shaders_vector_nodes_props) +
            list(shaders_converter_nodes_props) +
            list(shaders_layout_nodes_props) +
            list(compo_input_nodes_props) +
            list(compo_output_nodes_props) +
            list(compo_color_nodes_props) +
            list(compo_converter_nodes_props) +
            list(compo_filter_nodes_props) +
            list(compo_vector_nodes_props) +
            list(compo_matte_nodes_props) +
            list(compo_distort_nodes_props) +
            list(compo_layout_nodes_props),
        )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            to_type = self.to_type
            # Those types of nodes will not swap.
            src_excludes = ('NodeFrame')
            # Those attributes of nodes will be copied if possible
            attrs_to_pass = ('color', 'hide', 'label', 'mute', 'parent',
                             'show_options', 'show_preview', 'show_texture',
                             'use_alpha', 'use_clamp', 'use_custom_color', 'location'
                             )
            selected = [n for n in nodes if n.select]
            reselect = []
            for node in [n for n in selected if
                         n.rna_type.identifier not in src_excludes and
                         n.rna_type.identifier != to_type]:
                new_node = nodes.new(to_type)
                for attr in attrs_to_pass:
                    if hasattr(node, attr) and hasattr(new_node, attr):
                        setattr(new_node, attr, getattr(node, attr))
                # set image datablock of dst to image of src
                if hasattr(node, 'image') and hasattr(new_node, 'image'):
                    if node.image:
                        new_node.image = node.image
                # Special cases
                if new_node.type == 'SWITCH':
                    new_node.hide = True
                # Dictionaries: src_sockets and dst_sockets:
                # 'INPUTS': input sockets ordered by type (entry 'MAIN' main type of inputs).
                # 'OUTPUTS': output sockets ordered by type (entry 'MAIN' main type of outputs).
                # in 'INPUTS' and 'OUTPUTS':
                # 'SHADER', 'RGBA', 'VECTOR', 'VALUE' - sockets of those types.
                # socket entry:
                # (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
                src_sockets = {
                    'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
                    'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
                }
                dst_sockets = {
                    'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
                    'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE': [], 'MAIN': None},
                }
                types_order_one = 'SHADER', 'RGBA', 'VECTOR', 'VALUE'
                types_order_two = 'SHADER', 'VECTOR', 'RGBA', 'VALUE'
                # check src node to set src_sockets values and dst node to set dst_sockets dict values
                for sockets, nd in ((src_sockets, node), (dst_sockets, new_node)):
                    # Check node's inputs and outputs and fill proper entries in "sockets" dict
                    for in_out, in_out_name in ((nd.inputs, 'INPUTS'), (nd.outputs, 'OUTPUTS')):
                        # enumerate in inputs, then in outputs
                        # find name, default value and links of socket
                        for i, socket in enumerate(in_out):
                            the_name = socket.name
                            dval = None
                            # Not every socket, especially in outputs has "default_value"
                            if hasattr(socket, 'default_value'):
                                dval = socket.default_value
                            socket_links = []
                            for lnk in socket.links:
                                socket_links.append(lnk)
                            # check type of socket to fill proper keys.
                            for the_type in types_order_one:
                                if socket.type == the_type:
                                    # create values for sockets['INPUTS'][the_type] and sockets['OUTPUTS'][the_type]
                                    # entry structure: (index_in_type, socket_index, socket_name, socket_default_value, socket_links)
                                    sockets[in_out_name][the_type].append((len(sockets[in_out_name][the_type]), i, the_name, dval, socket_links))
                        # Check which of the types in inputs/outputs is considered to be "main".
                        # Set values of sockets['INPUTS']['MAIN'] and sockets['OUTPUTS']['MAIN']
                        for type_check in types_order_one:
                            if sockets[in_out_name][type_check]:
                                sockets[in_out_name]['MAIN'] = type_check
                                break
    
                matches = {
                    'INPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
                    'OUTPUTS': {'SHADER': [], 'RGBA': [], 'VECTOR': [], 'VALUE_NAME': [], 'VALUE': [], 'MAIN': []},
                }
    
                for inout, soctype in (
                        ('INPUTS', 'MAIN',),
                        ('INPUTS', 'SHADER',),
                        ('INPUTS', 'RGBA',),
                        ('INPUTS', 'VECTOR',),
                        ('INPUTS', 'VALUE',),
                        ('OUTPUTS', 'MAIN',),
                        ('OUTPUTS', 'SHADER',),
                        ('OUTPUTS', 'RGBA',),
                        ('OUTPUTS', 'VECTOR',),
                        ('OUTPUTS', 'VALUE',),
                ):
                    if src_sockets[inout][soctype] and dst_sockets[inout][soctype]:
                        if soctype == 'MAIN':
                            sc = src_sockets[inout][src_sockets[inout]['MAIN']]
                            dt = dst_sockets[inout][dst_sockets[inout]['MAIN']]
                        else:
                            sc = src_sockets[inout][soctype]
                            dt = dst_sockets[inout][soctype]
                        # start with 'dt' to determine number of possibilities.
                        for i, soc in enumerate(dt):
                            # if src main has enough entries - match them with dst main sockets by indexes.
                            if len(sc) > i:
                                matches[inout][soctype].append(((sc[i][1], sc[i][3]), (soc[1], soc[3])))
                            # add 'VALUE_NAME' criterion to inputs.
                            if inout == 'INPUTS' and soctype == 'VALUE':
                                for s in sc:
                                    if s[2] == soc[2]:  # if names match
                                        # append src (index, dval), dst (index, dval)
                                        matches['INPUTS']['VALUE_NAME'].append(((s[1], s[3]), (soc[1], soc[3])))
    
                # When src ['INPUTS']['MAIN'] is 'VECTOR' replace 'MAIN' with matches VECTOR if possible.
                # This creates better links when relinking textures.
                if src_sockets['INPUTS']['MAIN'] == 'VECTOR' and matches['INPUTS']['VECTOR']:
                    matches['INPUTS']['MAIN'] = matches['INPUTS']['VECTOR']
    
                # Pass default values and RELINK:
                for tp in ('MAIN', 'SHADER', 'RGBA', 'VECTOR', 'VALUE_NAME', 'VALUE'):
                    # INPUTS: Base on matches in proper order.
                    for (src_i, src_dval), (dst_i, dst_dval) in matches['INPUTS'][tp]:
                        # pass dvals
                        if src_dval and dst_dval and tp in {'RGBA', 'VALUE_NAME'}:
                            new_node.inputs[dst_i].default_value = src_dval
                        # Special case: switch to math
                        if node.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
                                new_node.type == 'MATH' and\
                                tp == 'MAIN':
                            new_dst_dval = max(src_dval[0], src_dval[1], src_dval[2])
                            new_node.inputs[dst_i].default_value = new_dst_dval
                            if node.type == 'MIX_RGB':
                                if node.blend_type in [o[0] for o in operations]:
                                    new_node.operation = node.blend_type
                        # Special case: switch from math to some types
                        if node.type == 'MATH' and\
                                new_node.type in {'MIX_RGB', 'ALPHAOVER', 'ZCOMBINE'} and\
                                tp == 'MAIN':
                            for i in range(3):
                                new_node.inputs[dst_i].default_value[i] = src_dval
                            if new_node.type == 'MIX_RGB':
                                if node.operation in [t[0] for t in blend_types]:
                                    new_node.blend_type = node.operation
                                # Set Fac of MIX_RGB to 1.0
                                new_node.inputs[0].default_value = 1.0
                        # make link only when dst matching input is not linked already.
                        if node.inputs[src_i].links and not new_node.inputs[dst_i].links:
                            in_src_link = node.inputs[src_i].links[0]
                            in_dst_socket = new_node.inputs[dst_i]
                            links.new(in_src_link.from_socket, in_dst_socket)
                            links.remove(in_src_link)
                    # OUTPUTS: Base on matches in proper order.
                    for (src_i, src_dval), (dst_i, dst_dval) in matches['OUTPUTS'][tp]:
                        for out_src_link in node.outputs[src_i].links:
                            out_dst_socket = new_node.outputs[dst_i]
                            links.new(out_dst_socket, out_src_link.to_socket)
                # relink rest inputs if possible, no criteria
                for src_inp in node.inputs:
                    for dst_inp in new_node.inputs:
                        if src_inp.links and not dst_inp.links:
                            src_link = src_inp.links[0]
                            links.new(src_link.from_socket, dst_inp)
                            links.remove(src_link)
                # relink rest outputs if possible, base on node kind if any left.
                for src_o in node.outputs:
                    for out_src_link in src_o.links:
                        for dst_o in new_node.outputs:
                            if src_o.type == dst_o.type:
                                links.new(dst_o, out_src_link.to_socket)
                # relink rest outputs no criteria if any left. Link all from first output.
                for src_o in node.outputs:
                    for out_src_link in src_o.links:
                        if new_node.outputs:
                            links.new(new_node.outputs[0], out_src_link.to_socket)
                nodes.remove(node)
            return {'FINISHED'}
    
    
    class NWMergeNodes(Operator, NWBase):
        bl_idname = "node.nw_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):
    
            settings = context.user_preferences.addons[__name__].preferences
            merge_hide = settings.merge_hide
            merge_position = settings.merge_position  # 'center' or 'bottom'
    
            do_hide = False
            do_hide_shader = False
            if merge_hide == 'ALWAYS':
                do_hide = True
                do_hide_shader = True
            elif merge_hide == 'NON_SHADER':
                do_hide = True
    
    
            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', ('MIX', 'ADD'), 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', ('MIX', 'ADD'), 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] + 250.0
    
                    nodes_list.sort(key=lambda k: k[2], reverse=True)
    
                    if merge_position == 'CENTER':
                        loc_y = ((nodes_list[len(nodes_list) - 1][2]) + (nodes_list[len(nodes_list) - 2][2])) / 2  # average yloc of last two nodes (lowest two)
                    else:
                        loc_y = nodes_list[len(nodes_list) - 1][2]
                    offset_y = 100
                    if not do_hide:
                        offset_y = 200
                    if nodes_list == selected_shader and not do_hide_shader:
    
                        offset_y = 150.0
                    the_range = len(nodes_list) - 1
                    if len(nodes_list) == 1:
                        the_range = 1
                    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)
    
                                add.hide = do_hide_shader
                                if do_hide_shader:
                                    loc_y = loc_y - 50
    
                                first = 1
                                second = 2
                                add.width_hidden = 100.0
                            elif mode == 'ADD':
                                add_type = node_type + 'AddShader'
                                add = nodes.new(add_type)
    
                                add.hide = do_hide_shader
                                if do_hide_shader:
                                    loc_y = loc_y - 50
    
                                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:
    
                        # Prevent cyclic dependencies when nodes to be marged are linked to one another.
                        # Create list of invalid indexes.
                        invalid_i = [n[0] for n in (selected_mix + selected_math + selected_shader)]
                        # Link only if "to_node" index not in invalid indexes list.
                        if fs_link.to_node not in [nodes[i] for i in invalid_i]:
                            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
    
                    for i, x, y in nodes_list:
                        nodes[i].select = False
    
            return {'FINISHED'}
    
    
    
    class NWBatchChangeNodes(Operator, NWBase):
        bl_idname = "node.nw_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 NWChangeMixFactor(Operator, NWBase):
        bl_idname = "node.nw_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 NWCopySettings(Operator, NWBase):
        bl_idname = "node.nw_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 NWCopyLabel(Operator, NWBase):
        bl_idname = "node.nw_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 NWClearLabel(Operator, NWBase):
        bl_idname = "node.nw_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 NWModifyLabels(Operator, NWBase):
        """Modify Labels of all selected nodes."""
        bl_idname = "node.nw_modify_labels"
        bl_label = "Modify Labels"
        bl_options = {'REGISTER', 'UNDO'}
    
        prepend = StringProperty(
            name="Add to Beginning"
        )
        append = StringProperty(
            name="Add to End"
        )
        replace_from = StringProperty(
            name="Text to Replace"
        )
        replace_to = StringProperty(
            name="Replace with"
        )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            for node in [n for n in nodes if n.select]:
                node.label = self.prepend + node.label.replace(self.replace_from, self.replace_to) + self.append
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.prepend = ""
            self.append = ""
            self.remove = ""
            return context.window_manager.invoke_props_dialog(self)
    
    
    class NWAddTextureSetup(Operator, NWBase):
        bl_idname = "node.nw_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
    
            shader_types = [x[1] for x in shaders_shader_nodes_props if x[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
            texture_types = [x[1] for x in shaders_texture_nodes_props]
    
            valid = False
            if active:
                if active.select:
    
                    if active.type in shader_types or active.type in texture_types:
    
                        if not active.inputs[0].is_linked:
                            valid = True
            if valid:
                locx = active.location.x
                locy = active.location.y
    
    
                xoffset = [500.0, 700.0]
                isshader = True
                if active.type not in shader_types:
                    xoffset = [290.0, 500.0]
                    isshader = False
    
                coordout = 2
                image_type = 'ShaderNodeTexImage'
    
                if (active.type in texture_types and active.type != 'TEX_IMAGE') or (active.type == 'BACKGROUND'):
                    coordout = 0  # image texture uses UVs, procedural textures and Background shader use Generated
                    if active.type == 'BACKGROUND':
                        image_type = 'ShaderNodeTexEnvironment'
    
                if isshader:
                    tex = nodes.new(image_type)
                    tex.location = [locx - 200.0, locy + 28.0]
    
    
                map = nodes.new('ShaderNodeMapping')
    
                map.location = [locx - xoffset[0], locy + 80.0]
                map.width = 240
    
                coord = nodes.new('ShaderNodeTexCoord')
    
                coord.location = [locx - xoffset[1], locy + 40.0]
    
                active.select = False
    
    
                if isshader:
                    nodes.active = tex
                    links.new(tex.outputs[0], active.inputs[0])
                    links.new(map.outputs[0], tex.inputs[0])
                    links.new(coord.outputs[coordout], map.inputs[0])
    
                else:
                    nodes.active = map
                    links.new(map.outputs[0], active.inputs[0])
                    links.new(coord.outputs[coordout], map.inputs[0])
    
    class NWAddReroutes(Operator, NWBase):
        """Add Reroute Nodes and link them to outputs of selected nodes"""
        bl_idname = "node.nw_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