469 lines
17 KiB
Python
469 lines
17 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
|
|
|
|
from . import materials, 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 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_left, mat_right):
|
|
|
|
vertex_group_inner_l = add_vertex_group(obj, vars.TEARLINE_GROUP_INNER + "_L")
|
|
vertex_group_all_l = add_vertex_group(obj, vars.TEARLINE_GROUP_ALL + "_L")
|
|
vertex_group_inner_r = add_vertex_group(obj, vars.TEARLINE_GROUP_INNER + "_R")
|
|
vertex_group_all_r = add_vertex_group(obj, vars.TEARLINE_GROUP_ALL + "_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
|
|
weight = 1.0 - utils.smoothstep(0, 0.1, abs(uv.x - 0.5))
|
|
|
|
slot = obj.material_slots[poly.material_index]
|
|
if slot.material == mat_left:
|
|
vertex_group_inner_l.add([vertex.index], weight, 'REPLACE')
|
|
vertex_group_all_l.add([vertex.index], 1.0, 'REPLACE')
|
|
|
|
elif slot.material == mat_right:
|
|
vertex_group_inner_r.add([vertex.index], weight, 'REPLACE')
|
|
vertex_group_all_r.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:
|
|
iris_scale = cache_left.parameters.eye_iris_scale
|
|
iris_radius = cache_left.parameters.eye_iris_radius
|
|
depth_radius = cache_left.parameters.eye_iris_depth_radius
|
|
radius = iris_scale * iris_radius * depth_radius
|
|
#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:
|
|
iris_scale = cache_right.parameters.eye_iris_scale
|
|
iris_radius = cache_right.parameters.eye_iris_radius
|
|
depth_radius = cache_right.parameters.eye_iris_depth_radius
|
|
radius = iris_scale * iris_radius * depth_radius
|
|
#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):
|
|
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 = "None"
|
|
visemeProfile = "None"
|
|
|
|
for obj in objects:
|
|
|
|
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 = "Traditional"
|
|
|
|
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 == "None" or
|
|
expressionProfile == "Traditional"):
|
|
expressionProfile = "ExPlus"
|
|
|
|
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, "Eye_L_Look_L") or
|
|
find_shape_key(obj, "Eye_R_Look_R")):
|
|
if (expressionProfile == "None" or
|
|
expressionProfile == "Std"):
|
|
expressionProfile = "Ext"
|
|
|
|
if (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")):
|
|
if expressionProfile == "None":
|
|
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 = "PairsCC4"
|
|
|
|
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 == "PairsCC4" or
|
|
visemeProfile == "Direct"):
|
|
visemeProfile = "PairsCC3"
|
|
|
|
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 == "None":
|
|
visemeProfile = "Direct"
|
|
|
|
if (find_shape_key(obj, "Brow_Raise_Inner_Left") or
|
|
find_shape_key(obj, "Brow_Raise_Outer_Left") or
|
|
find_shape_key(obj, "Brow_Drop_Left") or
|
|
find_shape_key(obj, "Brow_Raise_Right")):
|
|
corrections = True
|
|
|
|
return expressionProfile, visemeProfile
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|