733 lines
27 KiB
Python
733 lines
27 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 math
|
|
|
|
import bpy, bmesh, mathutils
|
|
|
|
from . import materials, geom, jsonutils, utils, vars
|
|
|
|
|
|
def add_vertex_group(obj, name):
|
|
if name not in obj.vertex_groups:
|
|
return obj.vertex_groups.new(name = name)
|
|
else:
|
|
#group = obj.vertex_groups[name]
|
|
#clear_vertex_group(obj, group)
|
|
return obj.vertex_groups[name]
|
|
|
|
|
|
def remove_vertex_group(obj : bpy.types.Object, name):
|
|
if name in obj.vertex_groups:
|
|
obj.vertex_groups.remove(obj.vertex_groups[name])
|
|
|
|
|
|
def get_vertex_group(obj, names) -> bpy.types.VertexGroup:
|
|
if type(names) is str:
|
|
names = [ names ]
|
|
for name in names:
|
|
if name in obj.vertex_groups:
|
|
return obj.vertex_groups[name]
|
|
return None
|
|
|
|
|
|
def clear_vertex_group(obj, vertex_group: bpy.types.VertexGroup):
|
|
all_verts = []
|
|
for v in obj.data.vertices:
|
|
all_verts.append(v.index)
|
|
vertex_group.remove(all_verts)
|
|
|
|
|
|
def set_vertex_group(obj, vertex_group, value):
|
|
if type(vertex_group) is str:
|
|
try:
|
|
vertex_group = obj.vertex_groups[vertex_group]
|
|
except:
|
|
vertex_group = None
|
|
if vertex_group:
|
|
all_verts = []
|
|
for v in obj.data.vertices:
|
|
all_verts.append(v.index)
|
|
vertex_group.add(all_verts, value, 'ADD')
|
|
|
|
|
|
def count_vertex_group(obj, vertex_group: bpy.types.VertexGroup):
|
|
if type(vertex_group) is str or type(vertex_group) is list:
|
|
vertex_group = get_vertex_group(obj, vertex_group)
|
|
count = 0
|
|
if vertex_group:
|
|
vg_idx = vertex_group.index
|
|
for vert in obj.data.vertices:
|
|
for g in vert.groups:
|
|
if g.group == vg_idx:
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def total_vertex_group_weight(obj, vertex_group: bpy.types.VertexGroup):
|
|
if type(vertex_group) is str or type(vertex_group) is list:
|
|
vertex_group = get_vertex_group(obj, vertex_group)
|
|
weight = 0.0
|
|
if vertex_group:
|
|
vg_idx = vertex_group.index
|
|
for vert in obj.data.vertices:
|
|
for g in vert.groups:
|
|
if g.group == vg_idx:
|
|
weight += g.weight
|
|
return weight
|
|
|
|
|
|
def is_empty_vertex_group(obj, vertex_group: bpy.types.VertexGroup, threshold = 0.001):
|
|
if type(vertex_group) is str or type(vertex_group) is list:
|
|
vertex_group = get_vertex_group(obj, vertex_group)
|
|
weight = 0.0
|
|
if vertex_group:
|
|
vg_idx = vertex_group.index
|
|
for vert in obj.data.vertices:
|
|
for g in vert.groups:
|
|
if g.group == vg_idx:
|
|
weight += g.weight
|
|
break
|
|
if weight > threshold:
|
|
return False
|
|
return True
|
|
|
|
|
|
def generate_eye_occlusion_vertex_groups(obj, mat_left, mat_right):
|
|
|
|
vertex_group_inner_l = add_vertex_group(obj, vars.OCCLUSION_GROUP_INNER + "_L")
|
|
vertex_group_outer_l = add_vertex_group(obj, vars.OCCLUSION_GROUP_OUTER + "_L")
|
|
vertex_group_top_l = add_vertex_group(obj, vars.OCCLUSION_GROUP_TOP + "_L")
|
|
vertex_group_bottom_l = add_vertex_group(obj, vars.OCCLUSION_GROUP_BOTTOM + "_L")
|
|
vertex_group_all_l = add_vertex_group(obj, vars.OCCLUSION_GROUP_ALL + "_L")
|
|
|
|
vertex_group_inner_r = add_vertex_group(obj, vars.OCCLUSION_GROUP_INNER + "_R")
|
|
vertex_group_outer_r = add_vertex_group(obj, vars.OCCLUSION_GROUP_OUTER + "_R")
|
|
vertex_group_top_r = add_vertex_group(obj, vars.OCCLUSION_GROUP_TOP + "_R")
|
|
vertex_group_bottom_r = add_vertex_group(obj, vars.OCCLUSION_GROUP_BOTTOM + "_R")
|
|
vertex_group_all_r = add_vertex_group(obj, vars.OCCLUSION_GROUP_ALL + "_R")
|
|
|
|
mesh = obj.data
|
|
ul = mesh.uv_layers[0]
|
|
index = [0]
|
|
for poly in mesh.polygons:
|
|
for loop_index in poly.loop_indices:
|
|
loop_entry = mesh.loops[loop_index]
|
|
vertex = mesh.vertices[loop_entry.vertex_index]
|
|
uv = ul.data[loop_entry.index].uv
|
|
index[0] = vertex.index
|
|
|
|
slot = obj.material_slots[poly.material_index]
|
|
if slot.material == mat_left:
|
|
vertex_group_inner_l.add(index, uv.x, 'REPLACE')
|
|
vertex_group_outer_l.add(index, 1.0 - uv.x, 'REPLACE')
|
|
vertex_group_top_l.add(index, uv.y, 'REPLACE')
|
|
vertex_group_bottom_l.add(index, 1.0 - uv.y, 'REPLACE')
|
|
vertex_group_all_l.add([vertex.index], 1.0, 'REPLACE')
|
|
elif slot.material == mat_right:
|
|
vertex_group_inner_r.add(index, uv.x, 'REPLACE')
|
|
vertex_group_outer_r.add(index, 1.0 - uv.x, 'REPLACE')
|
|
vertex_group_top_r.add(index, uv.y, 'REPLACE')
|
|
vertex_group_bottom_r.add(index, 1.0 - uv.y, 'REPLACE')
|
|
vertex_group_all_r.add([vertex.index], 1.0, 'REPLACE')
|
|
|
|
|
|
def generate_tearline_vertex_groups(obj, mat, is_left=True, is_plus=False):
|
|
|
|
suffix = "_L" if is_left else "_R"
|
|
vertex_group_inner = add_vertex_group(obj, vars.TEARLINE_GROUP_INNER + suffix)
|
|
vertex_group_all = add_vertex_group(obj, vars.TEARLINE_GROUP_ALL + suffix)
|
|
|
|
mesh = obj.data
|
|
ul = mesh.uv_layers[0]
|
|
for poly in mesh.polygons:
|
|
slot = obj.material_slots[poly.material_index]
|
|
if slot.material == mat:
|
|
for loop_index in poly.loop_indices:
|
|
loop_entry = mesh.loops[loop_index]
|
|
vertex = mesh.vertices[loop_entry.vertex_index]
|
|
uv = ul.data[loop_entry.index].uv
|
|
if is_plus:
|
|
if is_left:
|
|
weight = utils.smoothstep(0.3, 0.0, uv.x) * (1.0 if uv.y < 0.5 else 0.0)
|
|
else:
|
|
weight = utils.smoothstep(0.7, 1.0, uv.x) * (1.0 if uv.y > 0.5 else 0.0)
|
|
else:
|
|
weight = 1.0 - utils.smoothstep(0, 0.1, abs(uv.x - 0.5))
|
|
|
|
vertex_group_inner.add([vertex.index], weight, 'REPLACE')
|
|
vertex_group_all.add([vertex.index], 1.0, 'REPLACE')
|
|
|
|
|
|
def rebuild_eye_vertex_groups(chr_cache):
|
|
if chr_cache:
|
|
for obj in chr_cache.get_cache_objects():
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj and obj_cache and obj_cache.is_eye() and not obj_cache.disabled:
|
|
mat_left, mat_right = materials.get_left_right_eye_materials(obj)
|
|
cache_left = chr_cache.get_material_cache(mat_left)
|
|
cache_right = chr_cache.get_material_cache(mat_right)
|
|
|
|
if cache_left and cache_right:
|
|
# Re-create the eye displacement group
|
|
generate_eye_vertex_groups(obj, mat_left, mat_right, cache_left, cache_right)
|
|
|
|
|
|
def generate_eye_vertex_groups(obj, mat_left, mat_right, cache_left, cache_right):
|
|
prefs = vars.prefs()
|
|
|
|
vertex_group_l = add_vertex_group(obj, prefs.eye_displacement_group + "_L")
|
|
vertex_group_r = add_vertex_group(obj, prefs.eye_displacement_group + "_R")
|
|
|
|
mesh = obj.data
|
|
ul = mesh.uv_layers[0]
|
|
for poly in mesh.polygons:
|
|
for loop_index in poly.loop_indices:
|
|
loop_entry = mesh.loops[loop_index]
|
|
vertex = mesh.vertices[loop_entry.vertex_index]
|
|
uv = ul.data[loop_entry.index].uv
|
|
x = uv.x - 0.5
|
|
y = uv.y - 0.5
|
|
radial = math.sqrt(x * x + y * y)
|
|
|
|
slot = obj.material_slots[poly.material_index]
|
|
if slot.material == mat_left:
|
|
sclera_scale = cache_left.parameters.eye_sclera_scale
|
|
iris_radius = cache_left.parameters.eye_iris_radius
|
|
radius = sclera_scale * (iris_radius / 0.16) * 0.128
|
|
#weight = 1.0 - utils.saturate(utils.smoothstep(0, radius, radial))
|
|
weight = utils.saturate(utils.remap(0, radius, 1.0, 0.0, radial))
|
|
vertex_group_l.add([vertex.index], weight, 'REPLACE')
|
|
|
|
elif slot.material == mat_right:
|
|
sclera_scale = cache_right.parameters.eye_iris_scale
|
|
iris_radius = cache_right.parameters.eye_iris_radius
|
|
radius = sclera_scale * (iris_radius / 0.16) * 0.128
|
|
#weight = 1.0 - utils.saturate(utils.smoothstep(0, radius, radial))
|
|
weight = utils.saturate(utils.remap(0, radius, 1.0, 0.0, radial))
|
|
vertex_group_r.add([vertex.index], weight, 'REPLACE')
|
|
|
|
|
|
def get_material_vertex_indices(obj, mat):
|
|
vert_indices = []
|
|
mesh = obj.data
|
|
for poly in mesh.polygons:
|
|
poly_mat = obj.material_slots[poly.material_index].material
|
|
if poly_mat == mat:
|
|
for vert_index in poly.vertices:
|
|
if vert_index not in vert_indices:
|
|
vert_indices.append(vert_index)
|
|
return vert_indices
|
|
|
|
|
|
def get_material_vertices(obj, mat):
|
|
"""Mesh Edit Mode"""
|
|
verts = []
|
|
mesh = obj.data
|
|
for poly in mesh.polygons:
|
|
poly_mat = obj.material_slots[poly.material_index].material
|
|
if poly_mat == mat:
|
|
for vert_index in poly.vertices:
|
|
if vert_index not in verts:
|
|
verts.append(mesh.vertices[vert_index])
|
|
return verts
|
|
|
|
|
|
def select_material_faces(obj, mat, select = True, deselect_first = False, include_edges = True, include_vertices = True):
|
|
mesh : bpy.types.Mesh = obj.data
|
|
poly : bpy.types.MeshPolygon
|
|
for poly in mesh.polygons:
|
|
|
|
poly_mat = obj.material_slots[poly.material_index].material
|
|
|
|
if deselect_first:
|
|
poly.select = False
|
|
if poly_mat == mat:
|
|
poly.select = select
|
|
|
|
if include_edges:
|
|
for edge_key in poly.edge_keys:
|
|
for edge_index in edge_key:
|
|
edge = mesh.edges[edge_index]
|
|
if deselect_first:
|
|
edge.select = False
|
|
if poly_mat == mat:
|
|
edge.select = select
|
|
|
|
if include_vertices:
|
|
for vertex_index in poly.vertices:
|
|
vertex = mesh.vertices[vertex_index]
|
|
if deselect_first:
|
|
vertex.select = False
|
|
if poly_mat == mat:
|
|
vertex.select = select
|
|
|
|
|
|
def remove_material_verts(obj, mat):
|
|
mesh = obj.data
|
|
utils.clear_selected_objects()
|
|
if utils.edit_mode_to(obj):
|
|
bpy.ops.mesh.select_all(action="DESELECT")
|
|
if utils.object_mode_to(obj):
|
|
for vert in mesh.vertices:
|
|
vert.select = False
|
|
for poly in mesh.polygons:
|
|
poly_mat = obj.material_slots[poly.material_index].material
|
|
if poly_mat == mat:
|
|
for vert_index in poly.vertices:
|
|
mesh.vertices[vert_index].select = True
|
|
if utils.edit_mode_to(obj):
|
|
bpy.ops.mesh.delete(type='VERT')
|
|
utils.object_mode_to(obj)
|
|
|
|
|
|
def find_shape_key(obj : bpy.types.Object, shape_key_name) -> bpy.types.ShapeKey:
|
|
try:
|
|
return obj.data.shape_keys.key_blocks[shape_key_name]
|
|
except:
|
|
return None
|
|
|
|
|
|
def objects_have_shape_key(objects, shape_key_name):
|
|
for obj in objects:
|
|
if find_shape_key(obj, shape_key_name) is not None:
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_viseme_profile(objects):
|
|
for key_name in vars.CC4_VISEME_NAMES:
|
|
if objects_have_shape_key(objects, key_name):
|
|
return vars.CC4_VISEME_NAMES
|
|
|
|
for key_name in vars.DIRECT_VISEME_NAMES:
|
|
if objects_have_shape_key(objects, key_name):
|
|
return vars.DIRECT_VISEME_NAMES
|
|
|
|
# there is some overlap between CC4 facial expression names and CC3 viseme names
|
|
# so consider CC3 visemes last
|
|
return vars.CC3_VISEME_NAMES
|
|
|
|
|
|
def get_facial_profile(objects):
|
|
expressionProfile = "UNKNOWN"
|
|
visemeProfile = "UNKNOWN"
|
|
|
|
for obj in objects:
|
|
|
|
if obj.type != "MESH": continue
|
|
|
|
if (find_shape_key(obj, "Mouth_Funnel_UL") or
|
|
find_shape_key(obj, "Mouth_Funnel_UR") or
|
|
find_shape_key(obj, "Eye_Look_Up_L") or
|
|
find_shape_key(obj, "Eye_Look_Up_R") or
|
|
find_shape_key(obj, "Jaw_Clench_L") or
|
|
find_shape_key(obj, "Jaw_Clench_R")):
|
|
expressionProfile = "MH"
|
|
|
|
if (find_shape_key(obj, "Move_Jaw_Down") or
|
|
find_shape_key(obj, "Turn_Jaw_Down") or
|
|
find_shape_key(obj, "Move_Jaw_Down") or
|
|
find_shape_key(obj, "Move_Jaw_Down")):
|
|
expressionProfile = "TRA"
|
|
|
|
if (find_shape_key(obj, "A01_Brow_Inner_Up") or
|
|
find_shape_key(obj, "A06_Eye_Look_Up_Left") or
|
|
find_shape_key(obj, "A15_Eye_Blink_Right") or
|
|
find_shape_key(obj, "A25_Jaw_Open") or
|
|
find_shape_key(obj, "A37_Mouth_Close")):
|
|
if (expressionProfile == "UNKNOWN" or
|
|
expressionProfile == "STD"):
|
|
expressionProfile = "TRA"
|
|
|
|
if (find_shape_key(obj, "Ear_Up_L") or
|
|
find_shape_key(obj, "Ear_Up_R") or
|
|
find_shape_key(obj, "Eyelash_Upper_Up_L") or
|
|
find_shape_key(obj, "Eyelash_Upper_Up_R") or
|
|
find_shape_key(obj, "Mouth_Pucker_Up_R") or
|
|
find_shape_key(obj, "Mouth_Funnel_Up_R")):
|
|
if (expressionProfile == "UNKNOWN" or
|
|
expressionProfile == "STD"):
|
|
expressionProfile = "EXT"
|
|
|
|
if (find_shape_key(obj, "Mouth_L") or
|
|
find_shape_key(obj, "Mouth_R") or
|
|
find_shape_key(obj, "Mouth_Pucker") or
|
|
find_shape_key(obj, "Mouth_Funnel") or
|
|
find_shape_key(obj, "Eye_L_Look_L") or
|
|
find_shape_key(obj, "Eye_R_Look_R")):
|
|
if expressionProfile == "UNKNOWN":
|
|
expressionProfile = "STD"
|
|
|
|
if (find_shape_key(obj, "V_Open") or
|
|
find_shape_key(obj, "V_Tight") or
|
|
find_shape_key(obj, "V_Tongue_up") or
|
|
find_shape_key(obj, "V_Tongue_Raise")):
|
|
visemeProfile = "PAIRS4"
|
|
|
|
if (find_shape_key(obj, "Open") or
|
|
find_shape_key(obj, "Tight") or
|
|
find_shape_key(obj, "Tongue_up") or
|
|
find_shape_key(obj, "Tongue_Raise")):
|
|
if (visemeProfile == "PAIRS4" or
|
|
visemeProfile == "DIRECT"):
|
|
visemeProfile = "PAIRS3"
|
|
|
|
if (find_shape_key(obj, "AE") or
|
|
find_shape_key(obj, "EE") or
|
|
find_shape_key(obj, "Er") or
|
|
find_shape_key(obj, "Oh")):
|
|
if visemeProfile == "UNKNOWN":
|
|
visemeProfile = "DIRECT"
|
|
|
|
return expressionProfile, visemeProfile
|
|
|
|
|
|
def set_facial_profile(objects, facial_profile, viseme_profile):
|
|
for obj in objects:
|
|
if obj.type != "MESH": continue
|
|
if facial_profile != "NONE" and facial_profile != "UNKNOWN":
|
|
if (find_shape_key(obj, "Move_Jaw_Down") or
|
|
find_shape_key(obj, "Turn_Jaw_Down") or
|
|
find_shape_key(obj, "Move_Jaw_Down") or
|
|
find_shape_key(obj, "Move_Jaw_Down") or
|
|
find_shape_key(obj, "A01_Brow_Inner_Up") or
|
|
find_shape_key(obj, "A06_Eye_Look_Up_Left") or
|
|
find_shape_key(obj, "A15_Eye_Blink_Right") or
|
|
find_shape_key(obj, "A25_Jaw_Open") or
|
|
find_shape_key(obj, "A37_Mouth_Close") or
|
|
find_shape_key(obj, "Ear_Up_L") or
|
|
find_shape_key(obj, "Ear_Up_R") or
|
|
find_shape_key(obj, "Eyelash_Upper_Up_L") or
|
|
find_shape_key(obj, "Eyelash_Upper_Up_R") or
|
|
find_shape_key(obj, "Eye_L_Look_L") or
|
|
find_shape_key(obj, "Eye_R_Look_R") or
|
|
find_shape_key(obj, "Mouth_L") or
|
|
find_shape_key(obj, "Mouth_R") or
|
|
find_shape_key(obj, "Eye_Wide_L") or
|
|
find_shape_key(obj, "Eye_Wide_R") or
|
|
find_shape_key(obj, "Mouth_Smile") or
|
|
find_shape_key(obj, "Eye_Blink")):
|
|
utils.set_prop(obj, "rl_facial_profile", facial_profile)
|
|
if viseme_profile != "NONE" and viseme_profile != "UNKNOWN":
|
|
if (find_shape_key(obj, "V_Open") or
|
|
find_shape_key(obj, "V_Tight") or
|
|
find_shape_key(obj, "V_Tongue_up") or
|
|
find_shape_key(obj, "V_Tongue_Raise") or
|
|
find_shape_key(obj, "Open") or
|
|
find_shape_key(obj, "Tight") or
|
|
find_shape_key(obj, "Tongue_up") or
|
|
find_shape_key(obj, "Tongue_Raise") or
|
|
find_shape_key(obj, "AE") or
|
|
find_shape_key(obj, "EE") or
|
|
find_shape_key(obj, "Er") or
|
|
find_shape_key(obj, "Oh")):
|
|
utils.set_prop(obj, "rl_viseme_profile", viseme_profile)
|
|
|
|
|
|
def set_shading(obj, smooth=True):
|
|
if utils.object_exists_is_mesh(obj):
|
|
for poly in obj.data.polygons:
|
|
poly.use_smooth = smooth
|
|
obj.data.update()
|
|
|
|
|
|
def get_child_objects_with_vertex_groups(parent, group_names, objects = None):
|
|
if objects is None:
|
|
objects = []
|
|
|
|
for vg in parent.vertex_groups:
|
|
if vg.name in group_names:
|
|
objects.append(parent)
|
|
break
|
|
|
|
for child in parent.children:
|
|
get_child_objects_with_vertex_groups(child, group_names, objects)
|
|
|
|
return objects
|
|
|
|
|
|
def has_vertex_color_data(obj):
|
|
if obj and obj.type == "MESH":
|
|
if obj.data.vertex_colors and obj.data.vertex_colors.active:
|
|
color_map = obj.data.vertex_colors.active
|
|
for vcol_data in color_map.data:
|
|
color = vcol_data.color
|
|
for i in range(0,4):
|
|
if color[i] > 0.0:
|
|
return True
|
|
return False
|
|
|
|
|
|
def count_selected_vertices(obj):
|
|
count = 0
|
|
if bpy.context.mode == 'EDIT_MESH':
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
for v in bm.verts:
|
|
if v.select:
|
|
count += 1
|
|
else:
|
|
for v in obj.data.vertices:
|
|
if v.select:
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def separate_mesh_by_material_slots(obj: bpy.types.Object, slot_indices: list):
|
|
if obj:
|
|
if slot_indices:
|
|
if utils.edit_mode_to(obj, only_this=True):
|
|
bpy.ops.mesh.select_all(action='DESELECT')
|
|
for slot_index in slot_indices:
|
|
if len(obj.material_slots) > slot_index:
|
|
bpy.context.object.active_material_index = slot_index
|
|
bpy.ops.object.material_slot_select()
|
|
count = count_selected_vertices(obj)
|
|
if count > 0 and count < len(obj.data.vertices):
|
|
bpy.ops.mesh.separate(type="SELECTED")
|
|
if utils.object_mode():
|
|
for o in bpy.context.selected_objects:
|
|
if o != obj:
|
|
return o
|
|
return None
|
|
|
|
|
|
def separate_mesh_material_type(chr_cache, obj: bpy.types.Object, material_type: str):
|
|
if chr_cache and obj:
|
|
material_slots = []
|
|
if utils.object_exists_is_mesh(obj):
|
|
for slot in obj.material_slots:
|
|
mat = slot.material
|
|
if utils.material_exists(mat):
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
if mat_cache and mat_cache.material_type == material_type:
|
|
material_slots.append(slot.slot_index)
|
|
if material_slots:
|
|
return separate_mesh_by_material_slots(obj, material_slots)
|
|
return None
|
|
|
|
|
|
def get_head_material_and_json(chr_cache, chr_json):
|
|
head_mat = None
|
|
head_mat_cache = None
|
|
head_mat_json = None
|
|
|
|
# find the head material in the character
|
|
for mat_cache in chr_cache.head_material_cache:
|
|
mat = mat_cache.material
|
|
if mat_cache.material_type == "SKIN_HEAD" and utils.material_exists(mat):
|
|
head_mat = mat
|
|
head_mat_cache = mat_cache
|
|
|
|
# find the head material json, from it's original json object
|
|
# the head material may have been split from the original body mesh,
|
|
# so we look in all the meshes for the head material
|
|
for obj in chr_cache.get_cache_objects():
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj.type == "MESH":
|
|
if head_mat.name in obj.data.materials:
|
|
mat_json = jsonutils.get_json(chr_json, f"Meshes/{obj_cache.source_name}/Materials/{head_mat_cache.source_name}")
|
|
if mat_json and jsonutils.get_json(mat_json, "Custom Shader/Shader Name") == "RLHead":
|
|
head_mat_json = mat_json
|
|
break
|
|
|
|
return head_mat, head_mat_json
|
|
|
|
|
|
def get_head_body_object_quick(chr_cache):
|
|
if chr_cache:
|
|
body_objects = chr_cache.get_objects_of_type("BODY")
|
|
for obj in body_objects:
|
|
if "wrinkle_source" in obj:
|
|
if obj["wrinkle_source"]:
|
|
return obj
|
|
return get_head_body_object(chr_cache)
|
|
return None
|
|
|
|
|
|
def get_eye_object(chr_cache):
|
|
# TODO merged expressions and morphs....
|
|
if chr_cache:
|
|
eyes = chr_cache.get_objects_of_type("EYE")
|
|
if eyes:
|
|
return eyes[0]
|
|
return None
|
|
|
|
|
|
def get_tongue_object(chr_cache):
|
|
# TODO merged expressions and morphs....
|
|
if chr_cache:
|
|
tongues = chr_cache.get_objects_of_type("TONGUE")
|
|
if tongues:
|
|
return tongues[0]
|
|
return None
|
|
|
|
|
|
def get_teeth_object(chr_cache):
|
|
# TODO merged expressions and morphs....
|
|
if chr_cache:
|
|
teeth = chr_cache.get_objects_of_type("TEETH")
|
|
if teeth:
|
|
return teeth[0]
|
|
return None
|
|
|
|
|
|
def get_head_body_object(chr_cache):
|
|
if not chr_cache: return None
|
|
body_cache = chr_cache.get_body_cache()
|
|
arm = chr_cache.get_armature()
|
|
|
|
# collect all possible body objects together
|
|
head_bones = [ "CC_Base_Head", "head", "spine.006" ]
|
|
body_objects = {}
|
|
|
|
if body_cache:
|
|
body_id = body_cache.object_id
|
|
for child in arm.children:
|
|
if utils.get_rl_object_id(child) == body_id and child not in body_objects:
|
|
body_objects[child] = total_vertex_group_weight(child, head_bones)
|
|
else:
|
|
for child in arm.children:
|
|
if child not in body_objects:
|
|
body_objects[child] = total_vertex_group_weight(child, head_bones)
|
|
|
|
# try to find which one contains the head (contains the most weight to head bone)
|
|
weight = -1
|
|
body = None
|
|
if body_objects:
|
|
for obj in body_objects:
|
|
try:
|
|
del obj["wrinkle_source"]
|
|
except: ...
|
|
if body_objects[obj] > weight:
|
|
weight = body_objects[obj]
|
|
body = obj
|
|
|
|
# fall back to the imported source body if nothing works
|
|
if not body:
|
|
body = chr_cache.get_body()
|
|
|
|
if body:
|
|
try:
|
|
body["wrinkle_source"] = True
|
|
except: ...
|
|
|
|
return body
|
|
|
|
|
|
LASH_DATA = None
|
|
|
|
def store_lash_data(chr_cache):
|
|
global LASH_DATA
|
|
# copy body
|
|
body_obj = utils.duplicate_object(get_head_body_object(chr_cache))
|
|
head_obj = separate_mesh_material_type(chr_cache, body_obj, "SKIN_HEAD")
|
|
lash_obj = separate_mesh_material_type(chr_cache, body_obj, "EYELASH")
|
|
utils.log_always(f"HEAD: {head_obj.name}")
|
|
utils.log_always(f"LASH: {lash_obj.name}")
|
|
utils.delete_object(body_obj)
|
|
|
|
mesh = lash_obj.data
|
|
head_mesh = head_obj.data
|
|
ul = mesh.uv_layers[0]
|
|
verts_done = set()
|
|
verts = {}
|
|
i = 0
|
|
for poly in mesh.polygons:
|
|
for loop_index in poly.loop_indices:
|
|
loop_entry = mesh.loops[loop_index]
|
|
if loop_entry.vertex_index not in verts_done:
|
|
i += 1
|
|
verts_done.add(loop_entry.vertex_index)
|
|
vertex = mesh.vertices[loop_entry.vertex_index]
|
|
lash_co = vertex.co
|
|
lash_uv = ul.data[loop_entry.index].uv
|
|
uv_id = lash_uv.to_tuple(5)
|
|
success, closest_local_co, closest_local_no, closest_face_index = head_obj.closest_point_on_mesh(lash_co)
|
|
head_co = closest_local_co
|
|
dir: mathutils.Vector = (lash_co - head_co)
|
|
head_dist = dir.length
|
|
head_dir = dir.normalized()
|
|
head_uv = uv_from_point(head_mesh, head_co, closest_face_index)
|
|
verts[uv_id] = (lash_uv.copy(), head_uv.copy(), head_dist, head_dir)
|
|
utils.delete_object(lash_obj)
|
|
utils.delete_object(head_obj)
|
|
LASH_DATA = verts
|
|
|
|
|
|
def restore_lash_data(chr_cache):
|
|
diag = geom.diag_mesh_create()
|
|
global LASH_DATA
|
|
body_obj = get_head_body_object(chr_cache)
|
|
lash_index = materials.get_material_slot_by_type(chr_cache, body_obj, "EYELASH")
|
|
head_index = materials.get_material_slot_by_type(chr_cache, body_obj, "SKIN_HEAD")
|
|
body_tm = geom.get_triangulated_bmesh(body_obj)
|
|
mesh: bpy.types.Mesh = body_obj.data
|
|
ul = mesh.uv_layers[0]
|
|
verts_done = set()
|
|
poly: bpy.types.MeshPolygon
|
|
vertex: bpy.types.MeshVertex
|
|
for poly in mesh.polygons:
|
|
if poly.material_index == lash_index:
|
|
for loop_index in poly.loop_indices:
|
|
loop_entry = mesh.loops[loop_index]
|
|
if loop_entry.vertex_index not in verts_done:
|
|
verts_done.add(loop_entry.vertex_index)
|
|
vertex = mesh.vertices[loop_entry.vertex_index]
|
|
lash_uv = ul.data[loop_entry.index].uv.copy()
|
|
uv_id = lash_uv.to_tuple(5)
|
|
if uv_id in LASH_DATA:
|
|
old_lash_uv, head_uv, head_dist, head_dir = LASH_DATA[uv_id]
|
|
head_co = geom.get_local_from_uv(body_obj, body_tm, head_index, head_uv.to_3d(), 0.001)
|
|
geom.diag_mesh_add_vert(head_co)
|
|
target_co = head_co + (head_dir * head_dist)
|
|
vertex.co = target_co.copy()
|
|
geom.diag_finish()
|
|
mesh.update()
|
|
|
|
|
|
def uv_from_point(mesh: bpy.types.Mesh, co, face_index):
|
|
ul = mesh.uv_layers[0]
|
|
poly: bpy.types.MeshPolygon = mesh.polygons[face_index]
|
|
num_verts = len(poly.loop_indices)
|
|
num_tris = num_verts - 2
|
|
loop0 = mesh.loops[poly.loop_indices[0]]
|
|
v0 = mesh.vertices[loop0.vertex_index].co
|
|
uv0 = ul.data[loop0.index].uv.to_3d()
|
|
for i in range(0, num_tris):
|
|
j = i + 1
|
|
k = i + 2
|
|
loopj = mesh.loops[poly.loop_indices[j]]
|
|
vj = mesh.vertices[loopj.vertex_index].co
|
|
uvj = ul.data[loopj.index].uv.to_3d()
|
|
loopk = mesh.loops[poly.loop_indices[k]]
|
|
vk = mesh.vertices[loopk.vertex_index].co
|
|
uvk = ul.data[loopk.index].uv.to_3d()
|
|
uv = mathutils.geometry.barycentric_transform(co, v0, vj, vk, uv0, uvj, uvk)
|
|
if mathutils.geometry.intersect_point_tri_2d(uv, uv0, uvj, uvk):
|
|
uv = mathutils.Vector((uv.x, uv.y))
|
|
return uv
|
|
# otherwise return the uv coords of the face vertex nearest to the co
|
|
d = (v0 - co).length
|
|
uv = ul.data[loop0.index].uv
|
|
for i in range(1, num_verts):
|
|
loopi = mesh.loops[poly.loop_indices[i]]
|
|
vi = mesh.vertices[loopi.vertex_index].co
|
|
di = (vi - co).length
|
|
if di < d:
|
|
d = di
|
|
uv = ul.data[loopi.index].uv
|
|
return uv |