From 6f6f724fdbc111a71dcb4dcb76ac65da736d0f71 Mon Sep 17 00:00:00 2001 From: franMarz <58062362+franMarz@users.noreply.github.com> Date: Sat, 19 Dec 2020 08:17:50 +0100 Subject: [PATCH] Several bake modes fixes - New Material ID baking: assign a unique, distinct color to every material, instead of using the material slot number as color index. Limitations: Support for up to 256 materials; due to the list being persistent only during the current session, between sessions (close and open de file) the result would be different if the materials with users in the scene change (creations, deletions, renames). - Vertex Colors layers present in the objects were overwritten in certain bake modes (Material ID, Element ID, Cavity, Dust, Paint Base, Selection), with the side effect of making some of these modes don't work themselves, generating straight black images. - Roughness and Smoothness were not delivering correct results due to the objects materials being replaced by void materials before the bake would start. - Diffuse, Roughness and Smoothness weren't working properly in multi-object bakes, materials other than the active object active material slot weren't baking. - Now, when baking Diffuse, Roughness and Smoothness, support image nodes created during baking are deleted once the bake ends. - Roughness and Smoothness baked images backgrounds set both to black: no more white background for Smoothness. --- op_bake.py | 159 ++++++++++++++++++++++++++++++--------------- utilities_bake.py | 38 +++++------ utilities_color.py | 25 ++++++- 3 files changed, 143 insertions(+), 79 deletions(-) diff --git a/op_bake.py b/op_bake.py index ae18db1..9cfa060 100644 --- a/op_bake.py +++ b/op_bake.py @@ -8,13 +8,13 @@ from . import utilities_ui from . import settings -from . import utilities_bake as ub #Use shorthand ub = utitlites_bake +from . import utilities_bake as ub # Notes: https://docs.blender.org/manual/en/dev/render/blender_render/bake.html modes={ 'normal_tangent': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), use_project=True), - 'normal_object': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT' ), + 'normal_object': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT'), 'cavity': ub.BakeMode('bake_cavity', type='EMIT', setVColor=ub.setup_vertex_color_dirty), 'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'), 'dust': ub.BakeMode('bake_dust', type='EMIT', setVColor=ub.setup_vertex_color_dirty), @@ -22,14 +22,14 @@ 'id_material': ub.BakeMode('bake_vertex_color',type='EMIT', setVColor=ub.setup_vertex_color_id_material), 'selection': ub.BakeMode('bake_vertex_color',type='EMIT', color=(0, 0, 0, 1), setVColor=ub.setup_vertex_color_selection), 'diffuse': ub.BakeMode('', type='DIFFUSE'), - # 'displacment': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'), + # 'displacement': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'), 'ao': ub.BakeMode('', type='AO', params=["bake_samples"], engine='CYCLES'), 'ao_legacy': ub.BakeMode('', type='AO', params=["bake_samples"], engine='CYCLES'), 'position': ub.BakeMode('bake_position', type='EMIT'), 'curvature': ub.BakeMode('', type='NORMAL', use_project=True, params=["bake_curvature_size"], composite="curvature"), 'wireframe': ub.BakeMode('bake_wireframe', type='EMIT', color=(0, 0, 0, 1), params=["bake_wireframe_size"]), - 'roughness': ub.BakeMode('', type='ROUGHNESS'), - 'smoothness': ub.BakeMode('', type='ROUGHNESS', invert=True) + 'roughness': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1)), + 'smoothness': ub.BakeMode('', type='ROUGHNESS', color=(1, 1, 1, 1), invert=True) } if hasattr(bpy.types,"ShaderNodeBevel"): @@ -56,13 +56,31 @@ def execute(self, context): if bake_mode not in modes: self.report({'ERROR_INVALID_INPUT'}, "Uknown mode '{}' only available: '{}'".format(bake_mode, ", ".join(modes.keys() )) ) - return + return {'CANCELLED'} # Store Selection selected_objects = [obj for obj in bpy.context.selected_objects] active_object = bpy.context.view_layer.objects.active ub.store_bake_settings() + if bake_mode == 'id_material': + #try to redirect deleted materials which were recovered with undo + for i, material in enumerate(ub.allMaterials): + try: material.name + except: ub.allMaterials[i] = bpy.data.materials.get(ub.allMaterialsNames[i]) + #store a persistent ordered list of all materials in the scene + if len(ub.allMaterials) == 0 : + ub.allMaterials = [material for material in bpy.data.materials if material.users != 0] + ub.allMaterialsNames = [material.name for material in ub.allMaterials] + else: + for obj in selected_objects: + for i in range(len(obj.material_slots)): + slot = obj.material_slots[i] + if slot.material: + if slot.material not in ub.allMaterials and slot.material.users != 0 : + ub.allMaterials.append(slot.material) + ub.allMaterialsNames.append(slot.material.name) + # Render sets bake( self = self, @@ -91,7 +109,7 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): print("Bake '{}'".format(mode)) - bpy.context.scene.render.engine = modes[mode].engine #Switch render engine + bpy.context.scene.render.engine = modes[mode].engine #Switch render engine # Disable edit mode if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT': @@ -111,24 +129,24 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): # Get image name name_texture = "{}_{}".format(set.name, mode) if bake_single: - name_texture = "{}_{}".format(sets[0].name, mode)# In Single mode bake into same texture + name_texture = "{}_{}".format(sets[0].name, mode) # In Single mode bake into same texture path = bpy.path.abspath("//{}.tga".format(name_texture)) # Requires 1+ low poly objects if len(set.objects_low) == 0: self.report({'ERROR_INVALID_INPUT'}, "No low poly object as part of the '{}' set".format(set.name) ) - return + return {'CANCELLED'} # Check for UV maps for obj in set.objects_low: if not obj.data.uv_layers or len(obj.data.uv_layers) == 0: self.report({'ERROR_INVALID_INPUT'}, "No UV map available for '{}'".format(obj.name)) - return + return {'CANCELLED'} # Check for cage inconsistencies if len(set.objects_cage) > 0 and (len(set.objects_low) != len(set.objects_cage)): self.report({'ERROR_INVALID_INPUT'}, "{}x cage objects do not match {}x low poly objects for '{}'".format(len(set.objects_cage), len(set.objects_low), obj.name)) - return + return {'CANCELLED'} # Get Materials material_loaded = get_material(mode) @@ -138,30 +156,31 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): else: material_empty = bpy.data.materials.new(name="TT_bake_node") + # Setup Image + is_clear = (not bake_single) or (bake_single and s==0) + image = setup_image(mode, name_texture, render_width, render_height, path, is_clear) - # Assign Materials to Objects + # Assign Materials to Objects and bake nodes to Materials if (len(set.objects_high) + len(set.objects_float)) == 0: # Low poly bake: Assign material to lowpoly for obj in set.objects_low: assign_vertex_color(mode, obj) assign_material(mode, obj, material_loaded, material_empty) + if mode == 'diffuse' or mode == 'roughness' or mode == 'smoothness' : + setup_image_bake_node(obj, image) else: # High to low poly: Low poly require empty material to bake into image for obj in set.objects_low: assign_material(mode, obj, None, material_empty) - + if mode == 'diffuse' or mode == 'roughness' or mode == 'smoothness' : + setup_image_bake_node(obj, image) # Assign material to highpoly for obj in (set.objects_high+set.objects_float): assign_vertex_color(mode, obj) assign_material(mode, obj, material_loaded) - - # Setup Image - is_clear = (not bake_single) or (bake_single and s==0) - image = setup_image(mode, name_texture, render_width, render_height, path, is_clear) - - # Assign bake node to Material - setup_image_bake_node(set.objects_low[0], image) + if mode != 'diffuse' and mode != 'roughness' and mode != 'smoothness' : + setup_image_bake_node(set.objects_low[0], image) print("Bake '{}' = {}".format(set.name, path)) @@ -186,17 +205,15 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): # Assign image to texture faces bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') - for area in bpy.context.screen.areas: if area.type == 'IMAGE_EDITOR': area.spaces[0].image = image # bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image - - bpy.ops.object.mode_set(mode='OBJECT') for obj_high in (set.objects_high): obj_high.select_set( state = True, view_layer = None) + cycles_bake( mode, bpy.context.scene.texToolsSettings.padding, @@ -224,19 +241,37 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): obj_cage ) - # Set background image (CYCLES & BLENDER_EEVEE) - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - area.spaces[0].image = image + if modes[mode].invert: + bpy.ops.image.invert(invert_r=True, invert_g=True, invert_b=True) + + # Set background image (CYCLES & BLENDER_EEVEE) + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + area.spaces[0].image = image + # Delete provisional bake nodes and vertex colors used during baking + if (len(set.objects_high) + len(set.objects_float)) == 0: + for obj in set.objects_low: + clear_image_bake_node(obj) + for vcl in obj.data.vertex_colors: + if vcl.name == 'TexTools': + obj.data.vertex_colors.remove(vcl) + break + else: + for obj in set.objects_low: + clear_image_bake_node(obj) + for obj in (set.objects_high+set.objects_float): + for vcl in obj.data.vertex_colors: + if vcl.name == 'TexTools': + obj.data.vertex_colors.remove(vcl) + break + # Restore renderable for cage objects for obj_cage in set.objects_cage: obj_cage.hide_render = False - - # Downsample image? + # Downsample image? (when baking single, only downsample on last bake) if not bake_single or (bake_single and s == len(sets)-1): - # When baking single, only downsample on last bake if render_width != size[0] or render_height != size[1]: image.scale(size[0],size[1]) @@ -251,7 +286,6 @@ def bake(self, mode, size, bake_single, sampling_scale, samples, ray_distance): - def apply_composite(image, scene_name, size): previous_scene = bpy.context.window.scene @@ -274,7 +308,6 @@ def apply_composite(image, scene_name, size): if "Offset" in scene.node_tree.nodes: scene.node_tree.nodes["Offset"].outputs[0].default_value = size - print("Assign offset: {}".format(scene.node_tree.nodes["Offset"].outputs[0].default_value)) # Render image bpy.ops.render.render(use_viewport=False) @@ -377,33 +410,63 @@ def setup_image(mode, name, width, height, path, is_clear): def setup_image_bake_node(obj, image): - if len(obj.data.materials) <= 0: - print("ERROR, need spare material to setup active image texture to bake!!!") + print("ERROR, need spare material to setup active image texture to bake!!!") else: for slot in obj.material_slots: if slot.material: if(slot.material.use_nodes == False): slot.material.use_nodes = True - # Assign bake node tree = slot.material.node_tree node = None - if "bake" in tree.nodes: - node = tree.nodes["bake"] + if "TexTools_bake" in tree.nodes: + node = tree.nodes["TexTools_bake"] else: node = tree.nodes.new("ShaderNodeTexImage") - node.name = "bake" + node.name = "TexTools_bake" node.select = True node.image = image tree.nodes.active = node +def clear_image_bake_node(obj): + if len(obj.data.materials) <= 0: + pass + else: + for slot in obj.material_slots: + if slot.material: + if(slot.material.use_nodes == False): + slot.material.use_nodes = True + tree = slot.material.node_tree + if "TexTools_bake" in tree.nodes: + node = tree.nodes["TexTools_bake"] + tree.nodes.remove(node) + + + def assign_vertex_color(mode, obj): if modes[mode].setVColor: + preActive = None + if len(obj.data.vertex_colors) > 0 : + vclsNames = [vcl.name for vcl in obj.data.vertex_colors] + preActive = obj.data.vertex_colors.active + preActive = preActive.name + if 'TexTools' in vclsNames : + if obj.data.vertex_colors['TexTools'].active == False : + obj.data.vertex_colors['TexTools'].active = True + else: + obj.data.vertex_colors.new(name='TexTools') + obj.data.vertex_colors['TexTools'].active = True + else: + obj.data.vertex_colors.new(name='TexTools') + modes[mode].setVColor(obj) + if preActive is not None: + obj.data.vertex_colors[preActive].active = True + def assign_material(mode, obj, material_bake=None, material_empty=None): @@ -414,7 +477,7 @@ def assign_material(mode, obj, material_bake=None, material_empty=None): # Select All faces bpy.ops.object.mode_set(mode='EDIT') - bm = bmesh.from_edit_mesh(bpy.context.active_object.data); + bm = bmesh.from_edit_mesh(bpy.context.active_object.data) faces = [face for face in bm.faces if face.select] bpy.ops.mesh.select_all(action='SELECT') @@ -438,14 +501,12 @@ def assign_material(mode, obj, material_bake=None, material_empty=None): material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples - - # Don't apply in diffuse mode - if mode != 'diffuse': + # Don't apply in diffuse, roughness or glossiness modes + if mode != 'diffuse' and mode != 'roughness' and mode != 'smoothness' : if material_bake: # Override with material_bake if len(obj.material_slots) == 0: obj.data.materials.append(material_bake) - else: obj.material_slots[0].material = material_bake obj.active_material_index = 0 @@ -468,18 +529,12 @@ def assign_material(mode, obj, material_bake=None, material_empty=None): bpy.ops.object.mode_set(mode='OBJECT') - - - - def get_material(mode): - - if modes[mode].material == "": - return None # No material setup requires + return None # No material setup required # Find or load material name = modes[mode].material @@ -573,8 +628,6 @@ def cycles_bake(mode, padding, sampling_scale, samples, ray_distance, is_multi, use_cage=True, cage_object=obj_cage.name ) - - if modes[mode].invert: - bpy.ops.image.invert(invert_r=True, invert_g=True, invert_b=True) + bpy.utils.register_class(op) diff --git a/utilities_bake.py b/utilities_bake.py index 1a26ae7..9a0769a 100644 --- a/utilities_bake.py +++ b/utilities_bake.py @@ -2,19 +2,18 @@ import bmesh import operator import time -from mathutils import Vector +from mathutils import Vector, Color from collections import defaultdict from math import pi -from mathutils import Color from . import settings from . import utilities_color -keywords_low = ['lowpoly','low','lowp','lp','lo','l'] -keywords_high = ['highpoly','high','highp','hp','hi','h'] -keywords_cage = ['cage','c'] -keywords_float = ['floater','float','f'] +keywords_low = ['lowpoly','low','lowp','lp','lo'] #excluded 'l' since TexTools v1.4 +keywords_high = ['highpoly','high','highp','hp','hi'] #excluded 'h' since TexTools v1.4 +keywords_cage = ['cage'] #excluded 'c' since TexTools v1.4 +keywords_float = ['floater','float'] #excluded 'f' since TexTools v1.4 split_chars = [' ','_','.','-'] @@ -82,8 +81,6 @@ def store_bake_settings(): settings.bake_objects_hide_render = [] - - # for obj in bpy.context.view_layer.objects: # if obj.hide_render == False and obj not in objects_sets: # Check if layer is active: @@ -113,8 +110,11 @@ def restore_bake_settings(): +allMaterials = [] +allMaterialsNames = [] stored_materials = {} stored_material_faces = {} + def store_materials_clear(): stored_materials.clear() stored_material_faces.clear() @@ -393,13 +393,10 @@ def __init__(self, name, objects_low, objects_cage, objects_high, objects_float) def setup_vertex_color_selection(obj): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') obj.select_set( state = True, view_layer = None) bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='VERTEX_PAINT') bpy.context.tool_settings.vertex_paint.brush.color = (0, 0, 0) @@ -418,9 +415,6 @@ def setup_vertex_color_selection(obj): def setup_vertex_color_dirty(obj): - - print("setup_vertex_color_dirty {}".format(obj.name)) - bpy.ops.object.select_all(action='DESELECT') obj.select_set( state = True, view_layer = None) bpy.context.view_layer.objects.active = obj @@ -428,7 +422,7 @@ def setup_vertex_color_dirty(obj): # Fill white then, bm = bmesh.from_edit_mesh(obj.data) - colorLayer = bm.loops.layers.color.verify() + colorLayer = bm.loops.layers.color.active color = utilities_color.safe_color( (1, 1, 1) ) @@ -449,18 +443,16 @@ def setup_vertex_color_id_material(obj): bpy.ops.object.select_all(action='DESELECT') obj.select_set( state = True, view_layer = None) bpy.context.view_layer.objects.active = obj - - bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') - bm = bmesh.from_edit_mesh(obj.data) - # colorLayer = bm.loops.layers.color.verify() + # bm = bmesh.from_edit_mesh(obj.data) + # colorLayer = bm.loops.layers.color.active for i in range(len(obj.material_slots)): slot = obj.material_slots[i] if slot.material: - # Select related faces bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') @@ -469,8 +461,8 @@ def setup_vertex_color_id_material(obj): for face in bm.faces: if face.material_index == i: face.select = True - - color = utilities_color.get_color_id(i, len(obj.material_slots)) + + color = utilities_color.get_color_id(allMaterials.index(slot.material), 256, jitter=True) bpy.ops.object.mode_set(mode='VERTEX_PAINT') bpy.context.tool_settings.vertex_paint.brush.color = color @@ -493,7 +485,7 @@ def setup_vertex_color_id_element(obj): bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') bm = bmesh.from_edit_mesh(obj.data) - colorLayer = bm.loops.layers.color.verify() + colorLayer = bm.loops.layers.color.active # Collect elements processed = set([]) diff --git a/utilities_color.py b/utilities_color.py index a3eb908..775df5b 100644 --- a/utilities_color.py +++ b/utilities_color.py @@ -219,9 +219,28 @@ def color_to_hex(color): -def get_color_id(index, count): +def get_color_id(index, count, jitter=False): # Get unique color color = Color() - color.hsv = ( index / (count) ), 0.9, 1.0 + indexList = [0, 171, 64, 213, 32, 96, 160, 224, 16, 48, 80, 112, 144, 176, 208, 240, 8, 24, 40, 56, 72, 88, 104, + 120, 136, 152, 168, 184, 200, 216, 232, 248, 4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, + 132, 140, 148, 156, 164, 172, 180, 188, 196, 204, 212, 220, 228, 236, 244, 252, 2, 6, 10, 14, 18, 22, 26, 30, 34, + 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 130, 134, 138, + 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 214, 218, 222, 226, 230, + 234, 238, 242, 246, 250, 254, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, + 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, + 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, + 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 128, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, + 195, 197, 199, 201, 203, 205, 207, 209, 211, 192, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 255] - return color \ No newline at end of file + # index *= 49 + # while index > 127 : + # index -= 113 + + if jitter: + color.hsv = ( indexList[index] / 256 ), 0.9, 1.0 + else: + color.hsv = ( index / (count) ), 0.9, 1.0 + + return color