Update blendshape_utils.py

This commit is contained in:
Jeffreytsai1004 2025-04-18 01:17:17 +08:00
parent 3a7ca4043d
commit 27759b19f4

View File

@ -623,3 +623,314 @@ class BlendShapeUtils:
except Exception as e: except Exception as e:
cmds.warning(f"Error creating in-between target: {str(e)}") cmds.warning(f"Error creating in-between target: {str(e)}")
return False return False
def categorize_targets(self, blendshape):
"""
Categorize BlendShape targets based on MetaHuman categories
Args:
blendshape (str): Name of the BlendShape node
Returns:
dict: Dictionary of categorized targets
"""
try:
# Check if BlendShape exists
if not cmds.objExists(blendshape):
cmds.warning(f"BlendShape node not found: {blendshape}")
return {}
# Get all targets
targets = self.get_targets(blendshape)
if not targets:
return {}
# Initialize categories
categorized = {
"FACS": [],
"Expressions": [],
"Phonemes": [],
"Other": []
}
# Categorize targets
for target in targets:
categorized_flag = False
# Check each category
for category, category_targets in self.mh_blendshape_categories.items():
for cat_target in category_targets:
if cat_target.lower() in target.lower():
categorized[category].append(target)
categorized_flag = True
break
if categorized_flag:
break
# If not categorized, add to Other
if not categorized_flag:
categorized["Other"].append(target)
return categorized
except Exception as e:
cmds.warning(f"Error categorizing targets: {str(e)}")
return {}
def create_metahuman_blendshape_set(self, base_mesh, name=None):
"""
Create a standard MetaHuman BlendShape set with predefined categories
Args:
base_mesh (str): Name of the base mesh
name (str, optional): Name of the BlendShape node
Returns:
str: Name of the created BlendShape node
"""
try:
# Check if base mesh exists
if not cmds.objExists(base_mesh):
cmds.warning(f"Base mesh not found: {base_mesh}")
return None
# Create name if not provided
if not name:
name = f"{base_mesh}_metahuman_blendShape"
# Create BlendShape
blendshape = self.create_blendshape(base_mesh, None, name)
if not blendshape:
return None
# Create standard MetaHuman targets (placeholder targets)
# In a real implementation, you would create actual target meshes
# Here we're just creating the attributes
# Add FACS targets
for target in self.mh_blendshape_categories["FACS"]:
# Create a temporary duplicate of the base mesh
temp_mesh = cmds.duplicate(base_mesh, name=f"temp_{target}")[0]
# Add as target
self.add_target(blendshape, temp_mesh, target, 0.0)
# Delete temporary mesh
cmds.delete(temp_mesh)
# Store as current working blendshape
self.current_blendshape = blendshape
self.current_base_mesh = base_mesh
print(f"Successfully created MetaHuman BlendShape set: {blendshape}")
return blendshape
except Exception as e:
cmds.warning(f"Error creating MetaHuman BlendShape set: {str(e)}")
return None
def import_metahuman_blendshapes(self, blendshape, import_dir):
"""
Import MetaHuman BlendShapes from a directory with automatic categorization
Args:
blendshape (str): Name of the BlendShape node
import_dir (str): Directory to import from
Returns:
dict: Dictionary of imported targets by category
"""
try:
# Check if BlendShape exists
if not cmds.objExists(blendshape):
cmds.warning(f"BlendShape node not found: {blendshape}")
return {}
# Check if directory exists
if not os.path.exists(import_dir):
cmds.warning(f"Import directory not found: {import_dir}")
return {}
# Get all mesh files in the directory
mesh_files = []
for file in os.listdir(import_dir):
file_path = os.path.join(import_dir, file)
if os.path.isfile(file_path) and file.lower().endswith((".obj", ".fbx", ".ma", ".mb")):
mesh_files.append(file_path)
if not mesh_files:
cmds.warning(f"No mesh files found in: {import_dir}")
return {}
# Initialize import results
import_results = {
"FACS": [],
"Expressions": [],
"Phonemes": [],
"Other": []
}
# Import each file
for file_path in mesh_files:
try:
# Get file name without extension
file_name = os.path.splitext(os.path.basename(file_path))[0]
# Import mesh
if file_path.lower().endswith(".obj"):
imported_nodes = cmds.file(file_path, i=True, type="OBJ", returnNewNodes=True)
elif file_path.lower().endswith(".fbx"):
imported_nodes = cmds.file(file_path, i=True, type="FBX", returnNewNodes=True)
else: # Maya files
imported_nodes = cmds.file(file_path, i=True, returnNewNodes=True)
# Find mesh in imported nodes
imported_mesh = None
for node in imported_nodes:
if cmds.objectType(node) == "transform" and cmds.listRelatives(node, shapes=True, type="mesh"):
imported_mesh = node
break
if not imported_mesh:
cmds.warning(f"No mesh found in imported file: {file_path}")
continue
# Add as target
target_name = file_name
self.add_target(blendshape, imported_mesh, target_name, 0.0)
# Delete imported mesh
cmds.delete(imported_mesh)
# Categorize target
categorized = False
for category, category_targets in self.mh_blendshape_categories.items():
for cat_target in category_targets:
if cat_target.lower() in target_name.lower():
import_results[category].append(target_name)
categorized = True
break
if categorized:
break
# If not categorized, add to Other
if not categorized:
import_results["Other"].append(target_name)
except Exception as e:
cmds.warning(f"Error importing file {file_path}: {str(e)}")
return import_results
except Exception as e:
cmds.warning(f"Error importing MetaHuman BlendShapes: {str(e)}")
return {}
def connect_to_rig(self, blendshape, controller):
"""
Connect all BlendShape targets to a controller with organized attributes
Args:
blendshape (str): Name of the BlendShape node
controller (str): Name of the controller
Returns:
bool: True if successful, False otherwise
"""
try:
# Check if BlendShape exists
if not cmds.objExists(blendshape):
cmds.warning(f"BlendShape node not found: {blendshape}")
return False
# Check if controller exists
if not cmds.objExists(controller):
cmds.warning(f"Controller not found: {controller}")
return False
# Categorize targets
categorized = self.categorize_targets(blendshape)
if not categorized:
cmds.warning(f"No targets found in BlendShape: {blendshape}")
return False
# Create category attributes on controller
for category in categorized.keys():
if not cmds.attributeQuery(category, node=controller, exists=True):
cmds.addAttr(controller, longName=category, attributeType="enum", enumName="-----", keyable=True)
# Connect targets by category
for category, targets in categorized.items():
for target in targets:
# Create attribute if it doesn't exist
if not cmds.attributeQuery(target, node=controller, exists=True):
cmds.addAttr(controller, longName=target, attributeType="float", min=0.0, max=1.0, defaultValue=0.0, keyable=True)
# Connect attribute to BlendShape target
cmds.connectAttr(f"{controller}.{target}", f"{blendshape}.{target}", force=True)
print(f"Successfully connected {blendshape} to {controller}")
return True
except Exception as e:
cmds.warning(f"Error connecting to rig: {str(e)}")
return False
def extract_target_deltas(self, blendshape, target_name, output_file=None):
"""
Extract vertex deltas from a BlendShape target
Args:
blendshape (str): Name of the BlendShape node
target_name (str): Name of the target
output_file (str, optional): Path to save deltas to JSON
Returns:
dict: Dictionary of vertex deltas
"""
try:
# Check if BlendShape exists
if not cmds.objExists(blendshape):
cmds.warning(f"BlendShape node not found: {blendshape}")
return {}
# Get target index
target_index = self._get_target_index(blendshape, target_name)
if target_index == -1:
cmds.warning(f"Target not found: {target_name}")
return {}
# Get base mesh
base_mesh = cmds.blendShape(blendshape, query=True, geometry=True)[0]
# Get vertex count
vertex_count = cmds.polyEvaluate(base_mesh, vertex=True)
# Get deltas
deltas = {}
for i in range(vertex_count):
# Get target weight points
point_data = cmds.getAttr(f"{blendshape}.inputTarget[0].inputTargetGroup[{target_index}].targetPoints[{i}]")
# Only store non-zero deltas
if point_data[0][0] != 0 or point_data[0][1] != 0 or point_data[0][2] != 0:
deltas[i] = {
"x": point_data[0][0],
"y": point_data[0][1],
"z": point_data[0][2]
}
# Save to file if requested
if output_file:
with open(output_file, "w") as f:
json.dump({
"blendshape": blendshape,
"target": target_name,
"deltas": deltas
}, f, indent=2)
return deltas
except Exception as e:
cmds.warning(f"Error extracting target deltas: {str(e)}")
return {}