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

371
scripts/dnalib/behavior.py Normal file
View File

@@ -0,0 +1,371 @@
from dataclasses import dataclass, field
from typing import List, Optional, cast
from dna import BinaryStreamReader as DNAReader
from .definition import Definition
from .layer import Layer
class Behavior(Definition):
"""
@type reader: BinaryStreamReader
@param reader: The binary stream reader being used
@type gui_to_raw: ConditionalTable
@param gui_to_raw: Mapping data about gui to raw values
@type psd: PSDMatrix
@param psd: The data representing Pose Space Deformation
@type blend_shapes: BlendShapesData
@param blend_shapes: The data representing blend shapes
@type animated_maps: AnimatedMapsConditionalTable
@param animated_maps: The data representing animated maps
@type joints: JointGroups
@param joints: The data representing joints
"""
def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None:
super().__init__(reader, layers)
self.gui_to_raw = ConditionalTable()
self.psd = PSDMatrix()
self.blend_shapes = BlendShapesData()
self.animated_maps_conditional_table = AnimatedMapsConditionalTable()
self.joint_groups = JointGroups()
self.behavior_read = False
def start_read(self) -> None:
super().start_read()
self.behavior_read = False
def is_read(self) -> bool:
return super().is_read() and self.behavior_read
def read(self) -> None:
"""
Starts reading in the behavior part of the DNA
"""
super().read()
if not self.behavior_read and self.layer_enabled(Layer.behavior):
self.behavior_read = True
self.add_gui_to_raw()
self.add_psd()
self.add_joint_groups()
self.add_blend_shapes()
self.add_animated_maps_conditional_table()
def get_animated_map_lods(self) -> List[int]:
return cast(List[int], self.reader.getAnimatedMapLODs())
def get_animated_map_from_values(self) -> List[float]:
return cast(List[float], self.reader.getAnimatedMapFromValues())
def get_animated_map_to_values(self) -> List[float]:
return cast(List[float], self.reader.getAnimatedMapToValues())
def get_animated_map_slope_values(self) -> List[float]:
return cast(List[float], self.reader.getAnimatedMapSlopeValues())
def get_animated_map_cut_values(self) -> List[float]:
return cast(List[float], self.reader.getAnimatedMapCutValues())
def get_animated_map_input_indices(self) -> List[int]:
return cast(List[int], self.reader.getAnimatedMapInputIndices())
def get_animated_map_output_indices(self) -> List[int]:
return cast(List[int], self.reader.getAnimatedMapOutputIndices())
def get_gui_to_raw_from_values(self) -> List[float]:
return cast(List[float], self.reader.getGUIToRawFromValues())
def get_gui_to_raw_to_values(self) -> List[float]:
return cast(List[float], self.reader.getGUIToRawToValues())
def gget_gui_to_raw_slope_values(self) -> List[float]:
return cast(List[float], self.reader.getGUIToRawSlopeValues())
def get_gui_to_raw_cut_values(self) -> List[float]:
return cast(List[float], self.reader.getGUIToRawCutValues())
def get_gui_to_raw_input_indices(self) -> List[int]:
return cast(List[int], self.reader.getGUIToRawInputIndices())
def get_gui_to_raw_output_indices(self) -> List[int]:
return cast(List[int], self.reader.getGUIToRawOutputIndices())
def get_psd_count(self) -> int:
return cast(int, self.reader.getPSDCount())
def get_psd_row_indices(self) -> List[int]:
return cast(List[int], self.reader.getPSDRowIndices())
def get_psd_column_indices(self) -> List[int]:
return cast(List[int], self.reader.getPSDColumnIndices())
def get_psd_values(self) -> List[float]:
return cast(List[float], self.reader.getPSDValues())
def get_blend_shape_channel_lods(self) -> List[int]:
return cast(List[int], self.reader.getBlendShapeChannelLODs())
def get_blend_shape_channel_input_indices(self) -> List[int]:
return cast(List[int], self.reader.getBlendShapeChannelInputIndices())
def get_blend_shape_channel_output_indices(self) -> List[int]:
return cast(List[int], self.reader.getBlendShapeChannelOutputIndices())
def get_joint_row_count(self) -> int:
return cast(int, self.reader.getJointRowCount())
def get_joint_column_count(self) -> int:
return cast(int, self.reader.getJointColumnCount())
def get_joint_variable_attribute_indices(self) -> int:
return cast(int, self.reader.getJointVariableAttributeIndices())
def get_joint_group_count(self) -> int:
return cast(int, self.reader.getJointGroupCount())
def get_joint_group_logs(self, joint_group_index: int) -> List[int]:
return cast(List[int], self.reader.getJointGroupLODs(joint_group_index))
def get_joint_group_input_indices(self, joint_group_index: int) -> List[int]:
return cast(List[int], self.reader.getJointGroupInputIndices(joint_group_index))
def get_joint_group_output_indices(self, joint_group_index: int) -> List[int]:
return cast(
List[int], self.reader.getJointGroupOutputIndices(joint_group_index)
)
def get_joint_group_values(self, joint_group_index: int) -> List[float]:
return cast(List[float], self.reader.getJointGroupValues(joint_group_index))
def get_joint_group_joint_indices(self, joint_group_index: int) -> List[int]:
return cast(List[int], self.reader.getJointGroupJointIndices(joint_group_index))
def add_gui_to_raw(self) -> None:
"""Reads in the gui to raw mapping"""
self.reader.gui_to_raw = ConditionalTable(
inputs=self.get_gui_to_raw_input_indices(),
outputs=self.get_gui_to_raw_output_indices(),
from_values=self.get_gui_to_raw_from_values(),
to_values=self.get_gui_to_raw_to_values(),
slope_values=self.gget_gui_to_raw_slope_values(),
cut_values=self.get_gui_to_raw_cut_values(),
)
def add_psd(self) -> None:
"""Reads in the PSD part of the behavior"""
self.psd = PSDMatrix(
count=self.get_psd_count(),
rows=self.get_psd_row_indices(),
columns=self.get_psd_column_indices(),
values=self.get_psd_values(),
)
def add_joint_groups(self) -> None:
"""Reads in the joints part of the behavior"""
self.joint_groups.joint_row_count = self.reader.getJointRowCount()
self.joint_groups.joint_column_count = self.reader.getJointColumnCount()
for lod in range(self.get_lod_count()):
self.joint_groups.joint_variable_attribute_indices.append(
self.reader.getJointVariableAttributeIndices(lod)
)
for joint_group_index in range(self.get_joint_group_count()):
self.joint_groups.joint_groups.append(
JointGroup(
lods=self.get_joint_group_logs(joint_group_index),
inputs=self.get_joint_group_input_indices(joint_group_index),
outputs=self.get_joint_group_output_indices(joint_group_index),
values=self.get_joint_group_values(joint_group_index),
joints=self.get_joint_group_joint_indices(joint_group_index),
)
)
def add_blend_shapes(self) -> None:
"""Reads in the blend shapes part of the behavior"""
self.blend_shapes = BlendShapesData(
lods=self.get_blend_shape_channel_lods(),
inputs=self.get_blend_shape_channel_input_indices(),
outputs=self.get_blend_shape_channel_output_indices(),
)
def add_animated_maps_conditional_table(self) -> None:
"""Reads in the animated maps part of the behavior"""
self.reader.animated_maps_conditional_table = AnimatedMapsConditionalTable(
lods=self.get_animated_map_lods(),
conditional_table=ConditionalTable(
from_values=self.get_animated_map_from_values(),
to_values=self.get_animated_map_to_values(),
slope_values=self.get_animated_map_slope_values(),
cut_values=self.get_animated_map_cut_values(),
inputs=self.get_animated_map_input_indices(),
outputs=self.get_animated_map_output_indices(),
),
)
@dataclass
class ConditionalTable:
"""
A model class for holding various values
Attributes
----------
@type from_values: List[float]
@param from_values: The list of values
@type to_values: List[float]
@param to_values: The list of values
@type slope_values: List[float]
@param slope_values: The list of slope values
@type cut_values: List[float]
@param cut_values: The list of cut values
@type inputs: List[int]
@param inputs: The indices of inputs
@type outputs: List[int]
@param outputs: The indices of outputs
"""
from_values: List[float] = field(default_factory=list)
to_values: List[float] = field(default_factory=list)
slope_values: List[float] = field(default_factory=list)
cut_values: List[float] = field(default_factory=list)
inputs: List[int] = field(default_factory=list)
outputs: List[int] = field(default_factory=list)
@dataclass
class PSDMatrix:
"""
A model class for holding data about Pose Space Deformation
Attributes
----------
@type count: int
@param count: The list of values
@type rows: List[int]
@param rows: List of row indices used for storing values
@type columns: List[int]
@param columns: List of row indices used for storing values
@type values: List[float]
@param values: The list of values, that can be accessed from the row and column index
"""
count: Optional[int] = field(default=None)
rows: List[int] = field(default_factory=list)
columns: List[int] = field(default_factory=list)
values: List[float] = field(default_factory=list)
@dataclass
class JointGroup:
"""
A model class for holding data about joint groups
Attributes
----------
@type lods: List[int]
@param lods: A list of lod indices that the joint group is contained within
@type values: List[float]
@param values: A list of values
@type joints: List[int]
@param joints: A list of joint indices
@type inputs: List[int]
@param inputs: The indices of inputs
@type outputs: List[int]
@param outputs: The indices of outputs
"""
lods: List[int] = field(default_factory=list)
values: List[float] = field(default_factory=list)
joints: List[int] = field(default_factory=list)
inputs: List[int] = field(default_factory=list)
outputs: List[int] = field(default_factory=list)
@dataclass
class BlendShapesData:
"""
A model class for holding data about blend shapes
Attributes
----------
@type lods: List[int]
@param lods: A list of lod indices that the blend shapes are contained within
@type inputs: List[int]
@param inputs: The indices of inputs
@type outputs: List[int]
@param outputs: The indices of outputs
"""
lods: List[int] = field(default_factory=list)
inputs: List[int] = field(default_factory=list)
outputs: List[int] = field(default_factory=list)
@dataclass
class AnimatedMapsConditionalTable:
"""
A model class for holding data about animated maps
Attributes
----------
@type lods: List[int]
@param lods: A list of lod indices that the blend shapes are contained within
@type conditional_table: ConditionalTable
@param conditional_table: Data needed for animated maps
"""
lods: List[int] = field(default_factory=list)
conditional_table: ConditionalTable = field(default_factory=ConditionalTable)
@dataclass
class JointGroups:
"""
A model class for storing data about joints
Attributes
----------
@type joint_row_count: int
@param joint_row_count: The row count of the matrix that stores the joints data
@type joint_column_count: int
@param joint_column_count: The column count of the matrix that stores the joints data
@type joint_variable_attribute_indices: List[List[int]]
@param joint_variable_attribute_indices: List of joint variable attribute indices per LOD
@type joint_groups: List[JointGroup]
@param joint_groups: The list of joint groups
"""
joint_row_count: Optional[int] = field(default=None)
joint_column_count: Optional[int] = field(default=None)
joint_variable_attribute_indices: List[List[int]] = field(default_factory=list)
joint_groups: List[JointGroup] = field(default_factory=list)

View File

@@ -0,0 +1,333 @@
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, cast
from dna import BinaryStreamReader as DNAReader
from dna import MeshBlendShapeChannelMapping
from ..model import Point3
from .descriptor import Descriptor
from .layer import Layer
class Definition(Descriptor):
"""
A class used for reading and accessing the definition part of the DNA file
Attributes
----------
@type reader: BinaryStreamReader
@param reader: The binary stream reader being used
@type definition: DefinitionModel
@param definition: The object that holds the definition data read from the DNA file
@type joints: Joints
@param joints: The data about joints
@type blend_shape_channels: GeometryEntity
@param blend_shape_channels: The names and indices of blend shape channels
@type animated_maps: GeometryEntity
@param animated_maps: The names and indices of animated maps
@type meshes: GeometryEntity
@param meshes: The names and indices of the meshes
@type gui_control_names: List[str]
@param gui_control_names: The list of gui control names
@type raw_control_names: List[str]
@param raw_control_names: The list of raw control names
@type mesh_blend_shape_channel_mapping: List[Tuple[int, int]]
@param mesh_blend_shape_channel_mapping: Mapping of mesh index to the blend shape channel index
@type mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]]
@param mesh_blend_shape_channel_mapping_indices_for_lod: The list of blend shape channel mapping indices by lod
@type neutral_joint_translations: List[Point3]
@param neutral_joint_translations: The list of neutral joint translations
@type neutral_joint_rotations: List[Point3]
@param neutral_joint_rotations: The list of neutral joint rotations
"""
def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None:
super().__init__(reader, layers)
self.joints = Joints()
self.blend_shape_channels = GeometryEntity()
self.animated_maps = GeometryEntity()
self.meshes = GeometryEntity()
self.meshes_mapping: Dict[str, int] = {}
self.gui_control_names: List[str] = []
self.raw_control_names: List[str] = []
self.mesh_blend_shape_channel_mapping: List[Tuple[int, int]] = []
self.mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] = []
self.neutral_joint_translations: List[Point3] = []
self.neutral_joint_rotations: List[Point3] = []
self.definition_read = False
def start_read(self) -> None:
super().start_read()
self.definition_read = False
def is_read(self) -> bool:
return super().is_read() and self.definition_read
def read(self) -> None:
"""
Starts reading in the definition part of the DNA
@rtype: DefinitionModel
@returns: the instance of the created definition model
"""
super().read()
if not self.definition_read and self.layer_enabled(Layer.definition):
self.definition_read = True
self.add_controls()
self.add_joints()
self.add_blend_shape_channels()
self.add_animated_maps()
self.add_meshes()
self.add_mesh_blend_shape_channel_mapping()
self.add_neutral_joints()
def get_lod_count(self) -> int:
return cast(int, self.reader.getLODCount())
def get_gui_control_count(self) -> int:
return cast(int, self.reader.getGUIControlCount())
def get_gui_control_name(self, index: int) -> str:
return cast(str, self.reader.getGUIControlName(index))
def get_raw_control_count(self) -> int:
return cast(int, self.reader.getRawControlCount())
def get_raw_control_name(self, index: int) -> str:
return cast(str, self.reader.getRawControlName(index))
def get_raw_control_names(self) -> List[str]:
names = []
for i in range(self.get_raw_control_count()):
names.append(self.get_raw_control_name(i))
return names
def get_neutral_joint_translation(self, index: int) -> Point3:
translation = cast(List[float], self.reader.getNeutralJointTranslation(index))
return Point3(translation[0], translation[1], translation[2])
def get_neutral_joint_translation_xs(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointTranslationXs())
def get_neutral_joint_translation_ys(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointTranslationYs())
def get_neutral_joint_translation_zs(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointTranslationZs())
def get_neutral_joint_rotation(self, index: int) -> Point3:
translation = cast(List[float], self.reader.getNeutralJointRotation(index))
return Point3(translation[0], translation[1], translation[2])
def get_neutral_joint_rotation_xs(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointRotationXs())
def get_neutral_joint_rotation_ys(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointRotationYs())
def get_neutral_joint_rotation_zs(self) -> List[float]:
return cast(List[float], self.reader.getNeutralJointRotationZs())
def get_mesh_blend_shape_channel_mapping_count(self) -> int:
return cast(int, self.reader.getMeshBlendShapeChannelMappingCount())
def get_mesh_blend_shape_channel_mapping(
self, index: int
) -> MeshBlendShapeChannelMapping:
return cast(
MeshBlendShapeChannelMapping,
self.reader.getMeshBlendShapeChannelMapping(index),
)
def get_mesh_blend_shape_channel_mapping_for_lod(self, lod: int) -> List[int]:
return cast(
List[int], self.reader.getMeshBlendShapeChannelMappingIndicesForLOD(lod)
)
def get_joint_count(self) -> int:
return cast(int, self.reader.getJointCount())
def get_joint_name(self, index: int) -> str:
return cast(str, self.reader.getJointName(index))
def get_joint_parent_index(self, index: int) -> int:
return cast(int, self.reader.getJointParentIndex(index))
def get_joint_indices_for_lod(self, index: int) -> List[int]:
return cast(List[int], self.reader.getJointIndicesForLOD(index))
def get_blend_shape_channel_count(self) -> int:
return cast(int, self.reader.getBlendShapeChannelCount())
def get_blend_shape_channel_name(self, index: int) -> str:
return cast(str, self.reader.getBlendShapeChannelName(index))
def get_mesh_count(self) -> int:
return cast(int, self.reader.getMeshCount())
def get_mesh_name(self, index: int) -> str:
return cast(str, self.reader.getMeshName(index))
def get_mesh_indices_for_lod(self, index: int) -> List[int]:
return cast(List[int], self.reader.getMeshIndicesForLOD(index))
def get_blend_shape_channel_indices_for_lod(self, index: int) -> List[int]:
return cast(List[int], self.reader.getBlendShapeChannelIndicesForLOD(index))
def get_animated_map_count(self) -> int:
return cast(int, self.reader.getAnimatedMapCount())
def get_animated_map_name(self, index: int) -> str:
return cast(str, self.reader.getAnimatedMapName(index))
def get_animated_map_names(self) -> List[str]:
names = []
for i in range(self.get_animated_map_count()):
names.append(self.get_animated_map_name(i))
return names
def get_animated_map_indices_for_lod(self, index: int) -> List[int]:
return cast(List[int], self.reader.getAnimatedMapIndicesForLOD(index))
def get_translation_unit(self) -> int:
return cast(int, self.reader.getTranslationUnit())
def get_rotation_unit(self) -> int:
return cast(int, self.reader.getRotationUnit())
def add_neutral_joints(self) -> None:
"""Reads in the neutral joints part of the definition"""
neutral_joint_translation_xs = self.get_neutral_joint_translation_xs()
neutral_joint_translation_ys = self.get_neutral_joint_translation_ys()
neutral_joint_translation_zs = self.get_neutral_joint_translation_zs()
neutral_joint_translation_count_x = len(neutral_joint_translation_xs)
for index in range(neutral_joint_translation_count_x):
self.neutral_joint_translations.append(
Point3(
x=neutral_joint_translation_xs[index],
y=neutral_joint_translation_ys[index],
z=neutral_joint_translation_zs[index],
)
)
neutral_joint_rotation_xs = self.get_neutral_joint_rotation_xs()
neutral_joint_rotation_ys = self.get_neutral_joint_rotation_ys()
neutral_joint_rotation_zs = self.get_neutral_joint_rotation_zs()
neutral_joint_rotation_count_x = len(neutral_joint_rotation_xs)
for index in range(neutral_joint_rotation_count_x):
self.neutral_joint_rotations.append(
Point3(
x=neutral_joint_rotation_xs[index],
y=neutral_joint_rotation_ys[index],
z=neutral_joint_rotation_zs[index],
)
)
def add_mesh_blend_shape_channel_mapping(self) -> None:
"""Reads in the mesh blend shape channel mapping"""
for index in range(self.get_mesh_blend_shape_channel_mapping_count()):
mapping = self.get_mesh_blend_shape_channel_mapping(index)
self.mesh_blend_shape_channel_mapping.append(
(mapping.meshIndex, mapping.blendShapeChannelIndex)
)
for lod in range(self.get_lod_count()):
self.mesh_blend_shape_channel_mapping_indices_for_lod.append(
self.get_mesh_blend_shape_channel_mapping_for_lod(lod)
)
def add_meshes(self) -> None:
"""Reads in the meshes of the definition"""
for index in range(self.get_mesh_count()):
mesh_name = self.get_mesh_name(index)
self.meshes.names.append(mesh_name)
self.meshes_mapping[mesh_name] = index
for index in range(self.get_lod_count()):
self.meshes.lod_indices.append(self.get_mesh_indices_for_lod(index))
def add_animated_maps(self) -> None:
"""Reads in the animated maps of the definition"""
for index in range(self.get_animated_map_count()):
self.animated_maps.names.append(self.get_animated_map_name(index))
for index in range(self.get_lod_count()):
self.animated_maps.lod_indices.append(
self.get_animated_map_indices_for_lod(index)
)
def add_blend_shape_channels(self) -> None:
"""Reads in the neutral joints part of the definition"""
for index in range(self.get_blend_shape_channel_count()):
self.blend_shape_channels.names.append(
self.get_blend_shape_channel_name(index)
)
for index in range(self.get_lod_count()):
self.blend_shape_channels.lod_indices.append(
self.get_blend_shape_channel_indices_for_lod(index)
)
def add_joints(self) -> None:
"""Reads in the joints of the definition"""
for index in range(self.get_joint_count()):
self.joints.names.append(self.get_joint_name(index))
self.joints.parent_index.append(self.get_joint_parent_index(index))
for index in range(self.get_lod_count()):
self.joints.lod_indices.append(self.get_joint_indices_for_lod(index))
def add_controls(self) -> None:
"""Reads in the gui and raw controls of the definition"""
for index in range(self.get_gui_control_count()):
self.gui_control_names.append(self.get_gui_control_name(index))
for index in range(self.get_raw_control_count()):
self.raw_control_names.append(self.get_raw_control_name(index))
@dataclass
class GeometryEntity:
"""
A model class for holding names and indices
Attributes
----------
@type names: List[str]
@param names: List of names
@type lod_indices: List[List[int]]
@param lod_indices: List of indices per lod
"""
names: List[str] = field(default_factory=list)
lod_indices: List[List[int]] = field(default_factory=list)
@dataclass
class Joints(GeometryEntity):
"""
A model class for holding data about the joints
Attributes
----------
@type parent_index: List[int]
@param parent_index: List of parent indices for each joint index
"""
parent_index: List[int] = field(default_factory=list)

View File

@@ -0,0 +1,129 @@
from typing import Dict, List, Optional, Tuple
from dna import BinaryStreamReader as DNAReader
from ..dnalib.layer import Layer
class Descriptor:
"""
A class used for reading and accessing the descriptor part of the DNA file
Attributes
----------
@type name: str
@param name: The name of the character
@type archetype: int
@param archetype: A value that represents the archetype of the character
@type gender: int
@param gender: A value that represents the gender of the character
@type age: int
@param age: The age of the character
@type metadata: Dict[str, str]
@param metadata: Metadata stored for the character
@type translation_unit: int
@param translation_unit: The translation unit that was used for creating the character
@type rotation_unit: int
@param rotation_unit: The translation unit that was used for creating the character
@type coordinate_system: Tuple[int, int, int]
@param coordinate_system: A tuple representing the coordinate system
@type lod_count: int
@param lod_count: The number of LODs for the characters
@type db_max_lod:int
@param db_max_lod: A LOD constraint representing the greatest LOD we wish wish to produce (ie. if the value is n, the potential LODs are 0, 1, .. n-1)
@type db_complexity: str
@param db_complexity: Will be used in future
@type db_name: str
@param db_name: DB identifier
"""
def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None:
self.reader = reader
self.layers = layers
self.name: Optional[str] = None
self.archetype: Optional[int] = None
self.gender: Optional[int] = None
self.age: Optional[int] = None
self.metadata: Dict[str, str] = {}
self.translation_unit: Optional[int] = None
self.rotation_unit: Optional[int] = None
self.coordinate_system: Optional[Tuple[int, int, int]] = None
self.lod_count: Optional[int] = None
self.db_max_lod: Optional[int] = None
self.db_complexity: Optional[str] = None
self.db_name: Optional[str] = None
self.descriptor_read = False
def start_read(self) -> None:
self.descriptor_read = False
def is_read(self) -> bool:
return self.descriptor_read
def layer_enabled(self, layer: Layer) -> bool:
return layer in self.layers or Layer.all in self.layers
def read(self) -> None:
"""
Starts reading in the descriptor part of the DNA
@rtype: DescriptorModel
@returns: the instance of the created descriptor model
"""
if not self.descriptor_read and self.layer_enabled(Layer.descriptor):
self.descriptor_read = True
self.add_basic_data()
self.add_metadata()
self.add_geometry_data()
self.add_db_data()
def add_basic_data(self) -> None:
"""Reads in the character name, archetype, gender and age"""
self.name = self.reader.getName()
self.archetype = self.reader.getArchetype()
self.gender = self.reader.getGender()
self.age = self.reader.getAge()
def add_metadata(self) -> None:
"""Reads in the metadata provided from the DNA file"""
for i in range(self.reader.getMetaDataCount()):
key = self.reader.getMetaDataKey(i)
self.metadata[key] = self.reader.getMetaDataValue(key)
def add_geometry_data(self) -> None:
"""Sets the translation unit, rotation unit, and coordinate system from the DNA file"""
self.translation_unit = self.reader.getTranslationUnit()
self.rotation_unit = self.reader.getRotationUnit()
coordinate_system = self.reader.getCoordinateSystem()
self.coordinate_system = (
coordinate_system.xAxis,
coordinate_system.yAxis,
coordinate_system.zAxis,
)
def add_db_data(self) -> None:
"""Reads in the db data from the DNA file"""
self.lod_count = self.reader.getLODCount()
self.db_max_lod = self.reader.getDBMaxLOD()
self.db_complexity = self.reader.getDBComplexity()
self.db_name = self.reader.getDBName()

250
scripts/dnalib/dnalib.py Normal file
View File

@@ -0,0 +1,250 @@
from typing import List, Optional, Tuple
from dna import BinaryStreamReader as DNAReader
from dna import DataLayer_All, FileStream, Status
from ..common import DNAViewerError
from ..model import UV, BlendShape, Joint, Layout, Point3
from .behavior import Behavior
from .geometry import Geometry
from .layer import Layer
class DNA(Behavior, Geometry):
"""
A class used for accessing data in DNA file.
@type dna_path: str
@param dna_path: The path of the DNA file
@type layers: Optional[List[Layer]]
@param layers: List of parts of DNA to be loaded. If noting is passed, whole DNA is going to be loaded. Same as
passing Layer.all.
"""
def __init__(self, dna_path: str, layers: Optional[List[Layer]] = None) -> None:
self.path = dna_path
self.reader = self.create_reader(dna_path)
layers = layers or [Layer.all]
Behavior.__init__(self, self.reader, layers)
Geometry.__init__(self, self.reader, layers)
self.read()
def create_reader(self, dna_path: str) -> DNAReader:
"""
Creates a stream reader needed for reading values from the DNA file.
@type dna_path: str
@param dna_path: The path of the DNA file
@rtype: DNA
@returns: The reader needed for reading values from the DNA file
"""
stream = FileStream(
dna_path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary
)
reader = DNAReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def is_read(self) -> bool:
return Behavior.is_read(self) and Geometry.is_read(self)
def read(self) -> None:
if not self.is_read():
self.start_read()
Behavior.read(self)
Geometry.read(self)
def read_all_neutral_joints(self) -> List[Joint]:
joints = []
for i in range(self.get_joint_count()):
name = self.get_joint_name(i)
translation = self.get_neutral_joint_translation(i)
orientation = self.get_neutral_joint_rotation(i)
parent_name = self.get_joint_name(self.get_joint_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_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 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_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_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_all_meshes_grouped_by_lod(self) -> List[List[int]]:
"""
Gets the list of list of mesh indices grouped by the lod number.
@type dna: DNA
@param dna: Instance of DNA.
@rtype: List[List[int]]
@returns: The list of list of mesh indices grouped by the lod number
"""
result: List[List[int]] = []
for lod in range(self.get_lod_count()):
mesh_indices = []
for mesh_index in self.get_mesh_indices_for_lod(lod):
mesh_indices.append(mesh_index)
result.append(mesh_indices)
return result
def get_blend_shapes(self, mesh_index: int) -> List[BlendShape]:
return self.geometry_meshes[mesh_index].blend_shapes
def get_mesh_id_from_mesh_name(self, mesh_name: str) -> Optional[int]:
return self.meshes_mapping.get(mesh_name, None)

283
scripts/dnalib/geometry.py Normal file
View File

@@ -0,0 +1,283 @@
from typing import Dict, List, Optional, Tuple, cast
from dna import BinaryStreamReader as DNAReader
from ..model import UV, BlendShape, Layout, Mesh, Point3, SkinWeightsData, Topology
from .definition import Definition
from .layer import Layer
class Geometry(Definition):
def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None:
super().__init__(reader, layers)
self.geometry_meshes: List[Mesh] = []
self.geometry_read = False
def start_read(self) -> None:
super().start_read()
self.geometry_read = False
def is_read(self) -> bool:
return super().is_read() and self.geometry_read
def read(self) -> None:
"""
Starts reading in the mesh from the geometry part of the DNA
"""
super().read()
if not self.geometry_read and self.layer_enabled(Layer.geometry):
self.geometry_read = True
self.geometry_meshes = []
for lod in range(self.get_lod_count()):
for mesh_index in self.get_mesh_indices_for_lod(lod):
self.geometry_meshes.append(self.add_mesh(mesh_index))
def get_maximum_influence_per_vertex(self, mesh_index: int) -> int:
return cast(int, self.reader.getMaximumInfluencePerVertex(meshIndex=mesh_index))
def get_vertex_position_count(self, mesh_index: int) -> int:
return cast(int, self.reader.getVertexPositionCount(mesh_index))
def get_skin_weights_values(
self, mesh_index: int, vertex_index: int
) -> List[float]:
return cast(
List[float],
self.reader.getSkinWeightsValues(
meshIndex=mesh_index, vertexIndex=vertex_index
),
)
def get_skin_weights_joint_indices(
self, mesh_index: int, vertex_index: int
) -> List[int]:
return cast(
List[int],
self.reader.getSkinWeightsJointIndices(
meshIndex=mesh_index, vertexIndex=vertex_index
),
)
def get_vertex_texture_coordinate_count(self, mesh_index: int) -> int:
return cast(
int, self.reader.getVertexTextureCoordinateCount(meshIndex=mesh_index)
)
def get_vertex_texture_coordinate(
self, mesh_index: int, texture_coordinate_index: int
) -> Tuple[float, float]:
return cast(
Tuple[float, float],
self.reader.getVertexTextureCoordinate(
meshIndex=mesh_index, textureCoordinateIndex=texture_coordinate_index
),
)
def get_face_count(self, mesh_index: int) -> int:
return cast(int, self.reader.getFaceCount(meshIndex=mesh_index))
def get_face_vertex_layout_indices(
self, mesh_index: int, face_index: int
) -> List[int]:
return cast(
List[int],
self.reader.getFaceVertexLayoutIndices(
meshIndex=mesh_index, faceIndex=face_index
),
)
def get_vertex_layout(
self, mesh_index: int, layout_index: int
) -> Tuple[int, int, int]:
return cast(
Tuple[int, int, int],
self.reader.getVertexLayout(meshIndex=mesh_index, layoutIndex=layout_index),
)
def get_vertex_layout_count(self, mesh_index: int) -> int:
return cast(int, self.reader.getVertexLayoutCount(meshIndex=mesh_index))
def get_vertex_position(
self, mesh_index: int, vertex_index: int
) -> Tuple[float, float, float]:
return cast(
Tuple[float, float, float],
self.reader.getVertexPosition(
meshIndex=mesh_index, vertexIndex=vertex_index
),
)
def get_blend_shape_target_vertex_indices(
self, mesh_index: int, blend_shape_target_index: int
) -> List[int]:
return cast(
List[int],
self.reader.getBlendShapeTargetVertexIndices(
meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index
),
)
def get_blend_shape_target_delta_count(
self, mesh_index: int, blend_shape_target_index: int
) -> int:
return cast(
int,
self.reader.getBlendShapeTargetDeltaCount(
meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index
),
)
def get_blend_shape_target_delta(
self, mesh_index: int, blend_shape_target_index: int, delta_index: int
) -> Tuple[int, int, int]:
return cast(
Tuple[int, int, int],
self.reader.getBlendShapeTargetDelta(
meshIndex=mesh_index,
blendShapeTargetIndex=blend_shape_target_index,
deltaIndex=delta_index,
),
)
def get_blend_shape_target_count(self, mesh_index: int) -> int:
return cast(int, self.reader.getBlendShapeTargetCount(meshIndex=mesh_index))
def get_blend_shape_channel_index(
self, mesh_index: int, blend_shape_target_index: int
) -> int:
return cast(
int,
self.reader.getBlendShapeChannelIndex(
meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index
),
)
def add_mesh(self, mesh_index: int) -> Mesh:
mesh = Mesh()
mesh.name = self.get_mesh_name(mesh_index)
mesh.topology = self.add_mesh_topology(mesh_index)
mesh.skin_weights = self.add_mesh_skin_weights(mesh_index)
mesh.blend_shapes = self.add_mesh_blend_shapes(mesh_index)
return mesh
def add_mesh_skin_weights(self, mesh_index: int) -> SkinWeightsData:
"""Reads in the skin weights"""
skin_weights = SkinWeightsData()
for vertex_index in range(self.get_vertex_position_count(mesh_index)):
skin_weights.values.append(
self.get_skin_weights_values(mesh_index, vertex_index)
)
skin_weights.joint_indices.append(
self.get_skin_weights_joint_indices(mesh_index, vertex_index)
)
return skin_weights
def add_mesh_topology(self, mesh_index: int) -> Topology:
"""Reads in the positions, texture coordinates, normals, layouts and face vertex layouts"""
topology = Topology()
topology.positions = self.add_positions(mesh_index)
topology.texture_coordinates = self.add_texture_coordinates(mesh_index)
topology.layouts = self.add_layouts(mesh_index)
topology.face_vertex_layouts = self.add_face_vertex_layouts(mesh_index)
return topology
def add_face_vertex_layouts(self, mesh_index: int) -> List[List[int]]:
"""Reads in the face vertex layouts"""
face_vertex_layouts = []
for face_index in range(self.get_face_count(mesh_index)):
face_vertex_layouts.append(
self.get_face_vertex_layout_indices(mesh_index, face_index)
)
return face_vertex_layouts
def add_layouts(self, mesh_index: int) -> List[Layout]:
"""Reads in the vertex layouts"""
layouts = []
for layout_index in range(self.get_vertex_layout_count(mesh_index)):
(
position_id,
texture_coordinate_id,
_,
) = self.get_vertex_layout(mesh_index, layout_index)
layouts.append(
Layout(
position_index=position_id,
texture_coordinate_index=texture_coordinate_id,
)
)
return layouts
def add_texture_coordinates(self, mesh_index: int) -> List[UV]:
"""Reads in the texture coordinates"""
texture_coordinates = []
for texture_coordinate_index in range(
self.get_vertex_texture_coordinate_count(mesh_index)
):
u, v = self.get_vertex_texture_coordinate(
mesh_index, texture_coordinate_index
)
texture_coordinates.append(UV(u=u, v=v))
return texture_coordinates
def add_positions(self, mesh_index: int) -> List[Point3]:
"""Reads in the vertex positions"""
positions = []
for vertex_index in range(self.get_vertex_position_count(mesh_index)):
x, y, z = self.get_vertex_position(mesh_index, vertex_index)
positions.append(Point3(x=x, y=y, z=z))
return positions
def read_target_deltas(
self, mesh_index: int, blend_shape_target_index: int
) -> Dict[int, Point3]:
"""
Reads in the target deltas
@rtype: Dict[int, Point3]
@returns: Mapping of vertex indices to positions
"""
result: Dict[int, Point3] = {}
vertices = self.get_blend_shape_target_vertex_indices(
mesh_index, blend_shape_target_index
)
blend_shape_target_delta_count = self.get_blend_shape_target_delta_count(
mesh_index, blend_shape_target_index
)
for delta_index in range(blend_shape_target_delta_count):
x, y, z = self.get_blend_shape_target_delta(
mesh_index, blend_shape_target_index, delta_index
)
result[vertices[delta_index]] = Point3(x=x, y=y, z=z)
return result
def add_mesh_blend_shapes(self, mesh_index: int) -> List[BlendShape]:
"""
Reads in the blend shapes
@type mesh_index: int
@param mesh_index: The mesh index
"""
blend_shape_target_count = self.get_blend_shape_target_count(mesh_index)
blend_shapes = []
for blend_shape_target_index in range(blend_shape_target_count):
blend_shapes.append(
BlendShape(
channel=self.get_blend_shape_channel_index(
mesh_index, blend_shape_target_index
),
deltas=self.read_target_deltas(
mesh_index, blend_shape_target_index
),
)
)
return blend_shapes

9
scripts/dnalib/layer.py Normal file
View File

@@ -0,0 +1,9 @@
from enum import Enum
class Layer(Enum):
descriptor = 1
definition = 2
behavior = 3
geometry = 4
all = 5