1068 lines
36 KiB
Python
1068 lines
36 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Tool - DNA Utilities
|
|
Provides utilities for working with DNA files and data
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import maya.cmds as cmds
|
|
import maya.mel as mel
|
|
|
|
# Import configuration
|
|
import config
|
|
|
|
class DNAUtils:
|
|
"""Utilities for working with DNA files and data"""
|
|
|
|
def __init__(self):
|
|
"""Initialize DNA utilities"""
|
|
self.pydna_available = False
|
|
self.pydna_error = None
|
|
self.dna_calib_available = False
|
|
self.dna_calib_error = None
|
|
self.current_dna = None
|
|
self.current_dna_file = None
|
|
self.current_dna_version = None
|
|
|
|
# Ensure pydna is in the Python path
|
|
if config.PYDNA_PATH not in sys.path:
|
|
sys.path.append(config.PYDNA_PATH)
|
|
|
|
# Ensure DNA Calibration tools are in the Python path
|
|
if config.DNA_CALIB_PATH not in sys.path:
|
|
sys.path.append(config.DNA_CALIB_PATH)
|
|
|
|
# Try to import pydna modules
|
|
try:
|
|
# First try to import from the specific version directory
|
|
import pydna
|
|
self.pydna_available = True
|
|
print(f"PyDNA module loaded successfully: {config.PYDNA_PATH}")
|
|
except ImportError as e:
|
|
self.pydna_error = str(e)
|
|
# Try to find pydna in alternative locations
|
|
try:
|
|
# Try parent directory
|
|
parent_dir = os.path.dirname(config.PYDNA_PATH)
|
|
if parent_dir not in sys.path:
|
|
sys.path.append(parent_dir)
|
|
import pydna
|
|
self.pydna_available = True
|
|
print(f"PyDNA module loaded successfully from alternative location: {parent_dir}")
|
|
except ImportError:
|
|
self.pydna_available = False
|
|
cmds.warning(f"PyDNA module not found, some DNA features may be unavailable: {self.pydna_error}")
|
|
|
|
# Try to import DNA Calibration modules
|
|
try:
|
|
# Import DNA Calibration modules
|
|
import dna_calibration
|
|
self.dna_calib_available = True
|
|
print(f"DNA Calibration module loaded successfully: {config.DNA_CALIB_PATH}")
|
|
except ImportError as e:
|
|
self.dna_calib_error = str(e)
|
|
self.dna_calib_available = False
|
|
cmds.warning(f"DNA Calibration module not found, calibration features may be unavailable: {self.dna_calib_error}")
|
|
|
|
def load_dna(self, file_path):
|
|
"""
|
|
Load a DNA file into the scene
|
|
|
|
Args:
|
|
file_path (str): Path to the DNA file
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not os.path.exists(file_path):
|
|
error_msg = f"DNA file not found: {file_path}"
|
|
cmds.warning(error_msg)
|
|
cmds.confirmDialog(
|
|
title="DNA loading error",
|
|
message=error_msg,
|
|
button=["OK"],
|
|
defaultButton="OK"
|
|
)
|
|
return False
|
|
|
|
if not self.pydna_available:
|
|
error_msg = "PyDNA module not found, cannot load DNA file"
|
|
if self.pydna_error:
|
|
error_msg += f"\nError details: {self.pydna_error}"
|
|
|
|
cmds.warning(error_msg)
|
|
cmds.confirmDialog(
|
|
title="PyDNA module loading error",
|
|
message=error_msg,
|
|
button=["OK"],
|
|
defaultButton="OK"
|
|
)
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_reader import DNAReader
|
|
|
|
# Read DNA file
|
|
reader = DNAReader()
|
|
dna = reader.read(file_path)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to read DNA file: {file_path}")
|
|
return False
|
|
|
|
# Create mesh from DNA
|
|
mesh_name = os.path.splitext(os.path.basename(file_path))[0]
|
|
result = self._create_mesh_from_dna(dna, mesh_name)
|
|
|
|
if not result:
|
|
cmds.warning(f"Unable to create mesh from DNA: {file_path}")
|
|
return False
|
|
|
|
# Create skeleton from DNA
|
|
result = self._create_skeleton_from_dna(dna, mesh_name)
|
|
|
|
if not result:
|
|
cmds.warning(f"Unable to create skeleton from DNA: {file_path}")
|
|
return False
|
|
|
|
print(f"Successfully loaded DNA file: {file_path}")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error loading DNA file: {str(e)}")
|
|
return False
|
|
|
|
def save_dna(self, file_path):
|
|
"""
|
|
Save the current scene as a DNA file
|
|
|
|
Args:
|
|
file_path (str): Path to save the DNA file
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot save DNA file")
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_writer import DNAWriter
|
|
|
|
# Get selected mesh and skeleton
|
|
selection = cmds.ls(selection=True)
|
|
if not selection:
|
|
cmds.warning("Please select a mesh or skeleton")
|
|
return False
|
|
|
|
# Find mesh and skeleton
|
|
mesh = None
|
|
skeleton = None
|
|
|
|
for obj in selection:
|
|
if cmds.objectType(obj) == "transform":
|
|
shapes = cmds.listRelatives(obj, shapes=True)
|
|
if shapes and cmds.objectType(shapes[0]) == "mesh":
|
|
mesh = obj
|
|
elif cmds.objectType(obj) == "joint":
|
|
# Find root joint
|
|
root = obj
|
|
parent = cmds.listRelatives(root, parent=True)
|
|
while parent and cmds.objectType(parent[0]) == "joint":
|
|
root = parent[0]
|
|
parent = cmds.listRelatives(root, parent=True)
|
|
skeleton = root
|
|
|
|
if not mesh:
|
|
cmds.warning("Mesh not found")
|
|
return False
|
|
|
|
if not skeleton:
|
|
cmds.warning("Skeleton not found")
|
|
return False
|
|
|
|
# Create DNA from mesh and skeleton
|
|
dna = self._create_dna_from_scene(mesh, skeleton)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to create DNA from scene")
|
|
return False
|
|
|
|
# Write DNA file
|
|
writer = DNAWriter()
|
|
result = writer.write(dna, file_path)
|
|
|
|
if not result:
|
|
cmds.warning(f"Unable to write DNA file: {file_path}")
|
|
return False
|
|
|
|
print(f"Successfully saved DNA file: {file_path}")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error saving DNA file: {str(e)}")
|
|
return False
|
|
|
|
def create_dna(self, mesh, skeleton):
|
|
"""
|
|
Create a DNA from a mesh and skeleton
|
|
|
|
Args:
|
|
mesh (str): Name of the mesh
|
|
skeleton (str): Name of the skeleton
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot create DNA")
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_creator import DNACreator
|
|
|
|
# Check if mesh and skeleton exist
|
|
if not cmds.objExists(mesh):
|
|
cmds.warning(f"Mesh not found: {mesh}")
|
|
return False
|
|
|
|
if not cmds.objExists(skeleton):
|
|
cmds.warning(f"Skeleton not found: {skeleton}")
|
|
return False
|
|
|
|
# Create DNA
|
|
creator = DNACreator()
|
|
dna = creator.create(mesh, skeleton)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to create DNA")
|
|
return False
|
|
|
|
# Store DNA in scene
|
|
self._store_dna_in_scene(dna, mesh)
|
|
|
|
print(f"Successfully created DNA from {mesh} and {skeleton}")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating DNA: {str(e)}")
|
|
return False
|
|
|
|
def update_dna(self, mesh, skeleton):
|
|
"""
|
|
Update a DNA from a mesh and skeleton
|
|
|
|
Args:
|
|
mesh (str): Name of the mesh
|
|
skeleton (str): Name of the skeleton
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot update DNA")
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_updater import DNAUpdater
|
|
|
|
# Check if mesh and skeleton exist
|
|
if not cmds.objExists(mesh):
|
|
cmds.warning(f"Mesh not found: {mesh}")
|
|
return False
|
|
|
|
if not cmds.objExists(skeleton):
|
|
cmds.warning(f"Skeleton not found: {skeleton}")
|
|
return False
|
|
|
|
# Get DNA from scene
|
|
dna = self._get_dna_from_scene(mesh)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to find DNA data, please create DNA first")
|
|
return False
|
|
|
|
# Update DNA
|
|
updater = DNAUpdater()
|
|
result = updater.update(dna, mesh, skeleton)
|
|
|
|
if not result:
|
|
cmds.warning(f"Unable to update DNA")
|
|
return False
|
|
|
|
# Store updated DNA in scene
|
|
self._store_dna_in_scene(dna, mesh)
|
|
|
|
print(f"Successfully updated DNA")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error updating DNA: {str(e)}")
|
|
return False
|
|
|
|
def export_dna(self, file_path):
|
|
"""
|
|
Export DNA to a file
|
|
|
|
Args:
|
|
file_path (str): Path to export the DNA file
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot export DNA")
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_writer import DNAWriter
|
|
|
|
# Get selected mesh
|
|
selection = cmds.ls(selection=True)
|
|
if not selection:
|
|
cmds.warning("Please select a mesh")
|
|
return False
|
|
|
|
mesh = None
|
|
for obj in selection:
|
|
if cmds.objectType(obj) == "transform":
|
|
shapes = cmds.listRelatives(obj, shapes=True)
|
|
if shapes and cmds.objectType(shapes[0]) == "mesh":
|
|
mesh = obj
|
|
break
|
|
|
|
if not mesh:
|
|
cmds.warning("Mesh not found")
|
|
return False
|
|
|
|
# Get DNA from scene
|
|
dna = self._get_dna_from_scene(mesh)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to find DNA data, please create DNA first")
|
|
return False
|
|
|
|
# Write DNA file
|
|
writer = DNAWriter()
|
|
result = writer.write(dna, file_path)
|
|
|
|
if not result:
|
|
cmds.warning(f"Unable to write DNA file: {file_path}")
|
|
return False
|
|
|
|
print(f"Successfully exported DNA file: {file_path}")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error exporting DNA file: {str(e)}")
|
|
return False
|
|
|
|
def get_dna_attributes(self, dna_file=None):
|
|
"""
|
|
Get DNA attributes
|
|
|
|
Args:
|
|
dna_file (str, optional): Path to the DNA file. If None, get from scene.
|
|
|
|
Returns:
|
|
dict: Dictionary of DNA attributes
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot get DNA attributes")
|
|
return {}
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
|
|
# Get DNA
|
|
dna = None
|
|
|
|
if dna_file and os.path.exists(dna_file):
|
|
# Read DNA file
|
|
from pydna.dna_reader import DNAReader
|
|
reader = DNAReader()
|
|
dna = reader.read(dna_file)
|
|
else:
|
|
# Get from scene
|
|
selection = cmds.ls(selection=True)
|
|
if not selection:
|
|
cmds.warning("Please select a mesh or load a DNA file")
|
|
return {}
|
|
|
|
mesh = None
|
|
for obj in selection:
|
|
if cmds.objectType(obj) == "transform":
|
|
shapes = cmds.listRelatives(obj, shapes=True)
|
|
if shapes and cmds.objectType(shapes[0]) == "mesh":
|
|
mesh = obj
|
|
break
|
|
|
|
if not mesh:
|
|
cmds.warning("Mesh not found")
|
|
return {}
|
|
|
|
dna = self._get_dna_from_scene(mesh)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to find DNA data")
|
|
return {}
|
|
|
|
# Get attributes
|
|
attributes = {}
|
|
|
|
# General attributes
|
|
attributes["general.name"] = dna.getName()
|
|
attributes["general.version"] = dna.getVersion()
|
|
attributes["general.topology"] = dna.getTopology()
|
|
|
|
# Mesh attributes
|
|
mesh_count = dna.getMeshCount()
|
|
attributes["mesh.count"] = mesh_count
|
|
|
|
for i in range(mesh_count):
|
|
mesh = dna.getMesh(i)
|
|
attributes[f"mesh.{i}.name"] = mesh.getName()
|
|
attributes[f"mesh.{i}.vertex_count"] = mesh.getVertexCount()
|
|
attributes[f"mesh.{i}.triangle_count"] = mesh.getTriangleCount()
|
|
|
|
# Joint attributes
|
|
joint_count = dna.getJointCount()
|
|
attributes["joint.count"] = joint_count
|
|
|
|
for i in range(joint_count):
|
|
joint = dna.getJoint(i)
|
|
attributes[f"joint.{i}.name"] = joint.getName()
|
|
attributes[f"joint.{i}.parent"] = joint.getParent()
|
|
|
|
# BlendShape attributes
|
|
blendshape_count = dna.getBlendShapeCount()
|
|
attributes["blendshape.count"] = blendshape_count
|
|
|
|
for i in range(blendshape_count):
|
|
blendshape = dna.getBlendShape(i)
|
|
attributes[f"blendshape.{i}.name"] = blendshape.getName()
|
|
attributes[f"blendshape.{i}.target_count"] = blendshape.getTargetCount()
|
|
|
|
return attributes
|
|
except Exception as e:
|
|
cmds.warning(f"Error getting DNA attributes: {str(e)}")
|
|
return {}
|
|
|
|
def set_dna_attribute(self, attr_name, value):
|
|
"""
|
|
Set a DNA attribute
|
|
|
|
Args:
|
|
attr_name (str): Name of the attribute
|
|
value (any): Value to set
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
if not self.pydna_available:
|
|
cmds.warning("PyDNA module not found, cannot set DNA attribute")
|
|
return False
|
|
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
|
|
# Get DNA from scene
|
|
selection = cmds.ls(selection=True)
|
|
if not selection:
|
|
cmds.warning("Please select a mesh")
|
|
return False
|
|
|
|
mesh = None
|
|
for obj in selection:
|
|
if cmds.objectType(obj) == "transform":
|
|
shapes = cmds.listRelatives(obj, shapes=True)
|
|
if shapes and cmds.objectType(shapes[0]) == "mesh":
|
|
mesh = obj
|
|
break
|
|
|
|
if not mesh:
|
|
cmds.warning("Mesh not found")
|
|
return False
|
|
|
|
dna = self._get_dna_from_scene(mesh)
|
|
|
|
if not dna:
|
|
cmds.warning(f"Unable to find DNA data, please create DNA first")
|
|
return False
|
|
|
|
# Set attribute
|
|
# Parse attribute name
|
|
parts = attr_name.split(".")
|
|
|
|
if len(parts) < 2:
|
|
cmds.warning(f"Invalid attribute name: {attr_name}")
|
|
return False
|
|
|
|
category = parts[0]
|
|
|
|
if category == "general":
|
|
if parts[1] == "name":
|
|
dna.setName(value)
|
|
elif parts[1] == "version":
|
|
dna.setVersion(value)
|
|
elif parts[1] == "topology":
|
|
dna.setTopology(value)
|
|
elif category == "mesh" and len(parts) >= 3:
|
|
try:
|
|
mesh_index = int(parts[1])
|
|
mesh = dna.getMesh(mesh_index)
|
|
|
|
if parts[2] == "name":
|
|
mesh.setName(value)
|
|
except (ValueError, IndexError):
|
|
cmds.warning(f"Invalid mesh index: {parts[1]}")
|
|
return False
|
|
elif category == "joint" and len(parts) >= 3:
|
|
try:
|
|
joint_index = int(parts[1])
|
|
joint = dna.getJoint(joint_index)
|
|
|
|
if parts[2] == "name":
|
|
joint.setName(value)
|
|
except (ValueError, IndexError):
|
|
cmds.warning(f"Invalid joint index: {parts[1]}")
|
|
return False
|
|
elif category == "blendshape" and len(parts) >= 3:
|
|
try:
|
|
blendshape_index = int(parts[1])
|
|
blendshape = dna.getBlendShape(blendshape_index)
|
|
|
|
if parts[2] == "name":
|
|
blendshape.setName(value)
|
|
except (ValueError, IndexError):
|
|
cmds.warning(f"Invalid BlendShape index: {parts[1]}")
|
|
return False
|
|
else:
|
|
cmds.warning(f"Unsupported attribute category: {category}")
|
|
return False
|
|
|
|
# Store updated DNA in scene
|
|
self._store_dna_in_scene(dna, mesh)
|
|
|
|
print(f"Successfully set DNA attribute: {attr_name} = {value}")
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error setting DNA attribute: {str(e)}")
|
|
return False
|
|
|
|
def _create_mesh_from_dna(self, dna, name):
|
|
"""
|
|
Create a mesh from DNA
|
|
|
|
Args:
|
|
dna: DNA object
|
|
name (str): Name for the mesh
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
try:
|
|
# Get mesh data from DNA
|
|
mesh_count = dna.getMeshCount()
|
|
|
|
if mesh_count == 0:
|
|
cmds.warning("No mesh data in DNA")
|
|
return False
|
|
|
|
# Create mesh for each DNA mesh
|
|
for i in range(mesh_count):
|
|
dna_mesh = dna.getMesh(i)
|
|
mesh_name = f"{name}_mesh_{i}"
|
|
|
|
# Get vertices
|
|
vertex_count = dna_mesh.getVertexCount()
|
|
vertices = []
|
|
|
|
for j in range(vertex_count):
|
|
pos = dna_mesh.getVertexPosition(j)
|
|
vertices.append((pos.x, pos.y, pos.z))
|
|
|
|
# Get triangles
|
|
triangle_count = dna_mesh.getTriangleCount()
|
|
triangles = []
|
|
|
|
for j in range(triangle_count):
|
|
tri = dna_mesh.getTriangle(j)
|
|
triangles.append((tri.v1, tri.v2, tri.v3))
|
|
|
|
# Create mesh
|
|
mesh = cmds.polyCreateFacet(
|
|
name=mesh_name,
|
|
p=vertices,
|
|
f=triangles
|
|
)[0]
|
|
|
|
# Get UVs
|
|
uv_count = dna_mesh.getUVCount()
|
|
|
|
if uv_count > 0:
|
|
# Create UV set
|
|
cmds.polyUVSet(mesh, create=True, uvSet="map1")
|
|
|
|
# Set UVs
|
|
for j in range(uv_count):
|
|
uv = dna_mesh.getUV(j)
|
|
cmds.polyEditUV(f"{mesh}.map1[{j}]", u=uv.u, v=uv.v)
|
|
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating mesh: {str(e)}")
|
|
return False
|
|
|
|
def _create_skeleton_from_dna(self, dna, name):
|
|
"""
|
|
Create a skeleton from DNA
|
|
|
|
Args:
|
|
dna: DNA object
|
|
name (str): Name for the skeleton
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
try:
|
|
# Get joint data from DNA
|
|
joint_count = dna.getJointCount()
|
|
|
|
if joint_count == 0:
|
|
cmds.warning("No joint data in DNA")
|
|
return False
|
|
|
|
# Create joints
|
|
joints = {}
|
|
|
|
for i in range(joint_count):
|
|
dna_joint = dna.getJoint(i)
|
|
joint_name = dna_joint.getName()
|
|
parent_index = dna_joint.getParent()
|
|
|
|
# Get joint position
|
|
pos = dna_joint.getPosition()
|
|
|
|
# Create joint
|
|
joint = cmds.joint(name=joint_name, p=(pos.x, pos.y, pos.z))
|
|
joints[i] = joint
|
|
|
|
# Set joint orientation
|
|
orient = dna_joint.getOrientation()
|
|
cmds.setAttr(f"{joint}.rotateX", orient.x)
|
|
cmds.setAttr(f"{joint}.rotateY", orient.y)
|
|
cmds.setAttr(f"{joint}.rotateZ", orient.z)
|
|
|
|
# Set parent
|
|
if parent_index >= 0:
|
|
parent_joint = joints.get(parent_index)
|
|
if parent_joint:
|
|
cmds.parent(joint, parent_joint)
|
|
|
|
# Rename root joint
|
|
root_joint = joints.get(0)
|
|
if root_joint:
|
|
cmds.rename(root_joint, f"{name}_root")
|
|
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating skeleton: {str(e)}")
|
|
return False
|
|
|
|
def _create_dna_from_scene(self, mesh, skeleton):
|
|
"""
|
|
Create a DNA from scene objects
|
|
|
|
Args:
|
|
mesh (str): Name of the mesh
|
|
skeleton (str): Name of the skeleton
|
|
|
|
Returns:
|
|
object: DNA object or None if failed
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_creator import DNACreator
|
|
|
|
# Create DNA
|
|
creator = DNACreator()
|
|
dna = creator.create(mesh, skeleton)
|
|
|
|
return dna
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating DNA from scene: {str(e)}")
|
|
return None
|
|
|
|
def _store_dna_in_scene(self, dna, mesh):
|
|
"""
|
|
Store DNA in the scene as mesh attributes
|
|
|
|
Args:
|
|
dna: DNA object
|
|
mesh (str): Name of the mesh
|
|
|
|
Returns:
|
|
bool: True if successful, False otherwise
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_writer import DNAWriter
|
|
|
|
# Serialize DNA to JSON
|
|
writer = DNAWriter()
|
|
dna_json = writer.toJSON(dna)
|
|
|
|
# Store as mesh attribute
|
|
if not cmds.attributeQuery("dnaData", node=mesh, exists=True):
|
|
cmds.addAttr(mesh, longName="dnaData", dataType="string")
|
|
|
|
cmds.setAttr(f"{mesh}.dnaData", dna_json, type="string")
|
|
|
|
return True
|
|
except Exception as e:
|
|
cmds.warning(f"Error storing DNA data: {str(e)}")
|
|
return False
|
|
|
|
def _get_dna_from_scene(self, mesh):
|
|
"""
|
|
Get DNA from scene
|
|
|
|
Args:
|
|
mesh (str): Name of the mesh
|
|
|
|
Returns:
|
|
object: DNA object or None if not found
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_reader import DNAReader
|
|
|
|
# Check if mesh has DNA data
|
|
if not cmds.attributeQuery("dnaData", node=mesh, exists=True):
|
|
return None
|
|
|
|
# Get DNA data
|
|
dna_json = cmds.getAttr(f"{mesh}.dnaData")
|
|
|
|
if not dna_json:
|
|
return None
|
|
|
|
# Deserialize DNA from JSON
|
|
reader = DNAReader()
|
|
dna = reader.fromJSON(dna_json)
|
|
|
|
# Store the current DNA
|
|
self.current_dna = dna
|
|
self.current_dna_version = self._detect_dna_version(dna)
|
|
|
|
return dna
|
|
except Exception as e:
|
|
cmds.warning(f"Error getting DNA data: {str(e)}")
|
|
return None
|
|
|
|
def detect_dna_version(self, dna_file):
|
|
"""
|
|
Detect the version of a DNA file
|
|
|
|
Args:
|
|
dna_file (str): Path to the DNA file
|
|
|
|
Returns:
|
|
str: DNA version (e.g., 'MH.4', 'MH.3', 'Custom')
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_reader import DNAReader
|
|
|
|
# Read DNA file
|
|
reader = DNAReader()
|
|
dna = reader.read(dna_file)
|
|
|
|
if not dna:
|
|
return "Unknown"
|
|
|
|
return self._detect_dna_version(dna)
|
|
except Exception as e:
|
|
cmds.warning(f"Error detecting DNA version: {str(e)}")
|
|
return "Unknown"
|
|
|
|
def _detect_dna_version(self, dna):
|
|
"""
|
|
Detect the version of a DNA object
|
|
|
|
Args:
|
|
dna: DNA object
|
|
|
|
Returns:
|
|
str: DNA version (e.g., 'MH.4', 'MH.3', 'Custom')
|
|
"""
|
|
try:
|
|
# Get DNA metadata
|
|
metadata = dna.getMetaData()
|
|
|
|
if not metadata:
|
|
return "Unknown"
|
|
|
|
# Check for MetaHuman version
|
|
if "metahuman" in metadata.lower():
|
|
if "4." in metadata:
|
|
return "MH.4"
|
|
elif "3." in metadata:
|
|
return "MH.3"
|
|
else:
|
|
return "MetaHuman"
|
|
|
|
# Check for specific markers
|
|
joint_count = dna.getJointCount()
|
|
mesh_count = dna.getMeshCount()
|
|
|
|
# MetaHuman 4 typically has 411 joints
|
|
if joint_count >= 400 and joint_count <= 420:
|
|
return "MH.4"
|
|
# MetaHuman 3 typically has 245 joints
|
|
elif joint_count >= 240 and joint_count <= 260:
|
|
return "MH.3"
|
|
else:
|
|
return "Custom"
|
|
except Exception as e:
|
|
cmds.warning(f"Error detecting DNA version: {str(e)}")
|
|
return "Unknown"
|
|
|
|
def validate_dna(self, dna_file=None):
|
|
"""
|
|
Validate a DNA file structure
|
|
|
|
Args:
|
|
dna_file (str, optional): Path to the DNA file. If None, validate current DNA.
|
|
|
|
Returns:
|
|
dict: Validation results with issues and warnings
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_reader import DNAReader
|
|
|
|
# Get DNA object
|
|
dna = None
|
|
if dna_file:
|
|
reader = DNAReader()
|
|
dna = reader.read(dna_file)
|
|
else:
|
|
dna = self.current_dna
|
|
|
|
if not dna:
|
|
return {"valid": False, "errors": ["No DNA data available"], "warnings": []}
|
|
|
|
# Initialize validation results
|
|
results = {
|
|
"valid": True,
|
|
"errors": [],
|
|
"warnings": [],
|
|
"info": {}
|
|
}
|
|
|
|
# Check basic structure
|
|
joint_count = dna.getJointCount()
|
|
mesh_count = dna.getMeshCount()
|
|
blendshape_count = dna.getBlendShapeCount() if hasattr(dna, "getBlendShapeCount") else 0
|
|
|
|
results["info"] = {
|
|
"version": self._detect_dna_version(dna),
|
|
"joint_count": joint_count,
|
|
"mesh_count": mesh_count,
|
|
"blendshape_count": blendshape_count
|
|
}
|
|
|
|
# Validate joints
|
|
if joint_count == 0:
|
|
results["errors"].append("No joints found in DNA")
|
|
results["valid"] = False
|
|
|
|
# Validate meshes
|
|
if mesh_count == 0:
|
|
results["errors"].append("No meshes found in DNA")
|
|
results["valid"] = False
|
|
|
|
# Check for root joint
|
|
root_joint = None
|
|
try:
|
|
root_joint = dna.getJoint(0)
|
|
except:
|
|
results["errors"].append("No root joint found")
|
|
results["valid"] = False
|
|
|
|
# Check for MetaHuman compatibility
|
|
version = results["info"]["version"]
|
|
if version in ["MH.4", "MH.3", "MetaHuman"]:
|
|
# Check for specific MetaHuman joints
|
|
mh_joints = ["head", "neck", "spine", "pelvis"]
|
|
found_joints = []
|
|
|
|
for i in range(joint_count):
|
|
joint = dna.getJoint(i)
|
|
joint_name = joint.getName().lower()
|
|
|
|
for mh_joint in mh_joints:
|
|
if mh_joint in joint_name and mh_joint not in found_joints:
|
|
found_joints.append(mh_joint)
|
|
|
|
missing_joints = [j for j in mh_joints if j not in found_joints]
|
|
if missing_joints:
|
|
results["warnings"].append(f"Missing expected MetaHuman joints: {', '.join(missing_joints)}")
|
|
|
|
# Set final validity
|
|
results["valid"] = len(results["errors"]) == 0
|
|
|
|
return results
|
|
except Exception as e:
|
|
cmds.warning(f"Error validating DNA: {str(e)}")
|
|
return {"valid": False, "errors": [str(e)], "warnings": []}
|
|
|
|
def extract_blendshapes_from_dna(self, output_dir, dna_file=None):
|
|
"""
|
|
Extract blendshapes from DNA file
|
|
|
|
Args:
|
|
output_dir (str): Directory to save extracted blendshapes
|
|
dna_file (str, optional): Path to the DNA file. If None, use current DNA.
|
|
|
|
Returns:
|
|
list: List of extracted blendshape file paths
|
|
"""
|
|
try:
|
|
# Import pydna modules
|
|
import pydna
|
|
from pydna.dna_reader import DNAReader
|
|
|
|
# Get DNA object
|
|
dna = None
|
|
if dna_file:
|
|
reader = DNAReader()
|
|
dna = reader.read(dna_file)
|
|
else:
|
|
dna = self.current_dna
|
|
|
|
if not dna:
|
|
cmds.warning("No DNA data available for extracting blendshapes")
|
|
return []
|
|
|
|
# Check if output directory exists, if not create it
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
# Get blendshape count
|
|
blendshape_count = dna.getBlendShapeCount() if hasattr(dna, "getBlendShapeCount") else 0
|
|
|
|
if blendshape_count == 0:
|
|
cmds.warning("No blendshapes found in DNA")
|
|
return []
|
|
|
|
# Extract blendshapes
|
|
extracted_files = []
|
|
base_mesh = None
|
|
|
|
# Create base mesh
|
|
try:
|
|
mesh_name = "temp_dna_base_mesh"
|
|
base_mesh = self._create_mesh_from_dna(dna, mesh_name)
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating base mesh: {str(e)}")
|
|
return []
|
|
|
|
# Extract each blendshape
|
|
for i in range(blendshape_count):
|
|
try:
|
|
blendshape = dna.getBlendShape(i)
|
|
blendshape_name = blendshape.getName()
|
|
|
|
# Create blendshape mesh
|
|
bs_mesh_name = f"temp_bs_{blendshape_name}"
|
|
self._create_blendshape_mesh(dna, i, bs_mesh_name)
|
|
|
|
# Export blendshape mesh
|
|
output_file = os.path.join(output_dir, f"{blendshape_name}.obj")
|
|
cmds.select(bs_mesh_name)
|
|
cmds.file(output_file, force=True, exportSelected=True, type="OBJexport")
|
|
|
|
# Delete temporary mesh
|
|
cmds.delete(bs_mesh_name)
|
|
|
|
extracted_files.append(output_file)
|
|
except Exception as e:
|
|
cmds.warning(f"Error extracting blendshape {i}: {str(e)}")
|
|
|
|
# Clean up
|
|
if base_mesh and cmds.objExists(base_mesh):
|
|
cmds.delete(base_mesh)
|
|
|
|
return extracted_files
|
|
except Exception as e:
|
|
cmds.warning(f"Error extracting blendshapes: {str(e)}")
|
|
return []
|
|
|
|
def _create_blendshape_mesh(self, dna, blendshape_index, name):
|
|
"""
|
|
Create a mesh for a specific blendshape
|
|
|
|
Args:
|
|
dna: DNA object
|
|
blendshape_index (int): Index of the blendshape
|
|
name (str): Name for the blendshape mesh
|
|
|
|
Returns:
|
|
str: Name of the created mesh or None if failed
|
|
"""
|
|
try:
|
|
# Get blendshape
|
|
blendshape = dna.getBlendShape(blendshape_index)
|
|
|
|
# Get base mesh
|
|
mesh_index = 0 # Usually the first mesh
|
|
dna_mesh = dna.getMesh(mesh_index)
|
|
|
|
# Get vertices and topology
|
|
vertex_count = dna_mesh.getVertexCount()
|
|
vertices = []
|
|
|
|
# Apply blendshape deltas to base vertices
|
|
for i in range(vertex_count):
|
|
base_pos = dna_mesh.getVertex(i)
|
|
delta = blendshape.getVertexDelta(i)
|
|
|
|
# Apply delta
|
|
pos = [base_pos.x + delta.x, base_pos.y + delta.y, base_pos.z + delta.z]
|
|
vertices.append(pos)
|
|
|
|
# Get face topology
|
|
face_count = dna_mesh.getFaceCount()
|
|
faces = []
|
|
|
|
for i in range(face_count):
|
|
face = dna_mesh.getFace(i)
|
|
faces.append([face.v1, face.v2, face.v3])
|
|
|
|
# Create mesh
|
|
mesh = cmds.polyCreateFacet(name=name, point=vertices, face=faces)[0]
|
|
|
|
return mesh
|
|
except Exception as e:
|
|
cmds.warning(f"Error creating blendshape mesh: {str(e)}")
|
|
return None
|