Source code for dna_viewer.builder.rig_builder

import logging
from importlib.machinery import SourceFileLoader
from importlib.util import module_from_spec, spec_from_loader
from pathlib import Path
from types import ModuleType
from typing import Optional

from maya import cmds, mel
from maya.api.OpenMaya import MSpace, MVector

from ..builder.maya.util import Maya
from ..common import ANALOG_GUI_HOLDER, GUI_HOLDER, RIG_LOGIC_PREFIX, DNAViewerError
from ..dnalib.dnalib import DNA
from .builder import Builder
from .config import RigConfig


[docs]class RigBuilder(Builder): """ A builder class used for building meshes """ def __init__(self, dna: DNA, config: Optional[RigConfig] = None) -> None: super().__init__(dna=dna, config=config) self.config: Optional[RigConfig] self.eye_l_pos: MVector self.eye_r_pos: MVector def _build(self) -> None: if super()._build(): self.add_gui() self.add_analog_gui() self.add_rig_logic() self.run_additional_assemble_script()
[docs] def run_additional_assemble_script(self) -> None: """ Runs an additional assemble script if specified in the character configuration. """ if self.config.aas_path: logging.info("running additional assemble script...") try: module_name = Path(self.config.aas_path).stem script = self.source_py_file(module_name, self.config.aas_path) script_method = getattr(script, self.config.aas_method) script_method( self.config.get_top_level_group(), self.config.get_rig_group(), self.config.aas_parameter, ) except Exception as e: raise DNAViewerError(f"Can't run aas script. Reason: {e}") from e
[docs] def add_rig_logic(self) -> None: """ Creates and adds a rig logic node specified in the character configuration. """ if ( self.config.add_rig_logic and self.config.add_joints and self.config.add_skin_cluster and self.config.add_blend_shapes and self.config.aas_path and self.config.analog_gui_path and self.config.gui_path ): logging.info("adding rig logic...") try: cmds.loadPlugin("embeddedRL4.mll") self.config.rig_logic_name = f"{RIG_LOGIC_PREFIX}{self.dna.name}" dna = self.dna.path.replace("\\", "/") mel_command = self.config.rig_logic_command mel_command += f' -n "{self.config.rig_logic_name}"' mel_command += f' -dfp "{dna}"' mel_command += f' -cn "{self.config.control_naming}"' mel_command += f' -jn "{self.config.joint_naming}"' mel_command += f' -bsn "{self.config.blend_shape_naming}"' mel_command += f' -amn "{self.config.animated_map_naming}"; ' logging.info(f"mel command: {mel_command}") mel.eval(mel_command) except Exception as e: logging.error( "The procedure needed for assembling the rig logic was not found, the plugin needed for this might not be loaded." ) raise DNAViewerError( f"Something went wrong, skipping adding the rig logic... Reason: {e}" ) from e
[docs] 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_path: logging.info("adding gui...") self.import_gui( gui_path=self.config.gui_path, group_name=GUI_HOLDER, ) self.position_gui(GUI_HOLDER) self.add_ctrl_attributes() self.add_animated_map_attributes()
[docs] def add_ctrl_attributes(self) -> None: """ Adds 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(".") self.add_attribute( control_name=ctrl_and_attr_names[0], long_name=ctrl_and_attr_names[1], )
[docs] def add_animated_map_attributes(self) -> None: """ Adds and sets the animated map attributes. """ names = self.dna.get_animated_map_names() for name in names: long_name = name.replace(".", "_") if self.config.gui_path: self.add_attribute( control_name=self.config.animated_map_attribute_multipliers_name, long_name=long_name, )
[docs] def position_gui(self, group_name: str) -> None: """Sets the gui position to align with the character eyes""" if not cmds.objExists(self.config.eye_gui_name) or not cmds.objExists( self.config.left_eye_joint_name ): logging.warning( "could not find joints needed for positioning the gui, leaving it at its default position..." ) return gui_y = ( Maya.get_transform(self.config.eye_gui_name).translation(MSpace.kObject).y ) eyes_y = ( Maya.get_transform(self.config.left_eye_joint_name) .translation(MSpace.kObject) .y ) delta_y = eyes_y - gui_y if isinstance(self.config.gui_translate_x, str): try: logging.warning( "gui_translate_x should be a float, trying to cast the value to float..." ) self.config.gui_translate_x = float(self.config.gui_translate_x) except ValueError: logging.error("could not cast string value to float") return Maya.get_transform(group_name).translateBy( MVector(self.config.gui_translate_x, delta_y, 0), MSpace.kObject )
[docs] def add_analog_gui(self) -> None: """ Adds an analog gui according to the specified analog gui options. If none is specified no analog gui will be added. """ if self.config.analog_gui_path and self.config.add_joints: logging.info("adding analog gui...") self.import_gui( gui_path=self.config.analog_gui_path, group_name=ANALOG_GUI_HOLDER, ) if self.dna.joints.names: self.add_eyes() self.add_eye_locators()
[docs] def add_eyes(self) -> None: """Add eyes to the analog gui""" self.eye_l_pos = Maya.get_translation(self.config.left_eye_joint_name) self.eye_r_pos = Maya.get_translation(self.config.right_eye_joint_name) Maya.set_translation( self.config.central_driver_name, Maya.get_translation(self.config.facial_root_joint_name), ) delta_l = Maya.get_translation( self.config.left_eye_aim_up_name ) - Maya.get_translation(self.config.left_eye_driver_name) delta_r = Maya.get_translation( self.config.right_eye_aim_up_name ) - Maya.get_translation(self.config.right_eye_driver_name) Maya.set_translation(self.config.left_eye_driver_name, self.eye_l_pos) Maya.set_translation( self.config.right_eye_driver_name, self.eye_r_pos, ) Maya.set_translation( self.config.left_eye_aim_up_name, MVector( self.eye_l_pos[0] + delta_l[0], self.eye_l_pos[1] + delta_l[1], self.eye_l_pos[2] + delta_l[2], ), ) Maya.set_translation( self.config.right_eye_aim_up_name, MVector( self.eye_r_pos[0] + delta_r[0], self.eye_r_pos[1] + delta_r[1], self.eye_r_pos[2] + delta_r[2], ), )
[docs] def add_eye_locators(self) -> None: """Add eye locators to the analog gui""" eye_l_locator_pos = Maya.get_translation(self.config.le_aim) eye_r_locator_pos = Maya.get_translation(self.config.re_aim) central_aim_pos = Maya.get_translation(self.config.central_aim) eye_middle_delta = (self.eye_l_pos - self.eye_r_pos) / 2 eye_middle = self.eye_r_pos + eye_middle_delta Maya.set_translation( self.config.central_aim, MVector(eye_middle[0], eye_middle[1], central_aim_pos[2]), ) Maya.set_translation( self.config.le_aim, MVector(self.eye_l_pos[0], self.eye_l_pos[1], eye_l_locator_pos[2]), ) Maya.set_translation( self.config.re_aim, MVector(self.eye_r_pos[0], self.eye_r_pos[1], eye_r_locator_pos[2]), )
[docs] def source_py_file(self, name: str, path: str) -> Optional[ModuleType]: """ Used for loading a python file, used for additional assemble script. @type name: str @param name: The name of the module. @type path: str @param path: The path of the python file. @rtype: Optional[ModuleType] @returns: The loaded module. """ path_obj = Path(path.strip()) if ( path and path_obj.exists() and path_obj.is_file() and path_obj.suffix == ".py" ): spec = spec_from_loader(name, SourceFileLoader(name, path)) module = module_from_spec(spec) spec.loader.exec_module(module) return module raise DNAViewerError(f"File {path} is not found!")
[docs] def import_gui(self, gui_path: str, group_name: str) -> 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. """ cmds.file(gui_path, i=True, groupReference=True, groupName=group_name)