401 lines
14 KiB
Python
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)
|