[Bf-extensions-cvs] [8dbd8a8d] master: Node Wrangler: support emission viewer inside node groups

David Friedli noreply at git.blender.org
Fri Apr 24 20:58:27 CEST 2020


Commit: 8dbd8a8d9293556872292352a0baaff7680dba71
Author: David Friedli
Date:   Fri Apr 24 20:57:23 2020 +0200
Branches: master
https://developer.blender.org/rBA8dbd8a8d9293556872292352a0baaff7680dba71

Node Wrangler: support emission viewer inside node groups

Differential Revision: https://developer.blender.org/D6956

Reviewers: gregzaal

===================================================================

M	node_wrangler.py

===================================================================

diff --git a/node_wrangler.py b/node_wrangler.py
index e26da88b..717ffd8e 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -596,9 +596,10 @@ draw_color_sets = {
     )
 }
 
+viewer_socket_name = "tmp_viewer"
 
 def is_visible_socket(socket):
-    return not socket.hide and socket.enabled
+    return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
 
 def nice_hotkey_name(punc):
     # convert the ugly string name into the actual character
@@ -1030,10 +1031,9 @@ def draw_callback_nodeoutline(self, context, mode):
 
         bgl.glDisable(bgl.GL_BLEND)
         bgl.glDisable(bgl.GL_LINE_SMOOTH)
-
-def get_nodes_links(context):
+def get_active_tree(context):
     tree = context.space_data.node_tree
-
+    path = []
     # Get nodes from currently edited tree.
     # If user is editing a group, space_data.node_tree is still the base level (outside group).
     # context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
@@ -1042,9 +1042,71 @@ def get_nodes_links(context):
     if tree.nodes.active:
         while tree.nodes.active != context.active_node:
             tree = tree.nodes.active.node_tree
+            path.append(tree)
+    return tree, path
 
+def get_nodes_links(context):
+    tree, path = get_active_tree(context)
     return tree.nodes, tree.links
 
+def is_viewer_socket(socket):
+    # checks if a internal socket is a valid viewer socket
+    return socket.name == viewer_socket_name and socket.NWViewerSocket
+
+def get_internal_socket(socket):
+    #get the internal socket from a socket inside or outside the group
+    node = socket.node
+    if node.type == 'GROUP_OUTPUT':
+        source_iterator = node.inputs
+        iterator = node.id_data.outputs
+    elif node.type == 'GROUP_INPUT':
+        source_iterator = node.outputs
+        iterator = node.id_data.inputs
+    elif hasattr(node, "node_tree"):
+        if socket.is_output:
+            source_iterator = node.outputs
+            iterator = node.node_tree.outputs
+        else:
+            source_iterator = node.inputs
+            iterator = node.node_tree.inputs
+    else:
+        return None
+
+    for i, s in enumerate(source_iterator):
+        if s == socket:
+            break
+    return iterator[i]
+
+def is_viewer_link(link, output_node):
+    if "Emission Viewer" in link.to_node.name or link.to_node == output_node and link.to_socket == output_node.inputs[0]:
+        return True
+    if link.to_node.type == 'GROUP_OUTPUT':
+        socket = get_internal_socket(link.to_socket)
+        if is_viewer_socket(socket):
+            return True
+    return False
+
+def get_group_output_node(tree):
+    for node in tree.nodes:
+        if node.type == 'GROUP_OUTPUT' and node.is_active_output == True:
+            return node
+
+def get_output_location(tree):
+    # get right-most location
+    sorted_by_xloc = (sorted(tree.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]
+
+    # get average y location
+    sum_yloc = 0
+    for node in tree.nodes:
+        sum_yloc += node.location.y
+
+    loc_x = max_xloc_node.location.x + max_xloc_node.dimensions.x + 80
+    loc_y = sum_yloc / len(tree.nodes)
+    return loc_x, loc_y
+
 # Principled prefs
 class NWPrincipledPreferences(bpy.types.PropertyGroup):
     base_color: StringProperty(
@@ -1624,13 +1686,17 @@ class NWAddAttrNode(Operator, NWBase):
         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'}
 
+    def __init__(self):
+        self.shader_output_type = ""
+        self.shader_output_ident = ""
+        self.shader_viewer_ident = ""
+
     @classmethod
     def poll(cls, context):
         if nw_check(context):
@@ -1643,35 +1709,160 @@ class NWEmissionViewer(Operator, NWBase):
                     return True
         return False
 
-    def invoke(self, context, event):
-        space = context.space_data
-        shader_type = space.shader_type
+    def ensure_viewer_socket(self, node, socket_type, connect_socket=None):
+        #check if a viewer output already exists in a node group otherwise create
+        if hasattr(node, "node_tree"):
+            index = None
+            if len(node.node_tree.outputs):
+                free_socket = None
+                for i, socket in enumerate(node.node_tree.outputs):
+                    if is_viewer_socket(socket) and is_visible_socket(node.outputs[i]) and socket.type == socket_type:
+                        #if viewer output is already used but leads to the same socket we can still use it
+                        is_used = self.is_socket_used_other_mats(socket)
+                        if is_used:
+                            if connect_socket == None:
+                                continue
+                            groupout = get_group_output_node(node.node_tree)
+                            groupout_input = groupout.inputs[i]
+                            links = groupout_input.links
+                            if connect_socket not in [link.from_socket for link in links]:
+                                continue
+                            index=i
+                            break
+                        if not free_socket:
+                            free_socket = i
+                if not index and free_socket:
+                    index = free_socket
+
+            if not index:
+                #create viewer socket
+                node.node_tree.outputs.new(socket_type, viewer_socket_name)
+                index = len(node.node_tree.outputs) - 1
+                node.node_tree.outputs[index].NWViewerSocket = True
+            return index
+
+    def init_shader_variables(self, space, shader_type):
         if shader_type == 'OBJECT':
             if space.id not in [light for light in bpy.data.lights]:  # cannot use bpy.data.lights directly as iterable
-                shader_output_type = "OUTPUT_MATERIAL"
-                shader_output_ident = "ShaderNodeOutputMaterial"
-                shader_viewer_ident = "ShaderNodeEmission"
+                self.shader_output_type = "OUTPUT_MATERIAL"
+                self.shader_output_ident = "ShaderNodeOutputMaterial"
+                self.shader_viewer_ident = "ShaderNodeEmission"
             else:
-                shader_output_type = "OUTPUT_LIGHT"
-                shader_output_ident = "ShaderNodeOutputLight"
-                shader_viewer_ident = "ShaderNodeEmission"
+                self.shader_output_type = "OUTPUT_LIGHT"
+                self.shader_output_ident = "ShaderNodeOutputLight"
+                self.shader_viewer_ident = "ShaderNodeEmission"
 
         elif shader_type == 'WORLD':
-            shader_output_type = "OUTPUT_WORLD"
-            shader_output_ident = "ShaderNodeOutputWorld"
-            shader_viewer_ident = "ShaderNodeBackground"
+            self.shader_output_type = "OUTPUT_WORLD"
+            self.shader_output_ident = "ShaderNodeOutputWorld"
+            self.shader_viewer_ident = "ShaderNodeBackground"
+
+    def get_shader_output_node(self, tree):
+        for node in tree.nodes:
+            if node.type == self.shader_output_type and node.is_active_output == True:
+                return node
+
+    @classmethod
+    def ensure_group_output(cls, tree):
+        #check if a group output node exists otherwise create
+        groupout = get_group_output_node(tree)
+        if not groupout:
+            groupout = tree.nodes.new('NodeGroupOutput')
+            loc_x, loc_y = get_output_location(tree)
+            groupout.location.x = loc_x
+            groupout.location.y = loc_y
+            groupout.select = False
+        return groupout
+
+    @classmethod
+    def search_sockets(cls, node, sockets, index=None):
+        #recursevley scan nodes for viewer sockets and store in list
+        for i, input_socket in enumerate(node.inputs):
+            if index and i != index:
+                continue
+            if len(input_socket.links):
+                link = input_socket.links[0]
+                next_node = link.from_node
+                external_socket = link.from_socket
+                if hasattr(next_node, "node_tree"):
+                    for socket_index, s in enumerate(next_node.outputs):
+                        if s == external_socket:
+                            break
+                    socket = next_node.node_tree.outputs[socket_index]
+                    if is_viewer_socket(socket) and socket not in sockets:
+                        sockets.append(socket)
+                        #continue search inside of node group but restrict socket to where we came from
+                        groupout = get_group_output_node(next_node.node_tree)
+                        cls.search_sockets(groupout, sockets, index=socket_index)
+
+    @classmethod
+    def scan_nodes(cls, tree, selection, sockets):
+        # get all selcted nodes and all viewer sockets in a material tree
+        for node in tree.nodes:
+            if node.select:
+                selection.append(node)
+                node.select = False
+
+            if hasattr(node, "node_tree"):
+                for socket in node.node_tree.outputs:
+                    if is_viewer_socket(socket) and (socket not in sockets):
+                        sockets.append(socket)
+                cls.scan_nodes(node.node_tree, selection, sockets)
+
+    def link_leads_to_used_socket(self, link):
+        #return True if link leads to a socket that is already used in this material
+        socket = get_internal_socket(link.to_socket)
+        return (socket and self.is_socket_used_active_mat(socket))
+
+    def is_socket_used_active_mat(self, socket):
+        #ensure used sockets in active material is calculated and check given socket
+        if not hasattr(self, "used_viewer_sockets_active_mat"):
+            self.used_viewer_sockets_active_mat = []
+  

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list