284 lines
10 KiB
Python
284 lines
10 KiB
Python
|
from dataclasses import dataclass, field
|
||
|
from typing import List, Optional, Tuple
|
||
|
|
||
|
from ..config.units import AngleUnit, LinearUnit
|
||
|
from ..model.behavior import Behavior
|
||
|
from ..model.definition import Definition
|
||
|
from ..model.descriptor import Descriptor
|
||
|
from ..model.geometry import UV, BlendShape, Geometry, Layout, Mesh, Point3
|
||
|
from ..model.joint import Joint
|
||
|
from ..util.conversion import Conversion
|
||
|
from ..util.error import DNAViewerError
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class BaseDNA:
|
||
|
"""
|
||
|
A model class for holding data about the DNA
|
||
|
|
||
|
Attributes
|
||
|
----------
|
||
|
@type descriptor: Descriptor
|
||
|
@param descriptor: Data representing the descriptor part of the DNA
|
||
|
|
||
|
@type definition: Definition
|
||
|
@param definition: Data representing the definition part of the DNA
|
||
|
|
||
|
@type behavior: Behavior
|
||
|
@param behavior: Data representing the behavior part of the DNA
|
||
|
|
||
|
@type geometry: Geometry
|
||
|
@param geometry: Data representing the geometry part of the DNA
|
||
|
"""
|
||
|
|
||
|
descriptor: Optional[Descriptor] = field(default=None)
|
||
|
definition: Optional[Definition] = field(default=None)
|
||
|
behavior: Optional[Behavior] = field(default=None)
|
||
|
geometry: Optional[Geometry] = field(default=None)
|
||
|
|
||
|
|
||
|
class DNA(BaseDNA):
|
||
|
"""
|
||
|
Used for getting data stored in the models
|
||
|
|
||
|
@type path: str
|
||
|
@param path: The path to the DNA file
|
||
|
"""
|
||
|
|
||
|
def __init__(self, path: str) -> None:
|
||
|
super().__init__()
|
||
|
self.path = path
|
||
|
|
||
|
def read_all_neutral_joints(self) -> List[Joint]:
|
||
|
joints = []
|
||
|
|
||
|
for i, name in enumerate(self.definition.joints.names):
|
||
|
translation = self.definition.neutral_joint_translations[i]
|
||
|
orientation = self.definition.neutral_joint_rotations[i]
|
||
|
parent_name = self.definition.joints.names[
|
||
|
self.definition.joints.parent_index[i]
|
||
|
]
|
||
|
|
||
|
joint = Joint(
|
||
|
name=name,
|
||
|
translation=translation,
|
||
|
orientation=orientation,
|
||
|
parent_name=parent_name,
|
||
|
)
|
||
|
|
||
|
joints.append(joint)
|
||
|
|
||
|
return joints
|
||
|
|
||
|
def get_all_skin_weights_joint_indices_for_mesh(
|
||
|
self, mesh_index: int
|
||
|
) -> List[List[int]]:
|
||
|
return self.geometry.meshes[mesh_index].skin_weights.joint_indices
|
||
|
|
||
|
def get_blend_shape_target_deltas_with_vertex_id(
|
||
|
self, mesh_index: int, blend_shape_target_index: int
|
||
|
) -> List[Tuple[int, Point3]]:
|
||
|
blend_shape = self.geometry.meshes[mesh_index].blend_shapes[
|
||
|
blend_shape_target_index
|
||
|
]
|
||
|
indices = list(blend_shape.deltas.keys())
|
||
|
|
||
|
deltas: List[Point3] = []
|
||
|
for i in indices:
|
||
|
deltas.append(blend_shape.deltas[i])
|
||
|
|
||
|
if not deltas:
|
||
|
return []
|
||
|
|
||
|
return list(zip(indices, deltas))
|
||
|
|
||
|
def get_all_skin_weights_values_for_mesh(
|
||
|
self, mesh_index: int
|
||
|
) -> List[List[float]]:
|
||
|
skin_weight_values = []
|
||
|
mesh = self.geometry.meshes[mesh_index]
|
||
|
for i in range(len(mesh.topology.positions)):
|
||
|
skin_weight_values.append(mesh.skin_weights.values[i])
|
||
|
|
||
|
return skin_weight_values
|
||
|
|
||
|
def get_skin_weight_matrix_for_mesh(
|
||
|
self, mesh_index: int
|
||
|
) -> List[List[Tuple[int, float]]]:
|
||
|
vertex_position_count = len(self.geometry.meshes[mesh_index].topology.positions)
|
||
|
|
||
|
joint_indices = self.get_all_skin_weights_joint_indices_for_mesh(mesh_index)
|
||
|
if len(joint_indices) != vertex_position_count:
|
||
|
raise DNAViewerError(
|
||
|
"Number of joint indices and vertex count don't match!"
|
||
|
)
|
||
|
|
||
|
skin_weight_values = self.get_all_skin_weights_values_for_mesh(mesh_index)
|
||
|
|
||
|
if len(skin_weight_values) != vertex_position_count:
|
||
|
raise DNAViewerError(
|
||
|
"Number of skin weight values and vertex count don't match!"
|
||
|
)
|
||
|
if len(joint_indices) != len(skin_weight_values):
|
||
|
raise DNAViewerError(
|
||
|
"Number of skin weight values and joint indices count don't match for vertex!"
|
||
|
)
|
||
|
|
||
|
weight_matrix = []
|
||
|
for indices, values in zip(joint_indices, skin_weight_values):
|
||
|
if not indices:
|
||
|
raise DNAViewerError(
|
||
|
"JointIndexArray for vertex can't be less than one!"
|
||
|
)
|
||
|
vertex_weights = []
|
||
|
for joint_index, skin_weight_value in zip(indices, values):
|
||
|
vertex_weights.append((joint_index, skin_weight_value))
|
||
|
weight_matrix.append(vertex_weights)
|
||
|
return weight_matrix
|
||
|
|
||
|
def get_vertex_texture_coordinates_for_mesh(self, mesh_index: int) -> List[UV]:
|
||
|
return self.geometry.meshes[mesh_index].topology.texture_coordinates
|
||
|
|
||
|
def get_vertex_normals_for_mesh(self, mesh_index: int) -> List[Point3]:
|
||
|
return self.geometry.meshes[mesh_index].topology.normals
|
||
|
|
||
|
def get_raw_control_names(self) -> List[str]:
|
||
|
return self.definition.raw_control_names
|
||
|
|
||
|
def get_animated_map_names(self) -> List[str]:
|
||
|
return self.definition.animated_maps.names
|
||
|
|
||
|
def get_vertex_positions_for_mesh_index(self, mesh_index: int) -> List[Point3]:
|
||
|
return self.geometry.meshes[mesh_index].topology.positions
|
||
|
|
||
|
def get_vertex_layout_positions_for_mesh_index(self, mesh_index: int) -> List[int]:
|
||
|
return [
|
||
|
item.position_index
|
||
|
for item in self.geometry.meshes[mesh_index].topology.layouts
|
||
|
]
|
||
|
|
||
|
def get_faces(self, mesh_index: int) -> List[List[int]]:
|
||
|
return self.geometry.meshes[mesh_index].topology.face_vertex_layouts
|
||
|
|
||
|
def get_polygon_faces_and_connects(
|
||
|
self,
|
||
|
mesh_index: int = None,
|
||
|
dna_faces: List[List[int]] = None,
|
||
|
dna_vertex_layout_positions: List[int] = None,
|
||
|
) -> Tuple[List[int], List[int]]:
|
||
|
if mesh_index is None:
|
||
|
if None in (dna_faces, dna_vertex_layout_positions):
|
||
|
raise DNAViewerError(
|
||
|
"get_polygon_faces_and_connects -> Must provide either mesh_index or dna_faces and dna_vertex_layout_positions"
|
||
|
)
|
||
|
if dna_faces is None:
|
||
|
dna_faces = self.get_faces(mesh_index)
|
||
|
if dna_vertex_layout_positions is None:
|
||
|
dna_vertex_layout_positions = (
|
||
|
self.get_vertex_layout_positions_for_mesh_index(mesh_index)
|
||
|
)
|
||
|
|
||
|
polygon_faces = []
|
||
|
polygon_connects = []
|
||
|
for vertices_layout_index_array in dna_faces:
|
||
|
polygon_faces.append(len(vertices_layout_index_array))
|
||
|
for vertex_layout_index_array in vertices_layout_index_array:
|
||
|
polygon_connects.append(
|
||
|
dna_vertex_layout_positions[vertex_layout_index_array]
|
||
|
)
|
||
|
|
||
|
return polygon_faces, polygon_connects
|
||
|
|
||
|
def get_layouts_for_mesh_index(self, mesh_index: int) -> List[Layout]:
|
||
|
return self.geometry.meshes[mesh_index].topology.layouts
|
||
|
|
||
|
def get_texture_coordinate_index(self, mesh_index: int, layout_id: int) -> int:
|
||
|
return (
|
||
|
self.geometry.meshes[mesh_index]
|
||
|
.topology.layouts[layout_id]
|
||
|
.texture_coordinate_index
|
||
|
)
|
||
|
|
||
|
def get_normal_for_mesh_index_and_layout(
|
||
|
self, mesh_index: int, layout: Layout
|
||
|
) -> Point3:
|
||
|
return self.geometry.meshes[mesh_index].topology.normals[layout.normal_index]
|
||
|
|
||
|
def has_blend_shapes(self, mesh_index: int) -> bool:
|
||
|
return (
|
||
|
len([bs.channel for bs in self.geometry.meshes[mesh_index].blend_shapes])
|
||
|
> 0
|
||
|
)
|
||
|
|
||
|
def get_maximum_influence_per_vertex(self, mesh_index: int) -> int:
|
||
|
return self.geometry.meshes[
|
||
|
mesh_index
|
||
|
].skin_weights.maximum_influence_per_vertex
|
||
|
|
||
|
def get_blend_shape_name(self, blend_shape_channel: int) -> str:
|
||
|
return self.definition.blend_shape_channels.names[blend_shape_channel]
|
||
|
|
||
|
def get_geometry(self) -> Geometry:
|
||
|
return self.geometry
|
||
|
|
||
|
def get_mesh_name(self, mesh_index: int) -> str:
|
||
|
return self.definition.meshes.names[mesh_index]
|
||
|
|
||
|
def get_mesh_names(self) -> List[str]:
|
||
|
return self.definition.meshes.names
|
||
|
|
||
|
def get_translation_unit_int(self) -> int:
|
||
|
return self.descriptor.translation_unit
|
||
|
|
||
|
def get_linear_unit(self) -> LinearUnit:
|
||
|
return Conversion.get_linear_unit_from_int(self.get_translation_unit_int())
|
||
|
|
||
|
def get_angle_unit_int(self) -> int:
|
||
|
return self.descriptor.rotation_unit
|
||
|
|
||
|
def get_angle_unit(self) -> AngleUnit:
|
||
|
return Conversion.get_angle_unit_from_int(self.get_angle_unit_int())
|
||
|
|
||
|
def get_lod_count(self) -> int:
|
||
|
return self.descriptor.lod_count
|
||
|
|
||
|
def get_mesh_indices_for_lod(self, lod: int) -> List[int]:
|
||
|
return self.definition.meshes.indices_for_lod[lod]
|
||
|
|
||
|
def get_joint_indices_for_lod(self, lod: int) -> List[int]:
|
||
|
return self.definition.joints.indices_for_lod[lod]
|
||
|
|
||
|
def get_lowest_lod_containing_meshes(
|
||
|
self, mesh_indices: List[int]
|
||
|
) -> Optional[int]:
|
||
|
unique_mesh_indices = set(mesh_indices)
|
||
|
for lod in range(self.get_lod_count()):
|
||
|
if any(list(unique_mesh_indices & set(self.get_mesh_indices_for_lod(lod)))):
|
||
|
return lod
|
||
|
return None
|
||
|
|
||
|
def get_character_name(self) -> str:
|
||
|
return self.descriptor.name
|
||
|
|
||
|
def get_meshes_by_lods(self, mesh_indices: List[int]) -> List[List[int]]:
|
||
|
result_list = []
|
||
|
for lod in range(self.get_lod_count()):
|
||
|
temp = list(set(mesh_indices) & set(self.get_mesh_indices_for_lod(lod)))
|
||
|
result_list.append(temp)
|
||
|
return result_list
|
||
|
|
||
|
def get_blend_shapes(self, mesh_index: int) -> List[BlendShape]:
|
||
|
return self.geometry.meshes[mesh_index].blend_shapes
|
||
|
|
||
|
def get_mesh_data(self, mesh_index: int) -> Mesh:
|
||
|
return self.geometry.meshes[mesh_index]
|
||
|
|
||
|
def get_mesh_id_from_mesh_name(self, mesh_name: str) -> Optional[int]:
|
||
|
for mesh_id, mesh in enumerate(self.geometry.meshes.values()):
|
||
|
if mesh.name == mesh_name:
|
||
|
return mesh_id
|
||
|
return None
|
||
|
|
||
|
def get_mesh_count(self) -> int:
|
||
|
return len(self.geometry.meshes.values())
|