import bpy import bmesh from mathutils import Vector from math import pi from . import utilities_color from . import settings 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 = [' ','_','.','-'] if settings.bversion >= 4.0: chs = {'ech':26, 'rch':2, 'trch':2, 'ssch':7, 'scch':0, 'mch':1, 'sch':12, 'stch':13, 'ach':14, 'arch':15, 'shch':23, 'shtch':25, 'cch':18, 'crch':19, 'esch':27, 'alch':4} else: sh = 0 # shift of channels, depends on the Blender version if settings.bversion >= 3.0: sh = 2 chs = {'ech':17+sh, 'rch':7+sh, 'ssch':1, 'scch':3, 'mch':4+sh, 'sch':5+sh, 'stch':0, 'shtch':0, 'ach':8+sh, 'arch':9+sh, 'shch':10+sh, 'cch':12+sh, 'crch':13+sh, 'trch':16+sh, 'esch':18+sh, 'alch':19+sh} allMaterials = [] allMaterialsNames = [] elementsCount = 0 class BakeMode: material = "" #Material name from external blend file type = 'EMIT' normal_space = 'TANGENT' setVColor = None #Set Vertex color method color = (0.23, 0.23, 0.23, 1) #Background color engine = 'CYCLES' #render engine, by default CYCLES composite = None #use composite scene to process end result use_project = False #Bake projected? invert = False relink = {'needed':False} params = [] #UI Parameters from scene settings def __init__(self, material="", type='EMIT', normal_space='TANGENT', setVColor=None, color= (0.23, 0.23, 0.23, 1), engine='CYCLES', params = [], composite=None, use_project=False, invert=False, relink = {'needed':False}): self.material = material self.type = type self.normal_space = normal_space self.setVColor = setVColor self.color = color self.engine = engine self.params = params self.composite = composite self.use_project = use_project self.invert = invert self.relink = relink def on_select_bake_mode(mode): print("Mode changed {}".format(mode)) if len(settings.sets) > 0: name_texture = "{}_{}".format(settings.sets[0].name, mode) if name_texture in bpy.data.images: image = bpy.data.images[name_texture] # Set background image for area in bpy.context.screen.areas: if area.ui_type == 'UV': area.spaces[0].image = image def store_bake_settings(): # Render Settings settings.bake_render_engine = bpy.context.scene.render.engine settings.bake_cycles_device = bpy.context.scene.cycles.device settings.bake_cycles_samples = bpy.context.scene.cycles.samples if settings.bversion >= 2.92: settings.bake_target_mode = bpy.context.scene.render.bake.target if settings.bversion < 3: settings.use_progressive_refine = bpy.context.scene.cycles.use_progressive_refine if settings.bversion >= 3: settings.use_denoising = bpy.context.scene.cycles.use_denoising # Disable Objects that are meant to be hidden sets = settings.sets objects_sets = [] for bset in sets: for obj in bset.objects_low: if obj not in objects_sets: objects_sets.append(obj) for obj in bset.objects_high: if obj not in objects_sets: objects_sets.append(obj) for obj in bset.objects_cage: if obj not in objects_sets: objects_sets.append(obj) 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: # for l in range(0, len(obj.layers)): # if obj.layers[l] and bpy.context.scene.layers[l]: # settings.bake_objects_hide_render.append(obj) # break #sav for obj in settings.bake_objects_hide_render: obj.hide_render = True # obj.cycles_visibility.shadow = False def restore_bake_settings(): # Render Settings if settings.bake_render_engine != '': bpy.context.scene.render.engine = settings.bake_render_engine bpy.context.scene.cycles.device = settings.bake_cycles_device bpy.context.scene.cycles.samples = settings.bake_cycles_samples if settings.bversion >= 2.92: bpy.context.scene.render.bake.target = settings.bake_target_mode if settings.bversion < 3: bpy.context.scene.cycles.use_progressive_refine = settings.use_progressive_refine if settings.bversion >= 3: bpy.context.scene.cycles.use_denoising = settings.use_denoising # Restore Objects that were hidden for baking for obj in settings.bake_objects_hide_render: if obj: obj.hide_render = False # obj.cycles_visibility.shadow = True def get_set_name_base(obj): def remove_digits(name): # Remove blender naming digits, e.g. cube.001, cube.002,... if len(name) > 4 and name[-4] == '.' and name[-3:].isdigit(): return name[:-4] return name # Reference parent as base name if obj.parent and obj.parent in bpy.context.selected_objects: return remove_digits(obj.parent.name).lower() # Reference group name as base name elif len(obj.users_collection) == 2: return remove_digits(obj.users_collection[0].name).lower() # Use Object name else: return remove_digits(obj.name).lower() def get_set_name(obj): if bpy.context.scene.texToolsSettings.bake_force == "Multi": return obj.name # Get Basic name name = get_set_name_base(obj) # Split by ' ','_','.' etc. split = name.lower() for char in split_chars: split = split.replace(char,' ') strings = split.split(' ') # Remove all keys from name keys = keywords_cage + keywords_high + keywords_low + keywords_float new_strings = [] for string in strings: is_found = False for key in keys: if string == key: is_found = True break if not is_found: new_strings.append(string) elif len(new_strings) > 0: # No more strings once key is found if we have already something break return "_".join(new_strings) def get_object_type(obj): if bpy.context.scene.texToolsSettings.bake_force == "Multi": return 'low' name = get_set_name_base(obj) # Detect by name pattern split = name.lower() for char in split_chars: split = split.replace(char,' ') strings = split.split(' ') # Detect float, more rare than low for string in strings: for key in keywords_float: if key == string: return 'float' # Detect by modifiers (Only if more than 1 object selected) if bpy.context.preferences.addons[__package__].preferences.bool_modifier_auto_high: if len(bpy.context.selected_objects) > 1: if obj.modifiers: for modifier in obj.modifiers: if modifier.type == 'SUBSURF' and modifier.render_levels > 0: return 'high' elif modifier.type == 'BEVEL': return 'high' # Detect High first, more rare for string in strings: for key in keywords_high: if key == string: return 'high' # Detect cage, more rare than low for string in strings: for key in keywords_cage: if key == string: return 'cage' # Detect low for string in strings: for key in keywords_low: if key == string: return 'low' # if nothing was detected, assume it is low return 'low' def get_baked_images(sets): images = [] for bset in sets: name_texture = "{}_".format(bset.name) for image in bpy.data.images: if name_texture in image.name: images.append(image) return images def get_bake_sets(): filtered = {} if len(bpy.context.selected_objects) == 0: if bpy.context.active_object is not None: if bpy.context.active_object.mode == 'EDIT' and bpy.context.active_object.type == 'MESH': filtered[bpy.context.active_object] = get_object_type(bpy.context.active_object) else: for obj in bpy.context.selected_objects: if obj.type == 'MESH': filtered[obj] = get_object_type(obj) groups = [] # Group by names for obj in filtered: name = get_set_name(obj) if not groups: groups.append([obj]) else: isFound = False for group in groups: groupName = get_set_name(group[0]) if name == groupName: group.append(obj) isFound = True break if not isFound: groups.append([obj]) # Sort groups alphabetically keys = [get_set_name(group[0]) for group in groups] keys.sort() sorted_groups = [] for key in keys: for group in groups: if key == get_set_name(group[0]): sorted_groups.append(group) break groups = sorted_groups bake_sets = [] for group in groups: low = [] high = [] cage = [] float = [] for obj in group: if filtered[obj] == 'low': low.append(obj) elif filtered[obj] == 'high': high.append(obj) elif filtered[obj] == 'cage': cage.append(obj) elif filtered[obj] == 'float': float.append(obj) name = get_set_name(group[0]) bake_sets.append(BakeSet(name, low, cage, high, float)) return bake_sets class BakeSet: objects_low = [] #low poly geometry objects_cage = [] #Cage low poly geometry objects_high = [] #High poly geometry objects_float = [] #Floating geometry name = "" has_issues = False def __init__(self, name, objects_low, objects_cage, objects_high, objects_float): self.objects_low = objects_low self.objects_cage = objects_cage self.objects_high = objects_high self.objects_float = objects_float self.name = name # Needs low poly objects to bake onto if len(objects_low) == 0: self.has_issues = True # Check Cage Object count to low poly count if len(objects_cage) > 0 and len(objects_low) != len(objects_cage): self.has_issues = True # Check for UV maps for obj in objects_low: if len(obj.data.uv_layers) == 0: self.has_issues = True break def assign_vertex_color(obj): if len(obj.data.vertex_colors) > 0 : vclsNames = [vcl.name for vcl in obj.data.vertex_colors] if 'TexTools_temp' in vclsNames: obj.data.vertex_colors['TexTools_temp'].active = True obj.data.vertex_colors['TexTools_temp'].active_render = True else: obj.data.vertex_colors.new(name='TexTools_temp') obj.data.vertex_colors['TexTools_temp'].active = True obj.data.vertex_colors['TexTools_temp'].active_render = True else: obj.data.vertex_colors.new(name='TexTools_temp') obj.data.vertex_colors['TexTools_temp'].active = True obj.data.vertex_colors['TexTools_temp'].active_render = True def setup_vertex_color_selection(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='VERTEX_PAINT') bpy.context.tool_settings.vertex_paint.brush.color = (0, 0, 0) bpy.context.object.data.use_paint_mask = False bpy.ops.paint.vertex_color_set() bpy.context.tool_settings.vertex_paint.brush.color = (1, 1, 1) bpy.context.object.data.use_paint_mask = True bpy.ops.paint.vertex_color_set() bpy.context.object.data.use_paint_mask = False bpy.ops.object.mode_set(mode='OBJECT') def setup_vertex_color_dirty(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') # Fill white then, bm = bmesh.from_edit_mesh(obj.data) if settings.bversion >= 3.4: colorLayerIndex = obj.data.attributes.active_color_index colorLayer = bm.loops.layers.color[colorLayerIndex] else: colorLayer = bm.loops.layers.color.active color = utilities_color.safe_color( (1, 1, 1) ) for face in bm.faces: for loop in face.loops: loop[colorLayer] = color obj.data.update() bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.paint.vertex_color_dirt(dirt_angle=pi/2) bpy.ops.paint.vertex_color_dirt() def setup_vertex_color_id_material(obj, previous_materials): 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.active for i, mtlname in enumerate(previous_materials[obj]): if mtlname is not None: # Select related faces bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') bm = bmesh.from_edit_mesh(bpy.context.active_object.data) for face in bm.faces: if face.material_index == i: face.select = True color = utilities_color.get_color_id(allMaterials.index(bpy.data.materials[mtlname]), 256, jitter=True) bpy.ops.object.mode_set(mode='VERTEX_PAINT') bpy.context.tool_settings.vertex_paint.brush.color = color bpy.context.object.data.use_paint_mask = True bpy.ops.paint.vertex_color_set() obj.data.update() bpy.ops.object.mode_set(mode='OBJECT') def setup_vertex_color_id_element(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) if settings.bversion >= 3.4: colorLayerIndex = obj.data.attributes.active_color_index colorLayer = bm.loops.layers.color[colorLayerIndex] else: colorLayer = bm.loops.layers.color.active # Collect elements processed = set([]) groups = [] for face in bm.faces: if face not in processed: bpy.ops.mesh.select_all(action='DESELECT') face.select = True bpy.ops.mesh.select_linked(delimit={'NORMAL'}) linked = [face for face in bm.faces if face.select] for link in linked: processed.add(link) groups.append(linked) global elementsCount # Color each group for i in range(0,len(groups)): color = utilities_color.get_color_id(elementsCount + i, 256, jitter=True) color = utilities_color.safe_color( color ) for face in groups[i]: for loop in face.loops: loop[colorLayer] = color elementsCount += len(groups) obj.data.update() bpy.ops.object.mode_set(mode='OBJECT') def get_image_material(image): # Clear & Create new material material = None if image.name in bpy.data.materials: # Incorrect existing material, delete first and create new for cycles material = bpy.data.materials[image.name] bpy.data.materials.remove(material, do_unlink=True) material = bpy.data.materials.new(image.name) else: material = bpy.data.materials.new(image.name) # Cycles Material if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE': material.use_nodes = True # Image Node node_image = None if "image" in material.node_tree.nodes: node_image = material.node_tree.nodes["image"] else: node_image = material.node_tree.nodes.new("ShaderNodeTexImage") node_image.name = "image" node_image.select = True node_image.image = image material.node_tree.nodes.active = node_image #Base Diffuse BSDF bsdf_node = None for n in material.node_tree.nodes: if n.bl_idname == "ShaderNodeBsdfPrincipled": bsdf_node = n if "_normal_" in image.name: # Add Normal Map Nodes node_normal_map = None if "normal_map" in material.node_tree.nodes: node_normal_map = material.node_tree.nodes["normal_map"] else: node_normal_map = material.node_tree.nodes.new("ShaderNodeNormalMap") node_normal_map.name = "normal_map" # Tangent or World space if(image.name.endswith("normal_tangent")): node_normal_map.space = 'TANGENT' elif(image.name.endswith("normal_object")): node_normal_map.space = 'WORLD' # image to normal_map link material.node_tree.links.new(node_image.outputs[0], node_normal_map.inputs[1]) # normal_map to diffuse_bsdf link if settings.bversion < 2.91: material.node_tree.links.new(node_normal_map.outputs[0], bsdf_node.inputs[19]) else: material.node_tree.links.new(node_normal_map.outputs[0], bsdf_node.inputs[20]) node_normal_map.location = bsdf_node.location - Vector((200, 0)) node_image.location = node_normal_map.location - Vector((200, 0)) else: # Other images display as Color # dump(node_image.color_mapping.bl_rna.property_tags) # image node to diffuse node link material.node_tree.links.new(node_image.outputs[0], bsdf_node.inputs[0]) return material elif bpy.context.scene.render.engine == 'BLENDER_EEVEE': material.use_nodes = True texture = None if image.name in bpy.data.textures: texture = bpy.data.textures[image.name] else: texture = bpy.data.textures.new(image.name, 'IMAGE') texture.image = image slot = material.texture_slot.add() slot.texture = texture slot.mapping = 'FLAT' # return material