Update blendshape_utils.py
This commit is contained in:
parent
3a7ca4043d
commit
27759b19f4
@ -623,3 +623,314 @@ class BlendShapeUtils:
|
||||
except Exception as e:
|
||||
cmds.warning(f"Error creating in-between target: {str(e)}")
|
||||
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 {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user