Files
2026-03-17 15:34:28 -06:00

1078 lines
37 KiB
Python

# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_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 <https://www.gnu.org/licenses/>.
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 ""