Update blendshape_utils.py
This commit is contained in:
parent
3a7ca4043d
commit
27759b19f4
@ -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 {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user