Base
This commit is contained in:
4
scripts/builder/__init__.py
Normal file
4
scripts/builder/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import *
|
436
scripts/builder/builder.py
Normal file
436
scripts/builder/builder.py
Normal file
@@ -0,0 +1,436 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from maya import cmds, mel
|
||||
|
||||
from ..builder.maya.util import Maya
|
||||
from ..common import DNAViewerError
|
||||
from ..dnalib.dnalib import DNA
|
||||
from ..model import Joint as JointModel
|
||||
from .config import AngleUnit, Config, LinearUnit
|
||||
from .joint import Joint as JointBuilder
|
||||
from .mesh import Mesh
|
||||
|
||||
|
||||
@dataclass
|
||||
class BuildResult:
|
||||
"""
|
||||
A class used for returning data after finishing the build process
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type meshes_per_lod: Dict[int, List[str]]
|
||||
@param meshes_per_lod: The list of mesh names created group by LOD number
|
||||
"""
|
||||
|
||||
meshes_per_lod: Dict[int, List[str]] = field(default_factory=dict)
|
||||
|
||||
def get_all_meshes(self) -> List[str]:
|
||||
"""
|
||||
Flatten meshes to single list.
|
||||
|
||||
@rtype: List[str]
|
||||
@returns: The list of all mesh names.
|
||||
"""
|
||||
|
||||
all_meshes = []
|
||||
for meshes_per_lod in self.meshes_per_lod.values():
|
||||
all_meshes.extend(meshes_per_lod)
|
||||
return all_meshes
|
||||
|
||||
|
||||
class Builder:
|
||||
"""
|
||||
A builder class used for building the character
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type config: Config
|
||||
@param config: The configuration options used for building the character
|
||||
|
||||
@type dna: DNA
|
||||
@param dna: The DNA object read from the DNA file
|
||||
|
||||
@type meshes: Dict[int, List[str]]
|
||||
@param meshes: A list of meshes created grouped by lod
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dna: DNA, config: Optional[Config] = None) -> None:
|
||||
self.config = config or Config()
|
||||
self.dna = dna
|
||||
self.meshes: Dict[int, List[str]] = {}
|
||||
self.all_loaded_meshes: List[int] = []
|
||||
|
||||
def _build(self) -> bool:
|
||||
self.new_scene()
|
||||
self.set_filtered_meshes()
|
||||
if not self.all_loaded_meshes:
|
||||
logging.error("No mashes has been loaded.")
|
||||
return False
|
||||
|
||||
self.create_groups()
|
||||
|
||||
self.set_units()
|
||||
self.add_joints()
|
||||
self.build_meshes()
|
||||
self.add_ctrl_attributes_on_root_joint()
|
||||
self.add_animated_map_attributes_on_root_joint()
|
||||
self.add_key_frames()
|
||||
return True
|
||||
|
||||
def build(self) -> BuildResult:
|
||||
"""Builds the character"""
|
||||
self.meshes = {}
|
||||
try:
|
||||
filename = Path(self.dna.path).stem
|
||||
logging.info("******************************")
|
||||
logging.info(f"{filename} started building")
|
||||
logging.info("******************************")
|
||||
|
||||
self._build()
|
||||
|
||||
logging.info(f"{filename} built successfully!")
|
||||
|
||||
except DNAViewerError as e:
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logging.error(f"Unhandled exception, {e}")
|
||||
raise DNAViewerError(f"Scene creation failed! Reason: {e}") from e
|
||||
return BuildResult(meshes_per_lod=self.meshes)
|
||||
|
||||
def new_scene(self) -> None:
|
||||
cmds.file(new=True, force=True)
|
||||
|
||||
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.
|
||||
"""
|
||||
if self.config.create_display_layers:
|
||||
cmds.editDisplayLayerMembers(
|
||||
f"{self.config.top_level_group}_lod{lod}_layer", 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,
|
||||
)
|
||||
builder.process()
|
||||
return joints
|
||||
|
||||
def add_joints(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.add_joints:
|
||||
logging.info("adding joints to character...")
|
||||
joints = self._add_joints()
|
||||
|
||||
if self.config.group_by_lod and joints:
|
||||
cmds.parent(joints[0].name, self.config.get_top_level_group())
|
||||
|
||||
def create_groups(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.group_by_lod:
|
||||
logging.info("building character node...")
|
||||
cmds.group(world=True, empty=True, name=self.config.get_top_level_group())
|
||||
cmds.group(
|
||||
parent=self.config.get_top_level_group(),
|
||||
empty=True,
|
||||
name=self.config.get_geometry_group(),
|
||||
)
|
||||
cmds.group(
|
||||
parent=self.config.get_top_level_group(),
|
||||
empty=True,
|
||||
name=self.config.get_rig_group(),
|
||||
)
|
||||
for lod in self.get_display_layers():
|
||||
name = f"{self.config.top_level_group}_lod{lod}_layer"
|
||||
if not cmds.objExists(name):
|
||||
if self.config.group_by_lod:
|
||||
cmds.group(
|
||||
parent=self.config.get_geometry_group(),
|
||||
empty=True,
|
||||
name=f"{self.config.top_level_group}_lod{lod}_grp",
|
||||
)
|
||||
cmds.select(
|
||||
f"{self.config.top_level_group}_lod{lod}_grp",
|
||||
replace=True,
|
||||
)
|
||||
if self.config.create_display_layers:
|
||||
cmds.createDisplayLayer(name=name, noRecurse=True)
|
||||
|
||||
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.
|
||||
"""
|
||||
if self.config.group_by_lod:
|
||||
parent_node = f"{self.config.get_top_level_group()}|{self.config.get_geometry_group()}|{self.config.top_level_group}_lod{lod}_grp"
|
||||
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 add_ctrl_attributes_on_root_joint(self) -> None:
|
||||
"""
|
||||
Adds and sets the raw gui control attributes on root joint.
|
||||
"""
|
||||
|
||||
if self.config.add_ctrl_attributes_on_root_joint and self.config.add_joints:
|
||||
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=self.config.facial_root_joint_name,
|
||||
long_name=ctrl_and_attr_names[1],
|
||||
)
|
||||
|
||||
def add_animated_map_attributes_on_root_joint(self) -> None:
|
||||
"""
|
||||
Adds and sets the animated map attributes on root joint.
|
||||
"""
|
||||
|
||||
if (
|
||||
self.config.add_animated_map_attributes_on_root_joint
|
||||
and self.config.add_joints
|
||||
):
|
||||
names = self.dna.get_animated_map_names()
|
||||
for name in names:
|
||||
long_name = name.replace(".", "_")
|
||||
self.add_attribute(
|
||||
control_name=self.config.facial_root_joint_name, long_name=long_name
|
||||
)
|
||||
|
||||
def add_attribute(self, control_name: str, long_name: str) -> None:
|
||||
"""
|
||||
Adds attributes wrapper for internal usage.
|
||||
"""
|
||||
cmds.addAttr(
|
||||
control_name,
|
||||
longName=long_name,
|
||||
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.add_key_frames and self.config.add_joints:
|
||||
logging.info("setting keyframe on the root joint...")
|
||||
cmds.currentTime(0)
|
||||
if cmds.objExists(self.config.facial_root_joint_name):
|
||||
cmds.select(self.config.facial_root_joint_name, replace=True)
|
||||
cmds.setKeyframe(inTangentType="linear", outTangentType="linear")
|
||||
|
||||
def set_filtered_meshes(self) -> None:
|
||||
self.all_loaded_meshes = self.get_filtered_meshes()
|
||||
|
||||
def get_mesh_indices_filter(self) -> List[int]:
|
||||
indices = []
|
||||
for index in range(self.dna.get_mesh_count()):
|
||||
mesh_name = self.dna.get_mesh_name(index)
|
||||
for cur_filter in self.config.mesh_filter:
|
||||
if cur_filter in mesh_name:
|
||||
indices.append(index)
|
||||
return indices
|
||||
|
||||
def get_filtered_meshes(self) -> List[int]:
|
||||
if not self.config.mesh_filter and not self.config.lod_filter:
|
||||
if self.config.meshes:
|
||||
return self.config.meshes
|
||||
return list(range(self.dna.get_mesh_count()))
|
||||
|
||||
meshes: List[int] = []
|
||||
meshes_by_lod = self.dna.get_all_meshes_grouped_by_lod()
|
||||
all_meshes = [mesh_index for meshes in meshes_by_lod for mesh_index in meshes]
|
||||
mesh_indices_filter = self.get_mesh_indices_filter()
|
||||
|
||||
if self.config.lod_filter:
|
||||
for lod in self.config.lod_filter:
|
||||
if 0 <= lod < len(meshes_by_lod):
|
||||
meshes.extend(meshes_by_lod[lod])
|
||||
if mesh_indices_filter:
|
||||
return list(set(meshes) & set(mesh_indices_filter))
|
||||
return meshes
|
||||
if self.config.mesh_filter:
|
||||
return list(set(all_meshes) & set(mesh_indices_filter))
|
||||
return all_meshes
|
||||
|
||||
def build_meshes(self) -> None:
|
||||
"""
|
||||
Builds the meshes. If specified in the config they get parented to a created
|
||||
character node transform, otherwise the meshes get put to the root level of the scene.
|
||||
"""
|
||||
|
||||
logging.info("adding character meshes...")
|
||||
self.meshes = {}
|
||||
for lod, meshes_per_lod in enumerate(
|
||||
self.dna.get_meshes_by_lods(self.all_loaded_meshes)
|
||||
):
|
||||
self.meshes[lod] = self.build_meshes_by_lod(
|
||||
lod=lod, meshes_per_lod=meshes_per_lod
|
||||
)
|
||||
|
||||
def build_meshes_by_lod(self, lod: int, meshes_per_lod: List[int]) -> List[str]:
|
||||
"""
|
||||
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[str] = []
|
||||
for mesh_index in meshes_per_lod:
|
||||
builder = Mesh(
|
||||
config=self.config,
|
||||
dna=self.dna,
|
||||
mesh_index=mesh_index,
|
||||
)
|
||||
builder.build()
|
||||
|
||||
mesh_name = self.dna.get_mesh_name(index=mesh_index)
|
||||
meshes.append(mesh_name)
|
||||
|
||||
self.add_mesh_to_display_layer(mesh_name, lod)
|
||||
self.attach_mesh_to_lod(mesh_name, lod)
|
||||
self.default_lambert_shader(mesh_name)
|
||||
return meshes
|
||||
|
||||
def default_lambert_shader(self, mesh_name: str) -> None:
|
||||
try:
|
||||
if self.config.group_by_lod:
|
||||
names = cmds.ls(f"*|{mesh_name}", l=True)
|
||||
for item in names:
|
||||
if item.startswith(f"|{self.config.get_top_level_group()}"):
|
||||
cmds.select(item, r=True)
|
||||
break
|
||||
else:
|
||||
cmds.select(mesh_name, r=True)
|
||||
|
||||
mel.eval("sets -e -forceElement initialShadingGroup")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Couldn't set lambert shader for mesh {mesh_name}. Reason: {e}"
|
||||
)
|
||||
raise DNAViewerError(e) from e
|
||||
|
||||
def set_units(self) -> None:
|
||||
"""Sets the translation and rotation units of the scene from @config"""
|
||||
|
||||
linear_unit = self.get_linear_unit()
|
||||
angle_unit = self.get_angle_unit()
|
||||
|
||||
cmds.currentUnit(linear=linear_unit.name, angle=angle_unit.name)
|
||||
|
||||
def get_linear_unit(self) -> LinearUnit:
|
||||
return self.get_linear_unit_from_int(self.dna.get_translation_unit())
|
||||
|
||||
def get_angle_unit(self) -> AngleUnit:
|
||||
return self.get_angle_unit_from_int(self.dna.get_rotation_unit())
|
||||
|
||||
def get_linear_unit_from_int(self, value: int) -> LinearUnit:
|
||||
"""
|
||||
Returns an enum from an int value.
|
||||
0 -> cm
|
||||
1 -> m
|
||||
|
||||
@type value: int
|
||||
@param value: The value that the enum is mapped to.
|
||||
|
||||
@rtype: LinearUnit
|
||||
@returns: LinearUnit.cm or LinearUnit.m
|
||||
"""
|
||||
|
||||
if value == 0:
|
||||
return LinearUnit.cm
|
||||
if value == 1:
|
||||
return LinearUnit.m
|
||||
raise DNAViewerError(f"Unknown linear unit set in DNA file! value {value}")
|
||||
|
||||
def get_angle_unit_from_int(self, value: int) -> AngleUnit:
|
||||
"""
|
||||
Returns an enum from an int value.
|
||||
0 -> degree
|
||||
1 -> radian
|
||||
|
||||
@type value: int
|
||||
@param value: The value that the enum is mapped to.
|
||||
|
||||
@rtype: AngleUnit
|
||||
@returns: AngleUnit.degree or AngleUnit.radian
|
||||
"""
|
||||
|
||||
if value == 0:
|
||||
return AngleUnit.degree
|
||||
if value == 1:
|
||||
return AngleUnit.radian
|
||||
raise DNAViewerError(f"Unknown angle unit set in DNA file! value {value}")
|
||||
|
||||
def get_display_layers(self) -> List[int]:
|
||||
"""Gets a lod id list that need to be created for the meshes from @config"""
|
||||
meshes: List[int] = []
|
||||
for idx, meshes_per_lod in enumerate(
|
||||
self.dna.get_meshes_by_lods(self.all_loaded_meshes)
|
||||
):
|
||||
if meshes_per_lod:
|
||||
meshes.append(idx)
|
||||
return list(set(meshes))
|
260
scripts/builder/config.py
Normal file
260
scripts/builder/config.py
Normal file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class LinearUnit(Enum):
|
||||
"""
|
||||
An enum used to represent the unit used for linear representation.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@cm: using cm as unit
|
||||
@m: using m as unit
|
||||
"""
|
||||
|
||||
cm = 0
|
||||
m = 1
|
||||
|
||||
|
||||
class AngleUnit(Enum):
|
||||
"""
|
||||
An enum used to represent the unit used for angle representation.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@degree: using degree as unit
|
||||
@radian: using radian as unit
|
||||
"""
|
||||
|
||||
degree = 0
|
||||
radian = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""
|
||||
A class used to represent the config for @Builder
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type mesh_filter: List[str]
|
||||
@param mesh_filter: List of mesh names that should be filtered. Mash names can be just substrings. ["head"] will find all meshes that contins string "head" in its mash name.
|
||||
|
||||
|
||||
@type lod_filter: List[int]
|
||||
@param lod_filter: List of lods that should be filtered.
|
||||
|
||||
@type group_by_lod: bool
|
||||
@param group_by_lod: A flag representing whether the character should be parented to a character transform node in the scene hierarchy
|
||||
|
||||
@type group_by_lod: bool
|
||||
@param group_by_lod: A flag representing whether the character should be parented to a character transform node in rig hierarchy
|
||||
|
||||
@type top_level_group: str
|
||||
@param top_level_group: Value that is going to be used when creating root group
|
||||
|
||||
@type geometry_group: str
|
||||
@param geometry_group: Value that is going to be used when creating group that contains geometry
|
||||
|
||||
@type facial_root_joint_name: str
|
||||
@param facial_root_joint_name: The name of the facial root joint
|
||||
|
||||
@type blend_shape_group_prefix: str
|
||||
@param blend_shape_group_prefix: prefix string for blend shape group
|
||||
|
||||
@type blend_shape_name_postfix: str
|
||||
@param blend_shape_name_postfix: postfix string for blend shape name
|
||||
|
||||
@type skin_cluster_suffix: str
|
||||
@param skin_cluster_suffix: postfix string for skin cluster name
|
||||
|
||||
@type animated_map_attribute_multipliers_name: str
|
||||
@param animated_map_attribute_multipliers_name: string for frame animated map attribute name
|
||||
|
||||
@type create_display_layers: bool
|
||||
@param create_display_layers: A flag representing whether the created meshes should be assigned to a display layer
|
||||
|
||||
@type add_joints: bool
|
||||
@param add_joints: A flag representing whether joints should be added
|
||||
|
||||
@type add_blend_shapes: bool
|
||||
@param add_blend_shapes: A flag representing whether blend shapes should be added
|
||||
|
||||
@type add_skin_cluster: bool
|
||||
@param add_skin_cluster: A flag representing whether skin should be added
|
||||
|
||||
@type add_ctrl_attributes_on_root_joint: bool
|
||||
@param add_ctrl_attributes_on_root_joint: A flag representing whether control attributes should be added to the root joint
|
||||
|
||||
@type add_animated_map_attributes_on_root_joint: bool
|
||||
@param add_animated_map_attributes_on_root_joint: A flag representing whether animated map attributes should be added to the root joint
|
||||
|
||||
@type add_key_frames: bool
|
||||
@param add_key_frames: A flag representing whether key frames should be added
|
||||
|
||||
@type add_mesh_name_to_blend_shape_channel_name: bool
|
||||
@param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it
|
||||
"""
|
||||
|
||||
meshes: List[int] = field(default_factory=list)
|
||||
mesh_filter: List[str] = field(default_factory=list)
|
||||
lod_filter: List[int] = field(default_factory=list)
|
||||
|
||||
group_by_lod: bool = field(default=True)
|
||||
top_level_group: str = "head"
|
||||
geometry_group: str = "geometry"
|
||||
|
||||
facial_root_joint_name: str = "FACIAL_C_FacialRoot"
|
||||
|
||||
blend_shape_group_prefix: str = "BlendshapeGroup_"
|
||||
blend_shape_name_postfix: str = "_blendShapes"
|
||||
skin_cluster_suffix: str = "skinCluster"
|
||||
|
||||
animated_map_attribute_multipliers_name = "FRM_WMmultipliers"
|
||||
|
||||
create_display_layers: bool = field(default=True)
|
||||
|
||||
add_joints: bool = field(default=True)
|
||||
add_blend_shapes: bool = field(default=True)
|
||||
add_skin_cluster: bool = field(default=True)
|
||||
add_ctrl_attributes_on_root_joint: bool = field(default=True)
|
||||
add_animated_map_attributes_on_root_joint: bool = field(default=True)
|
||||
add_key_frames: bool = field(default=True)
|
||||
add_mesh_name_to_blend_shape_channel_name: bool = field(default=True)
|
||||
|
||||
def get_top_level_group(self) -> str:
|
||||
return f"{self.top_level_group}_grp"
|
||||
|
||||
def get_geometry_group(self) -> str:
|
||||
return f"{self.geometry_group}_grp"
|
||||
|
||||
def get_rig_group(self) -> str:
|
||||
return f"{self.top_level_group}Rig_grp"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RigConfig(Config):
|
||||
"""
|
||||
A class used to represent the config for @RigBuilder
|
||||
|
||||
|
||||
@type add_rig_logic: bool
|
||||
@param add_rig_logic: A flag representing whether normals should be added
|
||||
|
||||
@type rig_logic_command: str
|
||||
@param rig_logic_command: The command used to start creating the rig logic using the plugin
|
||||
|
||||
@type rig_logic_name: str
|
||||
@param rig_logic_name: The name of the rig logic node
|
||||
|
||||
@type control_naming: str
|
||||
@param control_naming: The naming pattern of controls
|
||||
|
||||
@type joint_naming: str
|
||||
@param joint_naming: The naming pattern of joints
|
||||
|
||||
@type blend_shape_naming: str
|
||||
@param blend_shape_naming: The naming pattern of blend shapes
|
||||
|
||||
@type animated_map_naming: str
|
||||
@param animated_map_naming: The naming pattern of animated maps
|
||||
|
||||
@type gui_path: str
|
||||
@param gui_path: The location of the gui file
|
||||
|
||||
@type left_eye_joint_name: str
|
||||
@param left_eye_joint_name: The name of the left eye joint
|
||||
|
||||
@type eye_gui_name: str
|
||||
@param eye_gui_name: The name of the control in the gui
|
||||
|
||||
@type gui_translate_x: float
|
||||
@param gui_translate_x: Represents the value that the gui should be additionally translated on the X axis
|
||||
|
||||
@type analog_gui_path: str
|
||||
@param analog_gui_path: The location of the analog gui file
|
||||
|
||||
@type left_eye_joint_name: str
|
||||
@param left_eye_joint_name: The name of the left eye joint
|
||||
|
||||
@type right_eye_joint_name: str
|
||||
@param right_eye_joint_name: The name of the right eye joint
|
||||
|
||||
@type central_driver_name: str
|
||||
@param central_driver_name: The name of the central driver
|
||||
|
||||
@type left_eye_driver_name: str
|
||||
@param left_eye_driver_name: The name of the left eye driver
|
||||
|
||||
@type right_eye_driver_name: str
|
||||
@param right_eye_driver_name: The name of the right eye driver
|
||||
|
||||
@type central_aim: str
|
||||
@param central_aim: The name of the central aim
|
||||
|
||||
@type le_aim: str
|
||||
@param le_aim: The name of the left eye aim
|
||||
|
||||
@type re_aim: str
|
||||
@param re_aim: The name of the right eye aim
|
||||
|
||||
@type aas_path: Optional[str]
|
||||
@param aas_path: The location of the script file
|
||||
|
||||
@type aas_method: str
|
||||
@param aas_method: The method that should be called
|
||||
|
||||
@type aas_parameter: Dict[Any, Any]
|
||||
@param aas_parameter: The parameters that will be passed as the method arguments
|
||||
|
||||
"""
|
||||
|
||||
add_rig_logic: bool = field(default=True)
|
||||
rig_logic_command: str = field(default="createEmbeddedNodeRL4")
|
||||
rig_logic_name: str = field(default="")
|
||||
control_naming: str = field(default="<objName>.<attrName>")
|
||||
joint_naming: str = field(default="<objName>.<attrName>")
|
||||
blend_shape_naming: str = field(default="")
|
||||
animated_map_naming: str = field(default="")
|
||||
gui_path: str = field(default=None)
|
||||
|
||||
eye_gui_name: str = "CTRL_C_eye"
|
||||
gui_translate_x: float = 10
|
||||
|
||||
analog_gui_path: str = field(default=None)
|
||||
|
||||
left_eye_joint_name: str = "FACIAL_L_Eye"
|
||||
right_eye_joint_name: str = "FACIAL_R_Eye"
|
||||
|
||||
central_driver_name: str = "LOC_C_eyeDriver"
|
||||
left_eye_driver_name: str = "LOC_L_eyeDriver"
|
||||
right_eye_driver_name: str = "LOC_R_eyeDriver"
|
||||
|
||||
left_eye_aim_up_name: str = "LOC_L_eyeAimUp"
|
||||
right_eye_aim_up_name: str = "LOC_R_eyeAimUp"
|
||||
central_aim: str = "GRP_C_eyesAim"
|
||||
|
||||
le_aim: str = "GRP_L_eyeAim"
|
||||
re_aim: str = "GRP_R_eyeAim"
|
||||
|
||||
aas_path: Optional[str] = field(default=None)
|
||||
aas_method: str = "run_after_assemble"
|
||||
aas_parameter: Dict[Any, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.add_mesh_name_to_blend_shape_channel_name:
|
||||
self.blend_shape_naming = (
|
||||
f"<objName>{self.blend_shape_name_postfix}.<objName>__<attrName>"
|
||||
)
|
||||
else:
|
||||
self.blend_shape_naming = (
|
||||
f"<objName>{self.blend_shape_name_postfix}.<attrName>"
|
||||
)
|
||||
|
||||
self.animated_map_naming = (
|
||||
f"{self.animated_map_attribute_multipliers_name}.<objName>_<attrName>"
|
||||
)
|
81
scripts/builder/joint.py
Normal file
81
scripts/builder/joint.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from ..model import Joint as JointModel
|
||||
|
||||
|
||||
class Joint:
|
||||
"""
|
||||
A builder class used for adding joints to the scene
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type joints: List[JointModel]
|
||||
@param joints: data representing the joints
|
||||
|
||||
@type joint_flags: Dict[str, bool]
|
||||
@param joint_flags: A mapping used for setting flags that are used to avoid adding the same joint multiple times
|
||||
"""
|
||||
|
||||
def __init__(self, joints: List[JointModel]) -> None:
|
||||
self.joints = joints
|
||||
self.joint_flags: Dict[str, bool] = {}
|
||||
|
||||
for joint in self.joints:
|
||||
self.joint_flags[joint.name] = False
|
||||
|
||||
def add_joint_to_scene(self, joint: JointModel) -> None:
|
||||
"""
|
||||
Adds the given joint to the scene
|
||||
|
||||
@type joint: JointModel
|
||||
@param joint: The joint to be added to the scene
|
||||
"""
|
||||
|
||||
if self.joint_flags[joint.name]:
|
||||
return
|
||||
|
||||
in_parent_space = True
|
||||
|
||||
if cmds.objExists(joint.parent_name):
|
||||
cmds.select(joint.parent_name)
|
||||
else:
|
||||
if joint.name != joint.parent_name:
|
||||
parent_joint = next(
|
||||
j for j in self.joints if j.name == joint.parent_name
|
||||
)
|
||||
self.add_joint_to_scene(parent_joint)
|
||||
else:
|
||||
# this is the first node
|
||||
cmds.select(d=True)
|
||||
in_parent_space = False
|
||||
|
||||
position = (
|
||||
joint.translation.x,
|
||||
joint.translation.y,
|
||||
joint.translation.z,
|
||||
)
|
||||
orientation = (
|
||||
joint.orientation.x,
|
||||
joint.orientation.y,
|
||||
joint.orientation.z,
|
||||
)
|
||||
cmds.joint(
|
||||
p=position,
|
||||
o=orientation,
|
||||
n=joint.name,
|
||||
r=in_parent_space,
|
||||
a=not in_parent_space,
|
||||
scaleCompensate=False,
|
||||
)
|
||||
self.joint_flags[joint.name] = True
|
||||
|
||||
def process(self) -> None:
|
||||
"""Starts adding all the provided joints to the scene"""
|
||||
|
||||
for joint in self.joints:
|
||||
self.add_joint_to_scene(joint)
|
4
scripts/builder/maya/__init__.py
Normal file
4
scripts/builder/maya/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import *
|
424
scripts/builder/maya/mesh.py
Normal file
424
scripts/builder/maya/mesh.py
Normal file
@@ -0,0 +1,424 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Tuple
|
||||
|
||||
from maya import cmds
|
||||
from maya.api.OpenMaya import MDagModifier, MFnDagNode, MFnMesh, MObject, MPoint
|
||||
|
||||
from ...builder.maya.util import Maya
|
||||
from ...common import SKIN_WEIGHT_PRINT_RANGE
|
||||
from ...dnalib.dnalib import DNA
|
||||
from ...model import Point3
|
||||
|
||||
|
||||
@dataclass
|
||||
class Mesh:
|
||||
"""
|
||||
A model class for holding data needed in the mesh building process
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type dna_vertex_positions: List[Point3]
|
||||
@param dna_vertex_positions: Data representing the positions of the vertices
|
||||
|
||||
@type dna_vertex_layout_positions: List[int]
|
||||
@param dna_vertex_layout_positions: Data representing layout position indices of vertices
|
||||
|
||||
@type polygon_faces: List[int]
|
||||
@param polygon_faces: List of lengths of vertex layout indices
|
||||
|
||||
@type polygon_connects: List[int]
|
||||
@param polygon_connects: List of vertex layout position indices
|
||||
|
||||
@type derived_mesh_names: List[str]
|
||||
@param derived_mesh_names: List of mesh names
|
||||
"""
|
||||
|
||||
dna_vertex_positions: List[Point3] = field(default_factory=list)
|
||||
dna_vertex_layout_positions: List[int] = field(default_factory=list)
|
||||
polygon_faces: List[int] = field(default_factory=list)
|
||||
polygon_connects: List[int] = field(default_factory=list)
|
||||
derived_mesh_names: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class MayaMesh:
|
||||
"""
|
||||
A builder class used for adding joints to the scene
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type mesh_index: int
|
||||
@param mesh_index: The index of the mesh
|
||||
|
||||
@type dna: DNA
|
||||
@param dna: Instance of DNA
|
||||
|
||||
@type blend_shape_group_prefix: str
|
||||
@param blend_shape_group_prefix: prefix string for blend shape group
|
||||
|
||||
@type blend_shape_name_postfix: str
|
||||
@param blend_shape_name_postfix: postfix string for blend shape name
|
||||
|
||||
@type skin_cluster_suffix: str
|
||||
@param skin_cluster_suffix: postfix string for skin cluster name
|
||||
|
||||
@type data: Mesh
|
||||
@param data: mesh data used in the mesh creation process
|
||||
|
||||
@type fn_mesh: om.MFnMesh
|
||||
@param fn_mesh: OpenMaya class used for creating the mesh
|
||||
|
||||
@type mesh_object: om.MObject
|
||||
@param mesh_object: the object representing the mesh
|
||||
|
||||
@type dag_modifier: om.MDagModifier
|
||||
@param dag_modifier: OpenMaya class used for naming the mesh
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mesh_index: int,
|
||||
dna: DNA,
|
||||
blend_shape_group_prefix: str,
|
||||
blend_shape_name_postfix: str,
|
||||
skin_cluster_suffix: str,
|
||||
) -> None:
|
||||
self.mesh_index = mesh_index
|
||||
self.data: Mesh = Mesh()
|
||||
self.fn_mesh = MFnMesh()
|
||||
self.mesh_object: MObject = None
|
||||
self.dag_modifier: MDagModifier = None
|
||||
self.dna = dna
|
||||
self.blend_shape_group_prefix = blend_shape_group_prefix
|
||||
self.blend_shape_name_postfix = blend_shape_name_postfix
|
||||
self.skin_cluster_suffix = skin_cluster_suffix
|
||||
|
||||
def create_neutral_mesh(self) -> MObject:
|
||||
"""
|
||||
Creates the neutral mesh using the config provided for this builder class object
|
||||
|
||||
@rtype: om.MObject
|
||||
@returns: the instance of the created mesh object
|
||||
"""
|
||||
self.prepare_mesh()
|
||||
self.mesh_object = self.create_mesh_object()
|
||||
self.dag_modifier = self.rename_mesh()
|
||||
self.add_texture_coordinates()
|
||||
return self.mesh_object
|
||||
|
||||
def create_mesh_object(self) -> MObject:
|
||||
"""
|
||||
Gets a list of points that represent the vertex positions.
|
||||
|
||||
@rtype: MObject
|
||||
@returns: Maya objects representing maya mesh functions and the created maya mesh object.
|
||||
"""
|
||||
|
||||
mesh_object = self.fn_mesh.create(
|
||||
self.get_vertex_positions_from_dna_vertex_positions(),
|
||||
self.data.polygon_faces,
|
||||
self.data.polygon_connects,
|
||||
)
|
||||
|
||||
return mesh_object
|
||||
|
||||
def get_vertex_positions_from_dna_vertex_positions(self) -> List[MPoint]:
|
||||
"""
|
||||
Gets a list of points that represent the vertex positions.
|
||||
|
||||
@rtype: List[MPoint]
|
||||
@returns: List of maya point objects.
|
||||
"""
|
||||
|
||||
vertex_positions = []
|
||||
for position in self.data.dna_vertex_positions:
|
||||
vertex_positions.append(
|
||||
MPoint(
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
)
|
||||
)
|
||||
return vertex_positions
|
||||
|
||||
def rename_mesh(self) -> MDagModifier:
|
||||
"""
|
||||
Renames the initial mesh object that was created to the name from the configuration.
|
||||
|
||||
@rtype: Tuple[MDagModifier]
|
||||
@returns: Maya object representing the dag modifier.
|
||||
"""
|
||||
|
||||
mesh_name = self.dna.get_mesh_name(self.mesh_index)
|
||||
dag_modifier = MDagModifier()
|
||||
dag_modifier.renameNode(self.mesh_object, mesh_name)
|
||||
dag_modifier.doIt()
|
||||
return dag_modifier
|
||||
|
||||
def prepare_mesh(self) -> None:
|
||||
"""
|
||||
Gets a list of points that represent the vertex positions.
|
||||
|
||||
"""
|
||||
|
||||
logging.info("==============================")
|
||||
mesh_name = self.dna.get_mesh_name(self.mesh_index)
|
||||
logging.info(f"adding mesh: {mesh_name}")
|
||||
self.data.dna_vertex_positions = self.dna.get_vertex_positions_for_mesh_index(
|
||||
self.mesh_index
|
||||
)
|
||||
self.data.dna_vertex_layout_positions = (
|
||||
self.dna.get_vertex_layout_positions_for_mesh_index(self.mesh_index)
|
||||
)
|
||||
|
||||
(
|
||||
self.data.polygon_faces,
|
||||
self.data.polygon_connects,
|
||||
) = self.dna.get_polygon_faces_and_connects(self.mesh_index)
|
||||
|
||||
def add_texture_coordinates(self) -> None:
|
||||
"""
|
||||
Method for adding texture coordinates.
|
||||
|
||||
"""
|
||||
|
||||
logging.info("adding texture coordinates...")
|
||||
|
||||
(
|
||||
texture_coordinate_us,
|
||||
texture_coordinate_vs,
|
||||
texture_coordinate_indices,
|
||||
) = self.get_texture_data()
|
||||
|
||||
self.fn_mesh.setUVs(texture_coordinate_us, texture_coordinate_vs)
|
||||
self.fn_mesh.assignUVs(self.data.polygon_faces, texture_coordinate_indices)
|
||||
|
||||
mesh_name = self.dna.get_mesh_name(self.mesh_index)
|
||||
|
||||
cmds.select(mesh_name, replace=True)
|
||||
cmds.polyMergeUV(mesh_name, distance=0.01, constructionHistory=False)
|
||||
|
||||
def get_texture_data(self) -> Tuple[List[float], List[float], List[int]]:
|
||||
"""
|
||||
Gets the data needed for the creation of textures.
|
||||
|
||||
@rtype: Tuple[List[float], List[float], List[int]] @returns: The tuple containing the list of texture
|
||||
coordinate Us, the list of texture coordinate Vs and the list of texture coordinate indices.
|
||||
"""
|
||||
|
||||
texture_coordinates = self.dna.get_vertex_texture_coordinates_for_mesh(
|
||||
self.mesh_index
|
||||
)
|
||||
dna_faces = self.dna.get_faces(self.mesh_index)
|
||||
|
||||
coordinate_indices = []
|
||||
for layout_id in range(
|
||||
len(self.dna.get_layouts_for_mesh_index(self.mesh_index))
|
||||
):
|
||||
coordinate_indices.append(
|
||||
self.dna.get_texture_coordinate_index(self.mesh_index, layout_id)
|
||||
)
|
||||
|
||||
texture_coordinate_us = []
|
||||
texture_coordinate_vs = []
|
||||
texture_coordinate_indices = []
|
||||
|
||||
index_counter = 0
|
||||
|
||||
for vertices_layout_index_array in dna_faces:
|
||||
for vertex_layout_index_array in vertices_layout_index_array:
|
||||
texture_coordinate = texture_coordinates[
|
||||
coordinate_indices[vertex_layout_index_array]
|
||||
]
|
||||
texture_coordinate_us.append(texture_coordinate.u)
|
||||
texture_coordinate_vs.append(texture_coordinate.v)
|
||||
texture_coordinate_indices.append(index_counter)
|
||||
index_counter += 1
|
||||
|
||||
return texture_coordinate_us, texture_coordinate_vs, texture_coordinate_indices
|
||||
|
||||
def add_blend_shapes(self, add_mesh_name_to_blend_shape_channel_name: bool) -> None:
|
||||
"""Adds blend shapes to the mesh"""
|
||||
if self.dna.has_blend_shapes(self.mesh_index):
|
||||
self.create_blend_shapes(add_mesh_name_to_blend_shape_channel_name)
|
||||
self.create_blend_shape_node()
|
||||
|
||||
def create_blend_shape_node(self) -> None:
|
||||
"""
|
||||
Creates a blend shape node.
|
||||
"""
|
||||
mesh_name = self.dna.get_mesh_name(self.mesh_index)
|
||||
|
||||
nodes = []
|
||||
for derived_mesh_name in self.data.derived_mesh_names:
|
||||
nodes.append(derived_mesh_name)
|
||||
|
||||
cmds.select(nodes, replace=True)
|
||||
|
||||
cmds.select(mesh_name, add=True)
|
||||
cmds.blendShape(name=f"{mesh_name}{self.blend_shape_name_postfix}")
|
||||
cmds.delete(f"{self.blend_shape_group_prefix}{mesh_name}")
|
||||
|
||||
def create_blend_shapes(
|
||||
self, add_mesh_name_to_blend_shape_channel_name: bool
|
||||
) -> None:
|
||||
"""
|
||||
Builds all the derived meshes using the provided mesh and the blend shapes data of the DNA.
|
||||
|
||||
@type add_mesh_name_to_blend_shape_channel_name: bool
|
||||
@param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it
|
||||
"""
|
||||
|
||||
logging.info("adding derived meshes...")
|
||||
|
||||
group: str = cmds.group(
|
||||
empty=True,
|
||||
name=f"{self.blend_shape_group_prefix}{self.dna.get_mesh_name(self.mesh_index)}",
|
||||
)
|
||||
|
||||
self.data.derived_mesh_names = []
|
||||
blend_shapes = self.dna.get_blend_shapes(self.mesh_index)
|
||||
for blend_shape_target_index, blend_shape in enumerate(blend_shapes):
|
||||
|
||||
self.create_blend_shape(
|
||||
blend_shape_target_index,
|
||||
blend_shape.channel,
|
||||
group,
|
||||
add_mesh_name_to_blend_shape_channel_name,
|
||||
)
|
||||
cmds.setAttr(f"{group}.visibility", 0)
|
||||
|
||||
def create_blend_shape(
|
||||
self,
|
||||
blend_shape_target_index: int,
|
||||
blend_shape_channel: int,
|
||||
group: str,
|
||||
add_mesh_name_to_blend_shape_channel_name: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Builds a single derived mesh using the provided mesh and the blend shape data of the DNA.
|
||||
|
||||
|
||||
@type blend_shape_target_index: int
|
||||
@param blend_shape_target_index: Used for getting a delta value representing the value change concerning the blend shape.
|
||||
|
||||
@type blend_shape_channel: int
|
||||
@param blend_shape_channel: Used for getting the blend shape name from the DNA.
|
||||
|
||||
@type group: str
|
||||
@param group: The transform the new meshes will be added to.
|
||||
|
||||
@type add_mesh_name_to_blend_shape_channel_name: bool
|
||||
@param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it
|
||||
"""
|
||||
|
||||
new_vert_layout = self.get_vertex_positions_from_dna_vertex_positions()
|
||||
|
||||
zipped_deltas = self.dna.get_blend_shape_target_deltas_with_vertex_id(
|
||||
self.mesh_index, blend_shape_target_index
|
||||
)
|
||||
for zipped_delta in zipped_deltas:
|
||||
delta: Point3 = zipped_delta[1]
|
||||
new_vert_layout[zipped_delta[0]] += MPoint(
|
||||
delta.x,
|
||||
delta.y,
|
||||
delta.z,
|
||||
)
|
||||
|
||||
new_mesh = self.fn_mesh.create(
|
||||
new_vert_layout, self.data.polygon_faces, self.data.polygon_connects
|
||||
)
|
||||
derived_name = self.dna.get_blend_shape_channel_name(blend_shape_channel)
|
||||
name = (
|
||||
f"{self.dna.geometry_meshes[self.mesh_index].name}__{derived_name}"
|
||||
if add_mesh_name_to_blend_shape_channel_name
|
||||
else derived_name
|
||||
)
|
||||
self.dag_modifier.renameNode(new_mesh, name)
|
||||
self.dag_modifier.doIt()
|
||||
|
||||
dag = MFnDagNode(Maya.get_element(group))
|
||||
dag.addChild(new_mesh)
|
||||
|
||||
self.data.derived_mesh_names.append(name)
|
||||
|
||||
def add_skin_cluster(self, joint_names: List[str], joint_ids: List[int]) -> None:
|
||||
"""
|
||||
Adds skin cluster to the mesh
|
||||
|
||||
@type joint_names: List[str]
|
||||
@param joint_names: Joint names needed for adding the skin cluster
|
||||
|
||||
@type joint_ids: List[int]
|
||||
@param joint_ids: Joint indices needed for setting skin weights
|
||||
"""
|
||||
|
||||
mesh_name = self.dna.get_mesh_name(self.mesh_index)
|
||||
|
||||
self._add_skin_cluster(mesh_name, joint_names)
|
||||
self.set_skin_weights(mesh_name, joint_ids)
|
||||
|
||||
def _add_skin_cluster(self, mesh_name: str, joint_names: List[str]) -> None:
|
||||
"""
|
||||
Creates a skin cluster object.
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The mesh name that is used for skin cluster naming.
|
||||
|
||||
@type joints: List[Joint]
|
||||
@param joints: List of joints used for adding the skin cluster.
|
||||
"""
|
||||
|
||||
logging.info("adding skin cluster...")
|
||||
maximum_influences = self.dna.get_maximum_influence_per_vertex(self.mesh_index)
|
||||
|
||||
cmds.select(joint_names[0], replace=True)
|
||||
|
||||
cmds.select(mesh_name, add=True)
|
||||
skin_cluster = cmds.skinCluster(
|
||||
toSelectedBones=True,
|
||||
name=f"{mesh_name}_{self.skin_cluster_suffix}",
|
||||
maximumInfluences=maximum_influences,
|
||||
skinMethod=0,
|
||||
obeyMaxInfluences=True,
|
||||
)
|
||||
cmds.skinCluster(
|
||||
skin_cluster, edit=True, addInfluence=joint_names[1:], weight=0
|
||||
)
|
||||
|
||||
def set_skin_weights(self, mesh_name: str, joint_ids: List[int]) -> None:
|
||||
"""
|
||||
Sets the skin weights attributes.
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The mesh name that is used for getting the skin cluster name.
|
||||
|
||||
@type joint_ids: List[int]
|
||||
@param joint_ids: List of joint indices used for setting the skin weight attribute.
|
||||
"""
|
||||
|
||||
logging.info("adding skin weights...")
|
||||
skin_weights = self.dna.get_skin_weight_matrix_for_mesh(self.mesh_index)
|
||||
|
||||
# import skin weights
|
||||
temp_str = f"{mesh_name}_{self.skin_cluster_suffix}.wl["
|
||||
for vertex_id, skin_weight in enumerate(skin_weights):
|
||||
if not (vertex_id + 1) % SKIN_WEIGHT_PRINT_RANGE:
|
||||
logging.info(f"\t{vertex_id + 1} / {len(skin_weights)}")
|
||||
vertex_infos = skin_weight
|
||||
|
||||
# set all skin weights to zero
|
||||
vertex_string = f"{temp_str}{str(vertex_id)}].w["
|
||||
cmds.setAttr(f"{vertex_string}0]", 0.0)
|
||||
|
||||
# import skin weights
|
||||
for vertex_info in vertex_infos:
|
||||
cmds.setAttr(
|
||||
f"{vertex_string}{str(joint_ids.index(vertex_info[0]))}]",
|
||||
float(vertex_info[1]),
|
||||
)
|
||||
if len(skin_weights) % SKIN_WEIGHT_PRINT_RANGE != 0:
|
||||
logging.info(f"\t{len(skin_weights)} / {len(skin_weights)}")
|
204
scripts/builder/maya/skin_weights.py
Normal file
204
scripts/builder/maya/skin_weights.py
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
from maya import cmds, mel
|
||||
from maya.api.OpenMaya import MFnMesh, MGlobal
|
||||
from maya.api.OpenMayaAnim import MFnSkinCluster
|
||||
|
||||
from ...builder.maya.util import Maya
|
||||
from ...common import DNAViewerError
|
||||
|
||||
|
||||
class MayaSkinWeights:
|
||||
"""
|
||||
A class used for reading and storing skin weight related data needed for adding skin clusters
|
||||
"""
|
||||
|
||||
no_of_influences: int
|
||||
skinning_method: int
|
||||
joints: List[str]
|
||||
vertices_info: List[List[Union[int, float]]]
|
||||
|
||||
def __init__(self, skin_cluster: MFnSkinCluster, mesh_name: str) -> None:
|
||||
self.no_of_influences = cmds.skinCluster(skin_cluster.name(), q=True, mi=True)
|
||||
|
||||
self.skinning_method = cmds.skinCluster(skin_cluster.name(), q=True, sm=True)
|
||||
|
||||
self.joints = self.get_skin_cluster_influence(skin_cluster)
|
||||
|
||||
self.vertices_info = self.get_skin_weights_for_mesh_name(
|
||||
skin_cluster, mesh_name
|
||||
)
|
||||
|
||||
def get_skin_cluster_influence(self, skin_cluster: MFnSkinCluster) -> List[str]:
|
||||
"""
|
||||
Gets a list of joint names that are influences to the skin cluster.
|
||||
|
||||
@type skin_cluster: MFnSkinCluster
|
||||
@param skin_cluster: The functionalities of a maya skin cluster object
|
||||
|
||||
@rtype: List[str]
|
||||
@returns: The list if names of the joints that influence the skin cluster
|
||||
"""
|
||||
|
||||
influences: List[str] = cmds.skinCluster(skin_cluster.name(), q=True, inf=True)
|
||||
if influences and not isinstance(influences[0], str):
|
||||
influences = [obj.name() for obj in influences]
|
||||
return influences
|
||||
|
||||
def get_skin_weights_for_mesh_name(
|
||||
self,
|
||||
skin_cluster: MFnSkinCluster,
|
||||
mesh_name: str,
|
||||
) -> List[List[Union[int, float]]]:
|
||||
"""
|
||||
Gets the skin weights concerning the given mesh.
|
||||
|
||||
@type skin_cluster: MFnSkinCluster
|
||||
@param skin_cluster: The functionalities of a maya skin cluster object
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The name of the mesh
|
||||
|
||||
@rtype: List[List[Union[int, float]]]
|
||||
@returns: A list of list of weight indices and the weight values
|
||||
"""
|
||||
|
||||
mesh = Maya.get_element(mesh_name)
|
||||
components = MGlobal.getSelectionListByName(f"{mesh_name}.vtx[*]").getComponent(
|
||||
0
|
||||
)[1]
|
||||
weights_data, chunk = skin_cluster.getWeights(mesh, components)
|
||||
iterator = [
|
||||
weights_data[i : i + chunk] for i in range(0, len(weights_data), chunk)
|
||||
]
|
||||
|
||||
vertices_info = []
|
||||
for weights in iterator:
|
||||
vertex_weights: List[float] = []
|
||||
vertices_info.append(vertex_weights)
|
||||
|
||||
for i, weight in enumerate(weights):
|
||||
if weight:
|
||||
vertex_weights.append(i)
|
||||
vertex_weights.append(weight)
|
||||
return vertices_info
|
||||
|
||||
|
||||
def get_skin_weights_data(mesh_name: str) -> Tuple[MFnMesh, MFnSkinCluster]:
|
||||
"""
|
||||
Gets the maya objects that manipulate the mesh node and the skin cluster for a given mesh name.
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The name of the mesh
|
||||
|
||||
@rtype: Tuple[MFnMesh, MFnSkinCluster]
|
||||
@returns: The maya object that manipulate the mesh node and the skin cluster for a given mesh name.
|
||||
"""
|
||||
|
||||
skin_cluster_name = mel.eval(f"findRelatedSkinCluster {mesh_name}")
|
||||
if skin_cluster_name:
|
||||
skin_cluster = MFnSkinCluster(Maya.get_element(skin_cluster_name))
|
||||
mesh_node = MFnMesh(Maya.get_element(mesh_name))
|
||||
return mesh_node, skin_cluster
|
||||
raise DNAViewerError(f"Unable to find skin for given mesh: {mesh_name}")
|
||||
|
||||
|
||||
def get_skin_weights_from_scene(mesh_name: str) -> MayaSkinWeights:
|
||||
"""
|
||||
Gets the instance of this class filled with data from the scene for a given mesh name.
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The mesh name
|
||||
|
||||
@rtype: MayaSkinWeights
|
||||
@returns: An instance of this class with the data from the scene
|
||||
"""
|
||||
|
||||
_, skin_cluster = get_skin_weights_data(mesh_name)
|
||||
|
||||
return MayaSkinWeights(skin_cluster, mesh_name)
|
||||
|
||||
|
||||
def get_file_joint_mappings(
|
||||
skin_weights: MayaSkinWeights, skin_cluster: MFnSkinCluster
|
||||
) -> List[int]:
|
||||
"""
|
||||
Returns a list of object indices representing the influences concerning the joint names specified in the skin weight model.
|
||||
|
||||
@type skin_weights: MayaSkinWeights
|
||||
@param skin_weights: The instance of the model storing data about skin weights
|
||||
|
||||
@type skin_cluster: MFnSkinCluster
|
||||
@param skin_cluster: An object for working with functions concerning a skin cluster in maya
|
||||
|
||||
@rtype: List[int]
|
||||
@returns: a list of indices representing the influences concerning the given joints
|
||||
"""
|
||||
|
||||
file_joint_mapping: List[int] = []
|
||||
for joint_name in skin_weights.joints:
|
||||
file_joint_mapping.append(
|
||||
skin_cluster.indexForInfluenceObject(Maya.get_element(joint_name))
|
||||
)
|
||||
return file_joint_mapping
|
||||
|
||||
|
||||
def set_skin_weights_to_scene(mesh_name: str, skin_weights: MayaSkinWeights) -> None:
|
||||
"""
|
||||
Sets the skin weights to the scene.
|
||||
|
||||
@type mesh_name: str
|
||||
@param mesh_name: The mesh name
|
||||
|
||||
@type skin_weights: MayaSkinWeights
|
||||
@param skin_weights: The object containing data that need to be set to the scene.
|
||||
"""
|
||||
|
||||
mesh_node, skin_cluster = get_skin_weights_data(mesh_name)
|
||||
|
||||
file_joint_mapping = get_file_joint_mappings(skin_weights, skin_cluster)
|
||||
|
||||
import_skin_weights(skin_cluster, mesh_node, skin_weights, file_joint_mapping)
|
||||
|
||||
logging.info("Set skin weights ended.")
|
||||
|
||||
|
||||
def import_skin_weights(
|
||||
skin_cluster: MFnSkinCluster,
|
||||
mesh_node: MFnMesh,
|
||||
skin_weights: MayaSkinWeights,
|
||||
file_joint_mapping: List[int],
|
||||
) -> None:
|
||||
"""
|
||||
Imports the skin weights to the scene using the joint mapping and the data provided in the model containing the weights.
|
||||
|
||||
@type skin_cluster: MFnSkinCluster
|
||||
@param skin_cluster: An object for working with functions concerning a skin cluster in maya
|
||||
|
||||
@type mesh_node: MFnMesh
|
||||
@param mesh_node: An object for working with functions concerning meshes in maya
|
||||
|
||||
@type skin_weights: MayaSkinWeights
|
||||
@param skin_weights: The instance of the model storing data about skin weights
|
||||
|
||||
@type file_joint_mapping: List[int]
|
||||
@param file_joint_mapping: a list of indices representing the influences concerning joints
|
||||
"""
|
||||
|
||||
temp_str = f"{skin_cluster.name()}.wl["
|
||||
for vtx_id in range(cmds.polyEvaluate(mesh_node.name(), vertex=True)):
|
||||
vtx_info = skin_weights.vertices_info[vtx_id]
|
||||
|
||||
vtx_str = f"{temp_str}{str(vtx_id)}].w["
|
||||
|
||||
cmds.setAttr(f"{vtx_str}0]", 0.0)
|
||||
|
||||
for i in range(0, len(vtx_info), 2):
|
||||
cmds.setAttr(
|
||||
f"{vtx_str}{str(file_joint_mapping[int(vtx_info[i])])}]",
|
||||
vtx_info[i + 1],
|
||||
)
|
84
scripts/builder/maya/util.py
Normal file
84
scripts/builder/maya/util.py
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Union
|
||||
|
||||
from maya.api.OpenMaya import (
|
||||
MDagPath,
|
||||
MFnDagNode,
|
||||
MFnTransform,
|
||||
MGlobal,
|
||||
MSpace,
|
||||
MVector,
|
||||
)
|
||||
|
||||
from ...common import DNAViewerError
|
||||
|
||||
|
||||
class Maya:
|
||||
"""A utility class used for interfacing with maya transforms"""
|
||||
|
||||
@staticmethod
|
||||
def get_element(name: str) -> Union[MDagPath, MFnDagNode]:
|
||||
"""gets the Union[MDagPath, MFnDagNode] object of the element with the given name
|
||||
|
||||
@type name: str
|
||||
@param name: The name of the element to be retrieved
|
||||
|
||||
@rtype: Union[MDagPath, MFnDagNode]
|
||||
@returns: A OpenMaya object representing the given element
|
||||
"""
|
||||
try:
|
||||
sellist = MGlobal.getSelectionListByName(name)
|
||||
except Exception as exception:
|
||||
raise DNAViewerError(f"Element with name:{name} not found!") from exception
|
||||
|
||||
try:
|
||||
return sellist.getDagPath(0)
|
||||
except Exception:
|
||||
return sellist.getDependNode(0)
|
||||
|
||||
@staticmethod
|
||||
def get_transform(name: str) -> MFnTransform:
|
||||
"""gets the transform of the element with the given name
|
||||
|
||||
@type element: str
|
||||
@param element: The element name that we want the transform of
|
||||
|
||||
@rtype: MFnTransform
|
||||
@returns: A MFnTransform object representing the given elements transform
|
||||
"""
|
||||
return MFnTransform(Maya.get_element(name))
|
||||
|
||||
@staticmethod
|
||||
def get_translation(element: str, space: int = MSpace.kObject) -> MVector:
|
||||
"""gets the translation of the element with the given name
|
||||
|
||||
@type element: str
|
||||
@param element: The element name that we want the translation of
|
||||
|
||||
@type space: str
|
||||
@param space: A string value representing the translation space (default is "world")
|
||||
|
||||
@rtype: MVector
|
||||
@returns: A MVector object representing the given elements translation
|
||||
"""
|
||||
return MFnTransform(Maya.get_element(element)).translation(space)
|
||||
|
||||
@staticmethod
|
||||
def set_translation(
|
||||
element: str, translation: MVector, space: int = MSpace.kObject
|
||||
) -> None:
|
||||
"""sets the translation of the element with the given name
|
||||
|
||||
@type element: str
|
||||
@param element: The element name that we want to set the translation of
|
||||
|
||||
@type translation: MVector
|
||||
@param translation: The new translation value
|
||||
|
||||
@type space: str
|
||||
@param space: A string value representing the translation space (default is "object")
|
||||
"""
|
||||
element_obj = Maya.get_transform(element)
|
||||
element_obj.setTranslation(translation, space)
|
117
scripts/builder/mesh.py
Normal file
117
scripts/builder/mesh.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from ..builder.maya.mesh import MayaMesh
|
||||
from ..dnalib.dnalib import DNA
|
||||
from .config import Config
|
||||
|
||||
|
||||
class Mesh:
|
||||
"""
|
||||
A builder class used for adding joints to the scene
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@type dna: DNA
|
||||
@param dna: The location of the DNA file
|
||||
|
||||
@type mesh_index: int
|
||||
@param mesh_index: The mesh index we are working with
|
||||
|
||||
@type joint_ids: List[int]
|
||||
@param joint_ids: The joint indices used for adding skin
|
||||
|
||||
@type joint_names: List[str]
|
||||
@param joint_names: The joint names used for adding skin
|
||||
|
||||
@type config: Config
|
||||
@param config: The build options that will be applied when creating the mesh
|
||||
|
||||
|
||||
@type mesh: MayaMesh
|
||||
@param mesh: The builder class object for creating the meshes
|
||||
|
||||
@type dna: DNA
|
||||
@param dna: The DNA object that was loaded in
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
dna: DNA,
|
||||
mesh_index: int,
|
||||
) -> None:
|
||||
self.mesh_index: int = mesh_index
|
||||
self.joint_ids: List[int] = []
|
||||
self.joint_names: List[str] = []
|
||||
self.config = config
|
||||
self.dna = dna
|
||||
self.mesh = MayaMesh(
|
||||
self.mesh_index,
|
||||
self.dna,
|
||||
blend_shape_group_prefix=self.config.blend_shape_group_prefix,
|
||||
blend_shape_name_postfix=self.config.blend_shape_name_postfix,
|
||||
skin_cluster_suffix=self.config.skin_cluster_suffix,
|
||||
)
|
||||
|
||||
def build(self) -> None:
|
||||
"""Starts the build process, creates the neutral mesh, then adds normals, blends shapes and skin if needed"""
|
||||
|
||||
self.create_neutral_mesh()
|
||||
self.add_blend_shapes()
|
||||
self.add_skin_cluster()
|
||||
|
||||
def create_neutral_mesh(self) -> None:
|
||||
"""Creates the neutral mesh"""
|
||||
|
||||
self.mesh.create_neutral_mesh()
|
||||
|
||||
def add_blend_shapes(self) -> None:
|
||||
"""Reads in the blend shapes, then adds them to the mesh if it is set in the build options"""
|
||||
|
||||
if self.config.add_blend_shapes:
|
||||
logging.info("adding blend shapes...")
|
||||
self.mesh.add_blend_shapes(
|
||||
self.config.add_mesh_name_to_blend_shape_channel_name
|
||||
)
|
||||
|
||||
def add_skin_cluster(self) -> None:
|
||||
"""Adds skin cluster to the mesh if it is set in the build options"""
|
||||
|
||||
if self.config.add_skin_cluster and self.config.add_joints:
|
||||
self.prepare_joints()
|
||||
if self.joint_names:
|
||||
self.mesh.add_skin_cluster(self.joint_names, self.joint_ids)
|
||||
|
||||
def prepare_joints(self) -> None:
|
||||
"""
|
||||
Gets the joint indices and names needed for the given mesh.
|
||||
"""
|
||||
|
||||
self.prepare_joint_ids()
|
||||
|
||||
joints = self.dna.read_all_neutral_joints()
|
||||
self.joint_names = []
|
||||
for joint_id in self.joint_ids:
|
||||
self.joint_names.append(joints[joint_id].name)
|
||||
|
||||
def prepare_joint_ids(self) -> None:
|
||||
joints_temp: List[int] = []
|
||||
joint_indices = self.dna.get_all_skin_weights_joint_indices_for_mesh(
|
||||
self.mesh_index
|
||||
)
|
||||
self.joint_ids = []
|
||||
if any(joint_indices):
|
||||
for row in joint_indices:
|
||||
for column in row:
|
||||
joints_temp.append(column)
|
||||
|
||||
self.joint_ids = list(set(joints_temp))
|
||||
self.joint_ids.sort()
|
||||
else:
|
||||
lod = self.dna.get_lowest_lod_containing_meshes([self.mesh_index])
|
||||
if lod:
|
||||
self.joint_ids = self.dna.get_joint_indices_for_lod(lod)
|
293
scripts/builder/rig_builder.py
Normal file
293
scripts/builder/rig_builder.py
Normal file
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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],
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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],
|
||||
),
|
||||
)
|
||||
|
||||
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]),
|
||||
)
|
||||
|
||||
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!")
|
||||
|
||||
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)
|
Reference in New Issue
Block a user