This commit is contained in:
2025-05-02 00:14:28 +08:00
commit 6f27dc11e3
132 changed files with 28609 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from . import *

View 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)}")

View 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],
)

View 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)