From 7ad5abd14b050edbaa6f5ef51a31af70dee329eb Mon Sep 17 00:00:00 2001
From: lijenstina <lijenstina@gmail.com>
Date: Sat, 3 Sep 2016 02:43:59 +0200
Subject: [PATCH] Materials Utils: Update to Mix Nodes, report switch

Changes mostly touching materials_cycles_converter
Add color mix nodes tree between images and shader input
Add a scene prop for switching off the report in the UI
Remove the preferences conv_path entry it's not used
Remove the Value to RGB node for now (adding it properly needs an
refactor)
Change the placement of the texture nodes (hidden if > 1)
---
 materials_utils/__init__.py                   |  36 ++---
 materials_utils/materials_cycles_converter.py | 125 ++++++++++++++----
 materials_utils/warning_messages_utils.py     |   7 +-
 3 files changed, 124 insertions(+), 44 deletions(-)

diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py
index 94021dab7..019b32b8c 100644
--- a/materials_utils/__init__.py
+++ b/materials_utils/__init__.py
@@ -1687,28 +1687,30 @@ class MATERIAL_MT_scenemassive_opt(Menu):
 
     def draw(self, context):
         layout = self.layout
-        sc = context.scene
+        scene = context.scene.mat_specials
 
-        layout.prop(sc.mat_specials, "EXTRACT_ALPHA",
+        layout.prop(scene, "EXTRACT_ALPHA",
                     text="Extract Alpha Textures (slow)")
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "EXTRACT_PTEX",
+        layout.prop(scene, "EXTRACT_PTEX",
                     text="Extract Procedural Textures (slow)")
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "EXTRACT_OW", text="Re-extract Textures")
+        layout.prop(scene, "EXTRACT_OW", text="Re-extract Textures")
+        use_separator(self, context)
+        layout.prop(scene, "SET_FAKE_USER", text="Set Fake User on unused images")
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "SET_FAKE_USER", text="Set Fake User on unused images")
+        layout.prop(scene, "SCULPT_PAINT", text="Sculpt/Texture paint mode")
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "SCULPT_PAINT", text="Sculpt/Texture paint mode")
+        layout.prop(scene, "UV_UNWRAP", text="Set Auto UV Unwrap (Active Object)")
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "UV_UNWRAP", text="Set Auto UV Unwrap (Active Object)")
+        layout.prop(scene, "enable_report", text="Enable Report in the UI")
         use_separator(self, context)
 
         layout.label("Set the Bake Resolution")
-        res = str(sc.mat_specials.img_bake_size)
+        res = str(scene.img_bake_size)
         layout.label("Current Setting is : %s" % (res + "x" + res), icon='INFO')
         use_separator(self, context)
-        layout.prop(sc.mat_specials, "img_bake_size", icon='NODE_SEL', expand=True)
+        layout.prop(scene, "img_bake_size", icon='NODE_SEL', expand=True)
 
 
 class MATERIAL_PT_scenemassive(Panel):
@@ -1805,7 +1807,7 @@ class MATERIAL_MT_biconv_help(Menu):
         layout.label(text="If possible, avoid multiple conversions in a row")
         layout.label(text="Save Your Work Often", icon="ERROR")
         use_separator(self, context)
-        layout.label(text="Add a Mix Shader & Duplicate Missing Links")
+        layout.label(text="Try to link them manually using Mix Color nodes")
         layout.label(text="Only the last Image in the stack gets linked to Shader")
         layout.label(text="Current limitation:", icon="MOD_EXPLODE")
         use_separator(self, context)
@@ -1936,6 +1938,11 @@ class material_specials_scene_props(PropertyGroup):
             default=False,
             description=("Use automatical Angle based UV Unwrap of the active Object"),
             )
+    enable_report = BoolProperty(
+            attr="enable_report",
+            default=False,
+            description=("Enable Converter Report in the UI"),
+            )
     img_bake_size = EnumProperty(
             name="Bake Image Size",
             description="Set the resolution size of baked images \n",
@@ -1952,14 +1959,6 @@ class material_specials_scene_props(PropertyGroup):
 class VIEW3D_MT_material_utils_pref(AddonPreferences):
     bl_idname = __name__
 
-    conv_path = StringProperty(
-        name="Save Directory",
-        description=("Path to save images during conversion \n"
-                     "Default is the location of the blend file"),
-        default="//",
-        subtype='DIR_PATH',
-        )
-
     show_warnings = BoolProperty(
             name="Enable Warning messages",
             default=False,
@@ -2054,6 +2053,7 @@ class VIEW3D_MT_material_utils_pref(AddonPreferences):
     def draw(self, context):
         layout = self.layout
         sc = context.scene
+
         box = layout.box()
         box.label("Save Directory")
         split = box.split(0.85)
diff --git a/materials_utils/materials_cycles_converter.py b/materials_utils/materials_cycles_converter.py
index 1b46c0f75..1cb23baf7 100644
--- a/materials_utils/materials_cycles_converter.py
+++ b/materials_utils/materials_cycles_converter.py
@@ -5,6 +5,9 @@
 import bpy
 from os import path as os_path
 from bpy.types import Operator
+from math import (log2,
+                  ceil,
+                  )
 from bpy.props import (
             BoolProperty,
             EnumProperty,
@@ -26,6 +29,8 @@ CHECK_AUTONODE = False
 NODE_COLOR = (0.32, 0.75, 0.32)
 # set the node color for the paint base images (default reddish)
 NODE_COLOR_PAINT = (0.6, 0.0, 0.0)
+# set the mix node color (default blueish)
+NODE_COLOR_MIX = (0.1, 0.7, 0.8)
 
 # color for sculpt/texture painting setting (default clay the last entry is Roughness)
 PAINT_SC_COLOR = (0.80, 0.75, 0.54, 0.9)
@@ -267,13 +272,10 @@ def AutoNode(active=False, operator=None):
             # and a Color Ramp node
             shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse')
             shader.location = 0, 470
-            shader_val = TreeNodes.nodes.new('ShaderNodeValToRGB')
-            shader_val.location = 0, 270
             shout = TreeNodes.nodes.new('ShaderNodeOutputMaterial')
             shout.location = 200, 400
             try:
                 links.new(shader.outputs[0], shout.inputs[0])
-                links.new(shader.inputs[0], shader_val.outputs[0])
             except:
                 link_fail = True
 
@@ -291,7 +293,6 @@ def AutoNode(active=False, operator=None):
                         shader.location = 0, 470
                         try:
                             links.new(shader.outputs[0], shout.inputs[0])
-                            links.new(shader.inputs[0], shader_val.outputs[0])
                         except:
                             link_fail = True
 
@@ -303,7 +304,6 @@ def AutoNode(active=False, operator=None):
                         shader.location = 0, 470
                         try:
                             links.new(shader.outputs[0], shout.inputs[0])
-                            links.new(shader.inputs[0], shader_val.outputs[0])
                         except:
                             link_fail = True
 
@@ -315,7 +315,6 @@ def AutoNode(active=False, operator=None):
                         shader.location = 0, 520
                         try:
                             links.new(shader.outputs[0], shout.inputs[0])
-                            links.new(shader.inputs[0], shader_val.outputs[0])
                         except:
                             link_fail = True
 
@@ -329,7 +328,6 @@ def AutoNode(active=False, operator=None):
                         shader.location = 0, 450
                         try:
                             links.new(shader.outputs[0], shout.inputs[0])
-                            links.new(shader.inputs[0], shader_val.outputs[0])
                         except:
                             link_fail = True
                     else:
@@ -390,8 +388,6 @@ def AutoNode(active=False, operator=None):
                     for link in links:
                         links.remove(link)
 
-                    TreeNodes.nodes.remove(shader_val)
-
                     clay_frame = TreeNodes.nodes.new('NodeFrame')
                     clay_frame.name = 'Clay Material'
                     clay_frame.label = 'Clay Material'
@@ -476,6 +472,8 @@ def AutoNode(active=False, operator=None):
                                 img_name = (img.name if hasattr(img, "name") else "NO NAME")
                                 shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
                                 shtext.location = tex_node_loc
+                                shtext.hide = True
+                                shtext.width_hidden = 150
                                 shtext.image = img
                                 shtext.name = img_name
                                 shtext.label = "Image " + img_name
@@ -504,6 +502,8 @@ def AutoNode(active=False, operator=None):
                                         img_name = (img.name if hasattr(img, "name") else "NO NAME")
                                         shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
                                         shtext.location = tex_node_loc
+                                        shtext.hide = True
+                                        shtext.width_hidden = 150
                                         shtext.image = img
                                         shtext.name = img_name
                                         shtext.label = "Baked Image " + img_name
@@ -538,7 +538,7 @@ def AutoNode(active=False, operator=None):
                     if sT and sculpt_paint is False:
                         if tex.use_map_color_diffuse:
                             try:
-                                links.new(shtext.outputs[0], shader_val.inputs[0])
+                                links.new(shtext.outputs[0], shader.inputs[0])
                             except:
                                 pass
                         if tex.use_map_emit:
@@ -569,7 +569,7 @@ def AutoNode(active=False, operator=None):
 
                         if tex.use_map_mirror:
                             try:
-                                links.new(shtext.outputs[0], shader_val.inputs[0])
+                                links.new(shtext.outputs[0], shader.inputs[0])
                             except:
                                 link_fail = True
 
@@ -682,6 +682,8 @@ def AutoNode(active=False, operator=None):
                         img = bpy.data.images.get(paint_img_name)
                         img_name = (img.name if hasattr(img, "name") else "NO NAME")
                         shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
+                        shtext.hide = True
+                        shtext.width_hidden = 150
                         shtext.location = tex_node_loc
                         shtext.image = img
                         shtext.name = img_name
@@ -695,12 +697,17 @@ def AutoNode(active=False, operator=None):
                     except:
                         collect_report("ERROR: Failed to create image and node for Texture Painting")
 
-                # spread the texture nodes, create a node frame if necessary
+                # spread the texture nodes, create node frames if necessary
                 # create texture coordinate and mapping too
-                row_node, col_node = -1, False
-                check_frame = bool(len(tex_node_collect) > 1)
-                node_frame, tex_map, node_f_coord = None, None, None
+                row_node = -1
+                tex_node_collect_size = len(tex_node_collect)
+                median_point = ((tex_node_collect_size / 2) * 100)
+                check_frame = bool(tex_node_collect_size > 1)
+
+                node_frame, tex_map = None, None
+                node_f_coord, node_f_mix = None, None
                 tex_map_collection, tex_map_coord = [], None
+                tree_size, tree_tex_start = 0, 0
 
                 if check_frame:
                     node_frame = TreeNodes.nodes.new('NodeFrame')
@@ -711,22 +718,37 @@ def AutoNode(active=False, operator=None):
                     node_f_coord.name = "Coordinates"
                     node_f_coord.label = "Coordinates"
 
+                    node_f_mix = TreeNodes.nodes.new('NodeFrame')
+                    node_f_mix.name = "Mix"
+                    node_f_mix.label = "Mix"
+
                 if tex_node_collect:
                     tex_map_coord = TreeNodes.nodes.new('ShaderNodeTexCoord')
                     tex_map_coord.location = -900, 575
 
+                    # precalculate the depth of the inverted tree
+                    tree_size = int(ceil(log2(tex_node_collect_size)))
+                    # offset the start of the mix nodes by the depth of the tree
+                    tree_tex_start = ((tree_size + 1) * 150)
+
                 for node_tex in tex_node_collect:
-                    row_node = (row_node + 1 if not col_node else row_node)
-                    col_node = not col_node
-                    tex_node_loc = (-(200 + (row_node * 150)), (400 if col_node else 650))
+                    row_node += 1
+                    col_node_start = (median_point - (-(row_node * 50) + median_point))
+                    tex_node_row = tree_tex_start + 300
+                    mix_node_row = tree_tex_start + 620
+                    tex_node_loc = (-(tex_node_row), col_node_start)
+
                     try:
                         node_tex.location = tex_node_loc
                         if check_frame:
                             node_tex.parent = node_frame
+                        else:
+                            node_tex.hide = False
 
                         tex_node_name = getattr(node_tex, "name", "NO NAME")
                         tex_map_name = "Mapping: {}".format(tex_node_name)
                         tex_map = TreeNodes.nodes.new('ShaderNodeMapping')
+                        tex_map.location = (-(mix_node_row), col_node_start)
                         tex_map.width = 240
                         tex_map.hide = True
                         tex_map.width_hidden = 150
@@ -739,25 +761,42 @@ def AutoNode(active=False, operator=None):
                         continue
 
                 if tex_map_collection:
-                    row_map_tex = -(350 + (row_node + 1) * 150)
-                    col_map_start = (((len(tex_map_collection) / 2) * 50) + 575)
+                    tex_mix_start = len(tex_map_collection) / 2
+                    row_map_start = -(tree_tex_start + 850)
 
                     if tex_map_coord:
-                        tex_map_coord.location = ((row_map_tex - 250), 500)
+                        tex_map_coord.location = (row_map_start,
+                                                  (median_point - (tex_mix_start * 50)))
 
                     for maps in tex_map_collection:
-                        row_node += 1
-                        tex_map_loc = row_map_tex, (-(row_node * 50) + col_map_start)
                         try:
-                            maps.location = tex_map_loc
                             if node_f_coord:
                                 maps.parent = node_f_coord
+                            else:
+                                maps.hide = False
 
                             links.new(maps.inputs[0], tex_map_coord.outputs['UV'])
                         except:
                             link_fail = True
                             continue
 
+                # create mix nodes to connect texture nodes to the shader input
+                # sculpt mode doesn't need them
+                if check_frame and not sculpt_paint:
+                    mix_node_pairs = loop_node_from_list(TreeNodes, links, tex_node_collect,
+                                                         0, tree_tex_start, median_point, node_f_mix)
+
+                    for n in range(1, tree_size):
+                        mix_node_pairs = loop_node_from_list(TreeNodes, links, mix_node_pairs,
+                                                             n, tree_tex_start, median_point, node_f_mix)
+                    try:
+                        for node in mix_node_pairs:
+                            links.new(node.outputs[0], shader.inputs[0])
+                    except:
+                        link_fail = True
+
+                    mix_node_pairs = []
+
                 tex_node_collect, tex_map_collection = [], []
 
                 if link_fail:
@@ -769,6 +808,44 @@ def AutoNode(active=False, operator=None):
     bpy.context.scene.render.engine = 'CYCLES'
 
 
+def loop_node_from_list(TreeNodes, links, node_list, loc, start, median_point, frame):
+    row = 1
+    mix_nodes = []
+    node_list_size = len(node_list)
+    tuplify = [tuple(node_list[s:s + 2]) for s in range(0, node_list_size, 2)]
+    for nodes in tuplify:
+        row += 1
+        create_mix = create_mix_node(TreeNodes, links, nodes, loc, start,
+                                     median_point, row, frame)
+        if create_mix:
+            mix_nodes.append(create_mix)
+    return mix_nodes
+
+
+def create_mix_node(TreeNodes, links, nodes, loc, start, median_point, row, frame):
+    mix_node = TreeNodes.nodes.new('ShaderNodeMixRGB')
+    mix_node.name = "MIX level: " + str(loc)
+    mix_node.label = "MIX level: " + str(loc)
+    mix_node.use_custom_color = True
+    mix_node.color = NODE_COLOR_MIX
+    mix_node.hide = True
+    mix_node.width_hidden = 75
+
+    if frame:
+        mix_node.parent = frame
+    mix_node.location = -(start - loc * 175), ((median_point / 4) + (row * 50))
+
+    try:
+        if len(nodes) > 1:
+            links.new(nodes[0].outputs[0], mix_node.inputs["Color2"])
+            links.new(nodes[1].outputs[0], mix_node.inputs["Color1"])
+        elif len(nodes) == 1:
+            links.new(nodes[0].outputs[0], mix_node.inputs["Color1"])
+    except:
+        collect_report("ERROR: Link failed for mix node {}".format(mix_node.label))
+    return mix_node
+
+
 # -----------------------------------------------------------------------------
 # Operator Classes #
 
diff --git a/materials_utils/warning_messages_utils.py b/materials_utils/warning_messages_utils.py
index 595e0e316..66c83714b 100644
--- a/materials_utils/warning_messages_utils.py
+++ b/materials_utils/warning_messages_utils.py
@@ -129,16 +129,19 @@ def collect_report(collection="", is_start=False, is_final=False):
     # collection passes a string for appending to COLLECT_REPORT global
     # is_final swithes to the final report with the operator in __init__
     global COLLECT_REPORT
+    scene = bpy.context.scene.mat_specials
+    use_report = scene.enable_report
 
     if is_start:
         # there was a crash somewhere before the is_final call
         COLLECT_REPORT = []
 
     if collection and type(collection) is str:
-        COLLECT_REPORT.append(collection)
+        if use_report:
+            COLLECT_REPORT.append(collection)
         print(collection)
 
-    if is_final:
+    if is_final and use_report:
         # final operator pass uses * as delimiter for splitting into new lines
         messages = "*".join(COLLECT_REPORT)
         bpy.ops.mat_converter.reports('INVOKE_DEFAULT', message=messages)
-- 
GitLab