# Copyright (C) 2021 Victor Soupday # This file is part of CC/iC Blender Tools # # CC/iC Blender Tools is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # CC/iC Blender Tools is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with CC/iC Blender Tools. If not, see . import os import bpy from . import imageutils, jsonutils, nodeutils, utils, params, vars def detect_skin_material_name(mat): name = mat.name.lower() if "std_skin_" in name or "ga_skin_" in name: return True return False def detect_key_words(hints, text): for hint in hints: h = hint.strip() starts = False ends = False deny = False if h != "": if h[0] == "!": h = h[1:] deny = True if h[0] == "^": h = h[1:] starts = True if h[-1] == "$": h = h[0:-1] ends = True if starts and ends and text.startswith(h) and text.endswith(h): if deny: return "Deny" else: return "True" elif starts and text.startswith(h): if deny: return "Deny" else: return "True" elif ends and text.endswith(h): if deny: return "Deny" else: return "True" elif h in text: if deny: return "Deny" else: return "True" return "False" def detect_scalp_material_name(mat): prefs = vars.prefs() material_name = mat.name.lower() hints = prefs.hair_scalp_hint.split(",") detect = detect_key_words(hints, material_name) if detect == "Deny": utils.log_info(f"{mat.name}: has deny keywords, defininately not scalp!") elif detect == "True": utils.log_info(f"{mat.name}: has keywords, is scalp.") return detect def detect_eyelash_material_name(mat): name = mat.name.lower() if "std_eyelash" in name or "ga_eyelash" in name: return True return False def detect_teeth_material_name(mat): name = mat.name.lower() if "std_upper_teeth" in name: return True elif "std_lower_teeth" in name: return True return False def detect_tongue_material_name(mat): name = mat.name.lower() if "std_tongue" in name or "ga_tongue" in name: return True return False def detect_nails_material_name(mat): name = mat.name.lower() if "std_nails" in name or "ga_nails" in name: return True return False def detect_body_object_name(obj): name = obj.name.lower() if "base_body" in name or "game_body" in name: return True return False def detect_smart_hair_maps(mat, tex_dirs, base_dir): if (imageutils.find_image_file(base_dir, tex_dirs, mat, "HAIRFLOW") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "HAIRROOT") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "HAIRID") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "HAIRVERTEXCOLOR") is not None): return "True" return "False" def detect_sss_maps(mat, tex_dirs, base_dir): if (imageutils.find_image_file(base_dir, tex_dirs, mat, "SSS") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "TRANSMISSION") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "RGBAMASK") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "MICRONORMAL") is not None or imageutils.find_image_file(base_dir, tex_dirs, mat, "MICRONMASK") is not None): return "True" return "False" def detect_hair_material(obj, mat, tex_dirs, base_dir, mat_json = None): prefs = vars.prefs() hints = prefs.hair_hint.split(",") material_name = mat.name.lower() if mat_json: shader = jsonutils.get_custom_shader(mat_json) if shader == "RLHair": return "True" else: return "False" # try to find one of the new hair maps: "Flow Map" or "Root Map" if detect_smart_hair_maps(mat, tex_dirs, base_dir) == "True": utils.log_info(f"{obj.name} / {mat.name}: has hair shader textures, is hair.") return "True" detect_mat = detect_key_words(hints, material_name) if detect_mat == "Deny": utils.log_info(f"{obj.name} / {mat.name}: Material has deny keywords, definitely not hair!") return "Deny" if detect_mat == "True": utils.log_info(f"{obj.name} / {mat.name}: Material has hair keywords, is hair.") return "True" return "False" def detect_hair_object(obj, tex_dirs, base_dir, obj_json = None): prefs = vars.prefs() hints = prefs.hair_hint.split(",") object_name = obj.name.lower() if obj_json: for mat in obj.data.materials: if mat: mat_json = jsonutils.get_material_json(obj_json, mat) shader = jsonutils.get_custom_shader(mat_json) if shader == "RLHair": utils.log_info(f"{obj.name} / {mat.name}: Hair material found in JSON data, Object is hair.") return "True" return "False" # with no Json data, attempt to identify hair object by object and material names... for mat in obj.data.materials: if mat: mat_json = jsonutils.get_material_json(obj_json, mat) detect_mat = detect_hair_material(obj, mat, tex_dirs, base_dir, mat_json) if detect_mat == "True": utils.log_info(f"{obj.name} / {mat.name}: Hair material found, Object is hair.") return "True" detect_obj = detect_key_words(hints, object_name) if detect_obj == "Deny": utils.log_info(f"{obj.name} / {mat.name}: Object has deny keywords, definitely not hair!") return "Deny" if detect_obj == "True": utils.log_info(f"{obj.name} / {mat.name}: Object has hair keywords, is hair.") return "True" return "False" def detect_cornea_material(mat): if "std_cornea_" in mat.name.lower(): return True return False def detect_eye_material(mat): if "std_eye_" in mat.name.lower(): return True return False def detect_material_side(mat, side): name = mat.name.lower() if side == "RIGHT": return "_r" in name elif side == "LEFT": return "_l" in name elif side == "UPPER": return "upper_" in name elif side == "LOWER": return "lower_" in name def detect_tearline_material(mat): if "std_tearline_" in mat.name.lower(): return True return False def detect_eye_occlusion_material(mat): if "std_eye_occlusion_" in mat.name.lower(): return True return False STD_MATERIAL_TYPES = { "Std_Tongue": "TONGUE", "Std_Skin_Head": "SKIN_HEAD", "Std_Skin_Body": "SKIN_BODY", "Std_Skin_Arm": "SKIN_ARM", "Std_Skin_Leg": "SKIN_LEG", "Std_Nails": "NAILS", "Std_Eyelash": "EYELASH", "Std_Tearline_R": "TEARLINE_RIGHT", "Std_Tearline_L": "TEARLINE_LEFT", "Std_Eye_Occlusion_R": "OCCLUSION_RIGHT", "Std_Eye_Occlusion_L": "OCCLUSION_LEFT", "Std_Upper_Teeth": "TEETH_UPPER", "Std_Lower_Teeth": "TEETH_LOWER", "Std_Eye_R": "EYE_RIGHT", "Std_Eye_L": "EYE_LEFT", "Std_Cornea_R": "CORNEA_RIGHT", "Std_Cornea_L": "CORNEA_LEFT", } def detect_materials_by_name(chr_cache, obj, mat): mat_name = mat.name.lower() material_type = "DEFAULT" object_type = "DEFAULT" tex_dirs = imageutils.get_material_tex_dirs(chr_cache, obj, mat) if chr_cache.is_import_type("OBJ"): if chr_cache.get_import_has_key(): base_name = utils.strip_name(mat.name) if base_name in STD_MATERIAL_TYPES: material_type = STD_MATERIAL_TYPES[base_name] object_type = "BODY" utils.log_info(f"Material: {mat_name} detected from morph as: {material_type}") return object_type, material_type if detect_hair_object(obj, tex_dirs, chr_cache.get_import_dir()) == "True": object_type = "HAIR" if detect_scalp_material_name(mat) == "True": material_type = "SCALP" elif detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir()) == "Deny": material_type = "DEFAULT" else: material_type = "HAIR" elif detect_body_object_name(obj): object_type = "BODY" if detect_skin_material_name(mat): if "head" in mat_name: material_type = "SKIN_HEAD" elif "body" in mat_name: material_type = "SKIN_BODY" elif "arm" in mat_name: material_type = "SKIN_ARM" elif "leg" in mat_name: material_type = "SKIN_LEG" elif detect_nails_material_name(mat): material_type = "NAILS" elif detect_eyelash_material_name(mat): material_type = "EYELASH" elif detect_cornea_material(mat): object_type = "EYE" if detect_material_side(mat, "LEFT"): material_type = "CORNEA_LEFT" else: material_type = "CORNEA_RIGHT" elif detect_eye_occlusion_material(mat): # detect occlusion before eye object_type = "OCCLUSION" if detect_material_side(mat, "LEFT"): material_type = "OCCLUSION_LEFT" else: material_type = "OCCLUSION_RIGHT" elif detect_eye_material(mat): object_type = "EYE" if detect_material_side(mat, "LEFT"): material_type = "EYE_LEFT" else: material_type = "EYE_RIGHT" elif detect_tearline_material(mat): object_type = "TEARLINE" if detect_material_side(mat, "LEFT"): material_type = "TEARLINE_LEFT" else: material_type = "TEARLINE_RIGHT" elif detect_teeth_material_name(mat): object_type = "TEETH" if detect_material_side(mat, "UPPER"): material_type = "TEETH_UPPER" else: material_type = "TEETH_LOWER" elif detect_tongue_material_name(mat): object_type = "TONGUE" material_type = "TONGUE" elif detect_sss_maps(mat, tex_dirs, chr_cache.get_import_dir()) == "True": material_type = "SSS" utils.log_info(f"Material: {mat_name} detected by name as: {material_type}") return object_type, material_type def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json): shader = jsonutils.get_custom_shader(mat_json) mat_name = mat.name.lower() material_type = "DEFAULT" object_type = "DEFAULT" tex_dirs = imageutils.get_material_tex_dirs(chr_cache, obj, mat) utils.log_info(f"Material Shader: {shader}") if shader == "Pbr" or shader == "Tra": # PBR materials can also refer to the scalp/base on hair objects, # the eyelashes on the body or the eye(iris) materials on the eyes. if detect_hair_object(obj, tex_dirs, chr_cache.get_import_dir(), obj_json) == "True": object_type = "HAIR" if detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir(), mat_json) == "True": material_type = "HAIR" elif detect_scalp_material_name(mat) == "True": material_type = "SCALP" else: material_type = "DEFAULT" elif detect_eyelash_material_name(mat): object_type = "BODY" material_type = "EYELASH" elif detect_eye_material(mat): object_type = "EYE" if detect_material_side(mat, "LEFT"): material_type = "EYE_LEFT" else: material_type = "EYE_RIGHT" else: material_type = "DEFAULT" elif shader == "RLSSS": material_type = "SSS" elif shader == "RLTongue": object_type = "TONGUE" material_type = "TONGUE" elif shader == "RLSkin": object_type = "BODY" if "body" in mat_name: material_type = "SKIN_BODY" elif "arm" in mat_name: material_type = "SKIN_ARM" elif "leg" in mat_name: material_type = "SKIN_LEG" elif detect_nails_material_name(mat): material_type = "NAILS" elif shader == "RLHead": object_type = "BODY" material_type = "SKIN_HEAD" elif shader == "RLEye": object_type = "EYE" if detect_material_side(mat, "LEFT"): material_type = "CORNEA_LEFT" else: material_type = "CORNEA_RIGHT" elif shader == "RLTeethGum": object_type = "TEETH" if detect_material_side(mat, "UPPER"): material_type = "TEETH_UPPER" else: material_type = "TEETH_LOWER" elif shader == "RLEyeOcclusion": object_type = "OCCLUSION" if detect_material_side(mat, "LEFT"): material_type = "OCCLUSION_LEFT" else: material_type = "OCCLUSION_RIGHT" elif shader == "RLEyeOcclusion_Plus": object_type = "OCCLUSION_PLUS" if detect_material_side(mat, "LEFT"): material_type = "OCCLUSION_PLUS_LEFT" else: material_type = "OCCLUSION_PLUS_RIGHT" elif shader == "RLEyeTearline": object_type = "TEARLINE" if detect_material_side(mat, "LEFT"): material_type = "TEARLINE_LEFT" else: material_type = "TEARLINE_RIGHT" elif shader == "RLEyeTearline_Plus": object_type = "TEARLINE_PLUS" if detect_material_side(mat, "LEFT"): material_type = "TEARLINE_PLUS_LEFT" else: material_type = "TEARLINE_PLUS_RIGHT" elif shader == "RLHair": object_type = "HAIR" material_type = "HAIR" else: object_type = "DEFAULT" material_type = "DEFAULT" # final check for body if object_type == "DEFAULT" and material_type == "DEFAULT": if detect_body_object_name(obj) and detect_skin_material_name(mat): object_type = "BODY" utils.log_info(f"Material: {obj.name}/{mat_name} detected from Json data as: {object_type}/{material_type}") return object_type, material_type def detect_materials(chr_cache, obj, mat, obj_json): if chr_cache.is_actor_core(): return "BODY", "DEFAULT" mat_json = jsonutils.get_material_json(obj_json, mat) if mat_json: return detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json) else: return detect_materials_by_name(chr_cache, obj, mat) def detect_embedded_textures(chr_cache, obj, obj_cache, mat, mat_cache): main_tex_dir = chr_cache.get_tex_dir() nodes = mat.node_tree.nodes # detect embedded textures for node in nodes: if node.type == "TEX_IMAGE" and node.image is not None: filepath = bpy.path.abspath(node.image.filepath) dir, name = os.path.split(filepath) # presence of packed images means that the fbx had embedded textures if node.image.packed_file: chr_cache.import_embedded = True # detect incorrect image paths for non packed (not embedded) images and attempt to correct... # (don't do this for user added materials) if not mat_cache.user_added and node.image.packed_file is None: if os.path.normpath(dir) != os.path.normpath(main_tex_dir): utils.log_warn("Import bug! Wrong image path detected: " + dir) correct_path = os.path.join(main_tex_dir, name) utils.log_warn(f"Attempting to correct to: {correct_path}") if os.path.exists(correct_path): utils.log_warn(f"Correct image path found: {correct_path}") node.image.filepath = correct_path else: correct_path = os.path.join(mat_cache.get_tex_dir(chr_cache), name) utils.log_warn(f"Attempting to correct to: {correct_path}") if os.path.exists(correct_path): utils.log_warn(f"Correct image path found: {correct_path}") node.image.filepath = correct_path else: utils.log_error("Unable to find correct image!") continue name = name.lower() color_node, color_socket = nodeutils.get_node_and_socket_connected_to_output(node, "Color") alpha_node, alpha_socket = nodeutils.get_node_and_socket_connected_to_output(node, "Alpha") if color_node and color_socket: if color_node.type == "BSDF_PRINCIPLED": if color_socket.name == "Base Color": nodeutils.store_texture_mapping(node, mat_cache, "DIFFUSE") elif color_socket.name in ["Specular", "Specular IOR Level"]: nodeutils.store_texture_mapping(node, mat_cache, "SPECULAR") elif color_socket.name == "Metallic": nodeutils.store_texture_mapping(node, mat_cache, "METALLIC") elif color_socket.name == "Roughness": nodeutils.store_texture_mapping(node, mat_cache, "ROUGHNESS") elif color_socket.name in ["Emission", "Emission Color"]: nodeutils.store_texture_mapping(node, mat_cache, "EMISSION") elif color_socket.name == "Alpha": nodeutils.store_texture_mapping(node, mat_cache, "ALPHA") if "diffuse" in name or "albedo" in name: mat_cache.alpha_is_diffuse = True elif color_socket.name in ["Subsurface", "Subsurface Weight"]: nodeutils.store_texture_mapping(node, mat_cache, "SSS") elif color_node.type == "NORMAL_MAP": if "_bump" in name: # fbx import plugs bump maps into the normal map node... nodeutils.store_texture_mapping(node, mat_cache, "BUMP") else: nodeutils.store_texture_mapping(node, mat_cache, "NORMAL") elif color_node.type == "BUMP": nodeutils.store_texture_mapping(node, mat_cache, "BUMP") else: #if color_node.type == "EMISSION": if node.label == "BASE COLOR": nodeutils.store_texture_mapping(node, mat_cache, "DIFFUSE") if nodeutils.get_node_connected_to_output(node, "Alpha"): nodeutils.store_texture_mapping(node, mat_cache, "ALPHA") mat_cache.alpha_is_diffuse = True elif alpha_node and alpha_socket: if alpha_node.type == "BSDF_PRINCIPLED": if alpha_socket.name == "Alpha": nodeutils.store_texture_mapping(node, mat_cache, "ALPHA") if "diffuse" in name or "albedo" in name: mat_cache.alpha_is_diffuse = True def detect_mixer_masks(chr_cache, obj, obj_cache, mat, mat_cache): main_tex_dir = chr_cache.get_tex_dir() mat_tex_dir = mat_cache.get_tex_dir(chr_cache) rgb_mask : bpy.types.Image = imageutils.find_material_image(mat, "RGBMASK") color_id_mask : bpy.types.Image = imageutils.find_material_image(mat, "COLORID") if rgb_mask or color_id_mask: mixer_settings = mat_cache.mixer_settings if rgb_mask: utils.log_info(f"Mixer RGB Mask found: {rgb_mask.filepath}") mixer_settings.rgb_image = rgb_mask rgb_mask.use_fake_user = True if color_id_mask: utils.log_info(f"Mixer Color Id Mask found: {color_id_mask.filepath}") mixer_settings.id_image = color_id_mask color_id_mask.use_fake_user = True def get_cornea_mat(obj, eye_mat, eye_mat_cache): props = vars.props() chr_cache = props.get_character_cache(obj, eye_mat) if eye_mat_cache.is_eye("LEFT"): side = "LEFT" else: side = "RIGHT" # then try to find in the material cache for mat_cache in chr_cache.eye_material_cache: if not mat_cache.disabled and mat_cache.is_cornea(side): return mat_cache.material, mat_cache utils.log_error("Unable to find the " + side + " cornea material!") return None, None def get_left_right_materials(obj): left = None right = None for idx in range(0, len(obj.material_slots)): slot = obj.material_slots[idx] if slot.material: name = slot.name.lower() if "_l" in name: left = slot.material elif "_r" in name: right = slot.material return left, right def get_left_right_eye_materials(obj): """Eye, (not cornea)""" left = None right = None for idx in range(0, len(obj.material_slots)): slot = obj.material_slots[idx] if slot.material: name = slot.name.lower() if "std_eye_l" in name: left = slot.material elif "std_eye_r" in name: right = slot.material return left, right def is_left_material(mat): if "_l" in mat.name.lower(): return True return False def is_right_material(mat): if "_r" in mat.name.lower(): return True return False def is_material_in_objects(mat, objects): if mat: for obj in objects: if obj.type == "MESH": if mat.name in obj.data.materials: return True return False def apply_backface_culling(obj, mat, sides): props = vars.props() mat_cache = props.get_material_cache(mat) if mat_cache is not None: mat_cache.culling_sides = sides if sides == 1: mat.use_backface_culling = True else: mat.use_backface_culling = False def apply_alpha_override(obj, mat, method): props = vars.props() mat_cache = props.get_material_cache(mat) if mat_cache is not None: mat_cache.alpha_mode = method set_material_alpha(mat, method) def determine_material_alpha(obj_cache, mat_cache, mat_json): is_alpha = False is_blend = False if mat_json: if "Opacity" in mat_json.keys(): if mat_json["Opacity"] < 1.0: is_alpha = True opacity_info = jsonutils.get_texture_info(mat_json, "Opacity") if opacity_info and "Texture Path" in opacity_info.keys() and opacity_info["Texture Path"]: is_alpha = True name = mat_cache.source_name if utils.name_contains_distinct_keywords(name, "Transparency", "Alpha", "Opacity"): is_alpha = True if utils.name_contains_distinct_keywords(name, "Blend", "Lenses", "Lens", "Glass", "Glasses"): is_blend = True if utils.name_contains_distinct_keywords(name, "Base", "Scalp", "Eyelash"): is_alpha = True if obj_cache.is_hair() or mat_cache.is_eyelash(): is_alpha = True if obj_cache.is_tearline() or obj_cache.is_eye_occlusion(): is_blend = True # sometimes the eye is pbr and not the digital human eye shader if detect_cornea_material(mat_cache.material): is_blend = True if is_blend: return "BLEND" elif is_alpha: return "HASHED" else: return "OPAQUE" def set_material_alpha(mat, method, shadows=True, refraction=False, depth=0.0): if method == "HASHED" or method == "DITHERED" or refraction: if utils.B420(): mat.surface_render_method = "DITHERED" else: mat.blend_method = "HASHED" mat.shadow_method = "HASHED" if shadows else "NONE" set_backface_culling(mat, False) if utils.B420(): mat.use_raytrace_refraction = refraction else: mat.use_screen_refraction = refraction mat.refraction_depth = depth elif method == "BLEND": if utils.B420(): mat.surface_render_method = "BLENDED" else: mat.blend_method = "BLEND" mat.shadow_method = "CLIP" if shadows else "NONE" mat.alpha_threshold = 0.5 set_backface_culling(mat, True) elif method == "CLIP": if utils.B420(): mat.surface_render_method = "BLENDED" else: mat.blend_method = "CLIP" mat.shadow_method = "CLIP" if shadows else "NONE" mat.alpha_threshold = 0.5 set_backface_culling(mat, False) else: if utils.B420(): mat.surface_render_method = "DITHERED" else: mat.blend_method = "OPAQUE" mat.shadow_method = "OPAQUE" set_backface_culling(mat, False) def set_backface_culling(mat, backface_culling=True): try: mat.use_backface_culling = backface_culling except: ... try: mat.use_backface_culling_shadow = backface_culling except: ... def test_for_material_uv_coords(obj, mat_slot, uvs): mesh = obj.data ul = mesh.uv_layers[0] for poly in mesh.polygons: if poly.material_index == mat_slot: for loop_index in poly.loop_indices: loop_entry = mesh.loops[loop_index] poly_uv = ul.data[loop_entry.index].uv for uv in uvs: du = uv[0] - poly_uv[0] dv = uv[1] - poly_uv[1] if abs(du) < 0.01 and abs(dv) < 0.01: return True return False def get_material_slot_by_type(chr_cache, obj, material_type): if obj.type == "MESH": for index, slot in enumerate(obj.material_slots): mat = slot.material if mat: mat_cache = chr_cache.get_material_cache(mat) if mat_cache and mat_cache.material_type == material_type: return index return -1 def get_material_by_type(chr_cache, obj, material_type): if obj.type == "MESH": for mat in obj.data.materials: mat_cache = chr_cache.get_material_cache(mat) if mat_cache and mat_cache.material_type == material_type: return mat return None def has_same_textures(mat_a, mat_b): if mat_a.node_tree and mat_b.node_tree: nodes_a = mat_a.node_tree.nodes nodes_b = mat_b.node_tree.nodes if nodes_a and nodes_b: for a in nodes_a: if a.type == "TEX_IMAGE": has_image = False for b in nodes_b: if b.type == "TEX_IMAGE": if a.image == b.image: has_image = True if not has_image: return False return True return False def has_same_parameters(cache_a, cache_b): if cache_a and cache_b: if cache_a.material_type == cache_b.material_type: params_a = cache_a.parameters params_b = cache_b.parameters items_a = params_a.items() items_b = params_b.items() # put the property group items into lists # [(prop_name, value), (prop_name, value), ...] # (because items are not subscriptable) list_a = [ i for i in items_a ] list_b = [ i for i in items_b ] for i in range(0, len(list_a)): # compare prop names if list_a[i][0] != list_b[i][0]: return False # compare prop values try: # try as array first if len(list_a[i][1]) == len(list_b[i][1]): for j in range(0, len(list_a[i][1])): if list_a[i][1][j] != list_b[i][1][j]: return False else: return False except: # then as a value if list_a[i][1] != list_b[i][1]: return False return True return False def find_duplicate_material(chr_cache, mat, processed_materials): source_name = utils.source_name(mat.name) mat_cache = chr_cache.get_material_cache(mat) if mat_cache and processed_materials is not None: for processed_mat in processed_materials: if mat != processed_mat: # only consider materials with the same base name if source_name == utils.source_name(processed_mat.name): processed_cache = chr_cache.get_material_cache(processed_mat) if processed_cache: # with the same material type if mat_cache.material_type == processed_cache.material_type: # that use the same textures if has_same_textures(mat, processed_mat): # and have the same parameters if has_same_parameters(mat_cache, processed_cache): # if there is a matching material that is the base name, # then set the first material name to this base name if mat.name == source_name: utils.force_material_name(processed_mat, source_name) return processed_mat return None def normalize_udim_uvs(obj): """Restore UDIM uv's to into range 0.0 - 1.0 """ mesh = obj.data ul = mesh.uv_layers[0] for uv_loop in ul.data: uv = uv_loop.uv x = int(uv[0]) y = int(uv[1]) uv[0] -= x uv[1] -= y def reconstruct_obj_materials(obj): mesh = obj.data # remove all materials mesh.materials.clear() # add new materials mat_head = bpy.data.materials.new("Std_Skin_Head") #0 mat_body = bpy.data.materials.new("Std_Skin_Body") #1 mat_arm = bpy.data.materials.new("Std_Skin_Arm") #2 mat_leg = bpy.data.materials.new("Std_Skin_Leg") #3 mat_nails = bpy.data.materials.new("Std_Nails") #4 mat_eyelash = bpy.data.materials.new("Std_Eyelash") #5 mat_uteeth= bpy.data.materials.new("Std_Upper_Teeth") #6 mat_lteeth = bpy.data.materials.new("Std_Lower_Teeth") #7 mat_tongue = bpy.data.materials.new("Std_Tongue") #8 mat_reye = bpy.data.materials.new("Std_Eye_R") #9 mat_leye = bpy.data.materials.new("Std_Eye_L") #10 mat_rcornea = bpy.data.materials.new("Std_Cornea_R") #11 mat_lcornea = bpy.data.materials.new("Std_Cornea_L") #12 mesh.materials.append(mat_head) mesh.materials.append(mat_body) mesh.materials.append(mat_arm) mesh.materials.append(mat_leg) mesh.materials.append(mat_nails) mesh.materials.append(mat_eyelash) mesh.materials.append(mat_uteeth) mesh.materials.append(mat_lteeth) mesh.materials.append(mat_tongue) mesh.materials.append(mat_reye) mesh.materials.append(mat_leye) mesh.materials.append(mat_rcornea) mesh.materials.append(mat_lcornea) ul = mesh.uv_layers[0] # figure out which polygon belongs to which material from the vertex groups and uv coords for poly in mesh.polygons: loop_index = poly.loop_indices[0] loop_entry = mesh.loops[loop_index] vertex = mesh.vertices[loop_entry.vertex_index] group = vertex.groups[0].group uv = ul.data[loop_entry.index].uv x = uv[0] if x > 5: poly.material_index = 5 # eyelash elif x > 4: poly.material_index = 4 # nails elif x > 3: poly.material_index = 3 # legs elif x > 2: poly.material_index = 2 # arms elif x > 1: poly.material_index = 1 # body else: # head/eyes/tongue/teeth - determine from vertex group if group == 0: # tongue poly.material_index = 8 elif group == 1: # body (head) poly.material_index = 0 elif group == 2: # eye # can't easily differentiate between the eye parts, set all to right cornea poly.material_index = 11 elif group == 3: # teeth # same with the teeth, set both to upper teeth poly.material_index = 6 def set_materials_setting(param, obj, context, objects_processed): props = vars.props() ob = context.object if obj is not None and obj not in objects_processed: if obj.type == "MESH": objects_processed.append(obj) if props.quick_set_mode == "OBJECT": for mat in obj.data.materials: if mat: if param == "OPAQUE" or param == "BLEND" or param == "HASHED" or param == "CLIP": apply_alpha_override(obj, mat, param) elif param == "SINGLE_SIDED": apply_backface_culling(obj, mat, 1) elif param == "DOUBLE_SIDED": apply_backface_culling(obj, mat, 2) elif ob is not None and ob.type == "MESH" and ob.active_material_index <= len(ob.data.materials): mat = utils.get_context_material(context) if mat: if param == "OPAQUE" or param == "BLEND" or param == "HASHED" or param == "CLIP": apply_alpha_override(obj, mat, param) elif param == "SINGLE_SIDED": apply_backface_culling(obj, mat, 1) elif param == "DOUBLE_SIDED": apply_backface_culling(obj, mat, 2) elif obj.type == "ARMATURE": for child in obj.children: set_materials_setting(param, child, context, objects_processed) def is_rl_material(mat): bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat) if bsdf_node and shader_node: return True return False def reconstruct_material_cache(chr_cache, mat): bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat) if bsdf_node and shader_node: for shader_def in params.SHADER_MATRIX: shader_name = shader_def["name"] if f"({shader_name})" in shader_node.name: mat_cache = chr_cache.add_material_cache(mat) #input_defs = shader_def["inputs"] #for input_def in input_defs: # socket_name = input_def[0] # func = input_def[1] # param_name = input_def[2] # params = input_def[3:] return mat_cache return None class CC3OperatorMaterial(bpy.types.Operator): """CC3 Material Functions""" bl_idname = "cc3.setmaterials" bl_label = "CC3 Material Functions" bl_options = {"REGISTER", "UNDO", "INTERNAL"} param: bpy.props.StringProperty( name = "param", default = "" ) def execute(self, context): props = vars.props() objects_processed = [] if props.quick_set_mode == "OBJECT": for obj in bpy.context.selected_objects: set_materials_setting(self.param, obj, context, objects_processed) else: set_materials_setting(self.param, context.object, context, objects_processed) return {"FINISHED"} @classmethod def description(cls, context, properties): if properties.param == "OPAQUE": return "Set blend mode of all selected objects with alpha channels to opaque" elif properties.param == "BLEND": return "Set blend mode of all selected objects with alpha channels to alpha blend" elif properties.param == "HASHED": return "Set blend mode of all selected objects with alpha channels to alpha hashed" elif properties.param == "CLIP": return "Set blend mode of all selected objects with alpha channels to alpha hashed" elif properties.param == "FETCH": return "Fetch the parameters from the selected objects" elif properties.param == "SINGLE_SIDED": return "Set material to be single sided, only visible from front facing" elif properties.param == "DOUBLE_SIDED": return "Set material to be double sided, visible from both sides" return ""