From 7ad5abd14b050edbaa6f5ef51a31af70dee329eb Mon Sep 17 00:00:00 2001 From: lijenstina Date: Sat, 3 Sep 2016 02:43:59 +0200 Subject: 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 94021dab..019b32b8 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 1b46c0f7..1cb23baf 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 595e0e31..66c83714 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) -- cgit v1.2.3