Metahuman_DNA_Calibration/dna_viewer/util/character_creator.py
2022-11-29 14:25:24 +01:00

401 lines
14 KiB
Python

import logging
from typing import Dict, List
from maya import cmds
from maya.api.OpenMaya import MObject
from ..builder.analog_gui import AnalogGui
from ..builder.gui import Gui
from ..builder.joint import Joint as JointBuilder
from ..builder.mesh import Mesh
from ..config.character import Character
from ..const.naming import (
ANALOG_GUI_HOLDER,
FACIAL_ROOT_JOINT,
FRM_MULTIPLIERS_NAME,
GEOMETRY_HOLDER_PREFIX,
GUI_HOLDER,
LOD_HOLDER_PREFIX,
LOD_HOLDER_PREFIX_UPPER,
RIG_HOLDER_PREFIX,
)
from ..model.dna import DNA
from ..model.joint import Joint as JointModel
from ..util.additional_assembly_script import AdditionalAssemblyScript
from ..util.maya_util import Maya
from ..util.rig_logic import RigLogic
from ..util.shader import Shader as ShaderUtil
class CharacterCreator:
"""
A class used for creating the character in the maya scene
Attributes
----------
@type config: Character
@param config: The character configuration containing build options
@type dna: DNA
@param dna: The DNA object read from the DNA file
@type character_name: str
@param character_name: The name of the character
@type meshes: Dict[int, List[MObject]]
@param meshes: A mapping of lod number to a list of meshes created for that lod
"""
def __init__(self, config: Character, dna: DNA) -> None:
self.config = config
self.dna = dna
self.character_name = self.dna.get_character_name()
self.meshes: Dict[int, List[MObject]] = {}
def add_mesh_to_display_layer(self, mesh_name: str, lod: int) -> None:
"""
Add the mesh with the given name to an already created display layer.
@type mesh_name: str
@param mesh_name: The name of the mesh that should be added to a display layer.
@type lod: int
@param lod: The lod value, this is needed for determining the name of the display layer that the mesh should be added to.
"""
cmds.editDisplayLayerMembers(f"{LOD_HOLDER_PREFIX_UPPER}{lod}", mesh_name)
def add_joints(self) -> List[JointModel]:
"""
Reads and adds the joints to the scene, also returns a list model objects of joints that were added.
@rtype: List[JointModel]
@returns: The list containing model objects representing the joints that were added to the scene.
"""
joints: List[JointModel] = self.dna.read_all_neutral_joints()
builder = JointBuilder(
joints,
self.config.modifiers.linear_modifier,
self.config.modifiers.angle_modifier,
)
builder.process()
return joints
def add_joints_to_character(self) -> None:
"""
Starts adding the joints the character, if the character configuration options have add_joints set to False,
this step will be skipped.
"""
if self.config.options.add_joints:
logging.info("adding joints to character...")
joints = self.add_joints()
if self.config.create_character_node:
cmds.parent(joints[0].name, self.character_name)
def create_character_node(self) -> None:
"""
Creates a Maya transform which will hold the character, if the character configuration options have
create_character_node set to False, this step will be skipped.
"""
if self.config.create_character_node:
logging.info("building character node...")
if not cmds.objExists(self.character_name):
cmds.createNode("transform", n=self.character_name)
def create_geometry_node(self) -> None:
"""
Creates a Maya transform which will hold the geometry of the character. If the character configuration
options have create_character_node set to False, this step will be skipped.
"""
if self.config.create_character_node:
logging.info("adding geometry node")
name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}"
if not cmds.objExists(name):
cmds.createNode("transform", n=name)
cmds.parent(name, self.character_name)
def create_rig_node(self) -> None:
"""
Creates a Maya transform which will hold the rig of the character. If the character configuration options
have create_character_node set to False, this step will be skipped.
"""
if self.config.create_character_node:
logging.info("adding rig node")
char_name = f"{RIG_HOLDER_PREFIX}{self.character_name}"
if not cmds.objExists(char_name):
cmds.createNode("transform", n=char_name)
cmds.parent(char_name, self.character_name)
def create_lod_node(self, lod: int, obj_name: str) -> None:
"""
Creates a Maya transform which will hold the meshes of the character for a given lod.
@type lod: str
@param lod: The lod number.
@type obj_name: str @param obj_name: The full path name of the object in the scene, if it is not found a new
lod holder will be created.
"""
if not cmds.objExists(obj_name):
parent_name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}"
name = f"{LOD_HOLDER_PREFIX}{lod}"
cmds.createNode("transform", n=name, p=parent_name)
def attach_mesh_to_lod(self, mesh_name: str, lod: int) -> None:
"""
Attaches the mesh called mesh_name to a given lod.
@type mesh_name: str
@param mesh_name: The mesh that needs to be attached to a lod holder object.
@type lod: str
@param lod: The name of the mesh that should be added to a display layer.
"""
parent_node = (
f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}|{LOD_HOLDER_PREFIX}{lod}"
)
cmds.parent(
self.get_mesh_node_fullpath_on_root(mesh_name=mesh_name), parent_node
)
def get_mesh_node_fullpath_on_root(self, mesh_name: str) -> str:
"""
Gets the full path in the scene of a mesh.
@type mesh_name: str
@param mesh_name: The mesh thats path is needed.
@rtype: str
@returns: The full path of the mesh object in the scene
"""
return str(Maya.get_element(f"|{mesh_name}").fullPathName())
def create_ctrl_attributes(self) -> None:
"""
Creates and sets the raw gui control attributes.
"""
gui_control_names = self.dna.get_raw_control_names()
for name in gui_control_names:
ctrl_and_attr_names = name.split(".")
cmds.addAttr(
ctrl_and_attr_names[0],
longName=ctrl_and_attr_names[1],
keyable=True,
attributeType="float",
minValue=0.0,
maxValue=1.0,
)
def create_frm_attributes(self) -> None:
"""
Creates and sets the animated map attributes.
"""
frm_names = self.dna.get_animated_map_names()
for name in frm_names:
cmds.addAttr(
FRM_MULTIPLIERS_NAME,
longName=name.replace(".", "_"),
keyable=True,
attributeType="float",
minValue=0.0,
maxValue=1.0,
)
def create_ctrl_attributes_on_joint(self) -> None:
"""
Creates control attributes on the root joint from the raw control names.
"""
if (
self.config.options.add_ctrl_attributes_on_root_joint
and self.config.options.add_joints
):
logging.info("adding ctrl attributes on root joint...")
names = self.dna.get_raw_control_names()
self.create_attribute_on_joint(names=names)
def create_animated_map_attributes(self) -> None:
"""
Creates animated map attributes on the root joint from the animated map names.
"""
if (
self.config.options.add_animated_map_attributes_on_root_joint
and self.config.options.add_joints
):
logging.info("adding animated map attributes on root joint...")
names = self.dna.get_animated_map_names()
self.create_attribute_on_joint(names=names)
def create_attribute_on_joint(self, names: List[str]) -> None:
"""
Create attributes from a provided list of names on the facial root joint.
@type names: List[str]
@param names: List of names that are added as attributes to the facial root joint.
"""
for name in names:
cmds.addAttr(
FACIAL_ROOT_JOINT,
longName=name.replace(".", "_"),
keyable=True,
attributeType="float",
minValue=0.0,
maxValue=1.0,
)
def add_key_frames(self) -> None:
"""
Adds a starting key frame to the facial root joint if joints are added and the add_key_frames option is set
to True.
"""
if self.config.options.add_key_frames and self.config.options.add_joints:
logging.info("setting keyframe on the root joint...")
cmds.currentTime(0)
cmds.select(FACIAL_ROOT_JOINT, replace=True)
cmds.setKeyframe(inTangentType="linear", outTangentType="linear")
def create_character_meshes(self) -> None:
"""
Builds the meshes of the character. If specified in the character options they get parented to a created
character node transform, otherwise the meshes get put to the root level of the scene.
"""
logging.info("building character meshes...")
meshes: Dict[int, List[MObject]] = {}
for lod, meshes_per_lod in enumerate(
self.dna.get_meshes_by_lods(self.config.meshes)
):
if self.config.create_character_node and meshes_per_lod:
logging.info(f"building LOD for {lod}")
obj_name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}|{LOD_HOLDER_PREFIX}{lod}"
self.create_lod_node(lod=lod, obj_name=obj_name)
meshes[lod] = self.create_meshes(
lod=lod,
meshes_per_lod=meshes_per_lod,
)
self.meshes = meshes
def create_meshes(self, lod: int, meshes_per_lod: List[int]) -> List[MObject]:
"""
Builds the meshes from the provided mesh ids and then attaches them to a given lod if specified in the
character configuration.
@type lod: int
@param lod: The lod number representing the display layer the meshes to the display layer.
@type meshes_per_lod: List[int]
@param meshes_per_lod: List of mesh indices that are being built.
@rtype: List[MObject]
@returns: The list of maya objects that represent the meshes added to the scene.
"""
meshes: List[MObject] = []
for mesh_index in meshes_per_lod:
builder = Mesh(
character_config=self.config,
dna=self.dna,
mesh_index=mesh_index,
)
builder.build()
mesh_name = self.dna.get_mesh_name(mesh_index=mesh_index)
meshes.append(mesh_name)
if self.config.create_display_layers:
self.add_mesh_to_display_layer(mesh_name, lod)
if self.config.create_character_node:
self.attach_mesh_to_lod(mesh_name, lod)
ShaderUtil.default_lambert_shader(
mesh_name, self.character_name, self.config.create_character_node
)
return meshes
def add_gui(self) -> None:
"""
Adds a gui according to the specified gui options. If none is specified no gui will be added.
"""
if self.config.gui_options:
logging.info("adding gui...")
builder = Gui(self.config.gui_options)
self.import_gui(
gui_path=self.config.gui_options.gui_path, group_name=GUI_HOLDER
)
builder.build()
self.create_ctrl_attributes()
self.create_frm_attributes()
def add_analog_gui(
self,
add_to_character_node: bool = False,
) -> None:
"""
Adds an analog gui according to the specified analog gui options. If none is specified no analog gui will be
added.
@type add_to_character_node: bool @param add_to_character_node: A flag that specifies if the imported analog
gui should be parented to the character node transform (default value is None).
"""
if self.config.analog_gui_options and self.config.options.add_joints:
logging.info("adding analog gui...")
builder = AnalogGui(self.config.analog_gui_options)
self.import_gui(
gui_path=self.config.analog_gui_options.gui_path,
group_name=ANALOG_GUI_HOLDER,
add_to_character_node=add_to_character_node,
)
builder.build()
def import_gui(
self, gui_path: str, group_name: str, add_to_character_node: bool = False
) -> None:
"""
Imports a gui using the provided parameters.
@type gui_path: str
@param gui_path: The path of the gui file that needs to be imported.
@type group_name: str
@param group_name: The name of the transform that holds the imported asset.
@type add_to_character_node: bool @param add_to_character_node: A flag representing if the gui holder should
be attached to the character node or be on the root of the scene (default value is False)
"""
cmds.file(gui_path, i=True, groupReference=True, groupName=group_name)
if add_to_character_node:
cmds.parent(group_name, f"{RIG_HOLDER_PREFIX}{self.character_name}")
def add_rig_logic_node(self) -> None:
"""
Creates and adds a rig logic node specified in the character configuration.
"""
RigLogic.add_rig_logic(config=self.config, character_name=self.character_name)
def run_additional_assembly_script(self) -> None:
"""
Runs an additional assembly script if specified in the character configuration.
"""
AdditionalAssemblyScript.run_additional_assembly_script(config=self.config)