Commands that perform useful calculations or provide additional functionality:

Fix “RuntimeError: Error loading DNA: DNA signature mismatched, expected DNA, got ver?”

dna::BehaviorReader::getJointVariableAttributeIndices (C++ function)

dna::BehaviorReader::getBlendShapeChannelInputIndices (C++ function)

dna::BehaviorReader::getBlendShapeChannelOutputIndices (C++ function)

dna::GeometryReader::getVertexTextureCoordinateCount (C++ function)

dna::GeometryReader::getVertexTextureCoordinateUs (C++ function)

dna::GeometryReader::getVertexTextureCoordinateVs (C++ function)

dna::GeometryReader::getVertexLayoutPositionIndices (C++ function)

dna::GeometryReader::getVertexLayoutTextureCoordinateIndices (C++ function)

dna::GeometryReader::getVertexLayoutNormalIndices (C++ function)

dna::GeometryReader::getMaximumInfluencePerVertex (C++ function)

dna::GeometryReader::getBlendShapeTargetDeltaCount (C++ function)

dna::GeometryReader::getBlendShapeTargetVertexIndices (C++ function)

dna::DefinitionReader::getBlendShapeChannelIndexListCount (C++ function)

dna::DefinitionReader::getBlendShapeChannelIndicesForLOD (C++ function)

dna::DefinitionReader::getAnimatedMapIndexListCount (C++ function)

dna::DefinitionReader::getAnimatedMapIndicesForLOD (C++ function)

dna::DefinitionReader::getMeshBlendShapeChannelMappingCount (C++ function)

dna::DefinitionReader::getMeshBlendShapeChannelMapping (C++ function)

dna::DefinitionReader::getNeutralJointTranslation (C++ function)

dna::DefinitionReader::getNeutralJointTranslationXs (C++ function)

dna::DefinitionReader::getNeutralJointTranslationYs (C++ function)

dna::DefinitionReader::getNeutralJointTranslationZs (C++ function)

dna::ReaderImpl::getBlendShapeChannelIndexListCount (C++ function)

dna::ReaderImpl::getBlendShapeChannelIndicesForLOD (C++ function)

dna::ReaderImpl::getMeshBlendShapeChannelMappingCount (C++ function)

dna::ReaderImpl::getMeshBlendShapeChannelMappingIndicesForLOD (C++ function)

dna::ReaderImpl::getJointVariableAttributeIndices (C++ function)

dna::ReaderImpl::getBlendShapeChannelOutputIndices (C++ function)

dna::ReaderImpl::getBlendShapeChannelInputIndices (C++ function)

dna::ReaderImpl::getVertexLayoutTextureCoordinateIndices (C++ function)

dna::ReaderImpl::getBlendShapeTargetVertexIndices (C++ function)

dna::BehaviorWriter::setBlendShapeChannelInputIndices (C++ function)

dna::BehaviorWriter::setBlendShapeChannelOutputIndices (C++ function)

dna::GeometryWriter::clearFaceVertexLayoutIndices (C++ function)

dna::GeometryWriter::setMaximumInfluencePerVertex (C++ function)

dna::GeometryWriter::setBlendShapeTargetVertexIndices (C++ function)

dna::DefinitionWriter::clearBlendShapeChannelNames (C++ function)

dna::DefinitionWriter::clearBlendShapeChannelIndices (C++ function)

dna::DefinitionWriter::setBlendShapeChannelIndices (C++ function)

dna::DefinitionWriter::clearLODBlendShapeChannelMappings (C++ function)

dna::DefinitionWriter::setLODBlendShapeChannelMapping (C++ function)

dna::DefinitionWriter::clearLODAnimatedMapMappings (C++ function)

dna::DefinitionWriter::clearMeshBlendShapeChannelMappings (C++ function)

dna::DefinitionWriter::setMeshBlendShapeChannelMapping (C++ function)

dna::DefinitionWriter::setNeutralJointTranslations (C++ function)

dna::WriterImpl::clearLODBlendShapeChannelMappings (C++ function)

dna::WriterImpl::clearMeshBlendShapeChannelMappings (C++ function)

dna::WriterImpl::setBlendShapeChannelInputIndices (C++ function)

dna::WriterImpl::setBlendShapeChannelOutputIndices (C++ function)

dna::WriterImpl::setBlendShapeTargetVertexIndices (C++ function)

get_all_meshes() (dna_viewer.builder.builder.BuildResult method)

meshes_per_lod (dna_viewer.builder.builder.BuildResult attribute)

add_ctrl_attributes_on_root_joint() (dna_viewer.builder.builder.Builder method)

add_mesh_to_display_layer() (dna_viewer.builder.builder.Builder method)

attach_mesh_to_lod() (dna_viewer.builder.builder.Builder method)

build_meshes_by_lod() (dna_viewer.builder.builder.Builder method)

default_lambert_shader() (dna_viewer.builder.builder.Builder method)

get_angle_unit_from_int() (dna_viewer.builder.builder.Builder method)

get_display_layers() (dna_viewer.builder.builder.Builder method)

get_filtered_meshes() (dna_viewer.builder.builder.Builder method)

get_linear_unit_from_int() (dna_viewer.builder.builder.Builder method)

get_mesh_indices_filter() (dna_viewer.builder.builder.Builder method)

get_mesh_node_fullpath_on_root() (dna_viewer.builder.builder.Builder method)

set_filtered_meshes() (dna_viewer.builder.builder.Builder method)

add_ctrl_attributes_on_root_joint (dna_viewer.builder.config.Config attribute)

blend_shape_group_prefix (dna_viewer.builder.config.Config attribute)

blend_shape_name_postfix (dna_viewer.builder.config.Config attribute)

create_display_layers (dna_viewer.builder.config.Config attribute)

facial_root_joint_name (dna_viewer.builder.config.Config attribute)

skin_cluster_suffix (dna_viewer.builder.config.Config attribute)

animated_map_naming (dna_viewer.builder.config.RigConfig attribute)

blend_shape_naming (dna_viewer.builder.config.RigConfig attribute)

central_driver_name (dna_viewer.builder.config.RigConfig attribute)

left_eye_aim_up_name (dna_viewer.builder.config.RigConfig attribute)

left_eye_driver_name (dna_viewer.builder.config.RigConfig attribute)

left_eye_joint_name (dna_viewer.builder.config.RigConfig attribute)

rig_logic_command (dna_viewer.builder.config.RigConfig attribute)

right_eye_aim_up_name (dna_viewer.builder.config.RigConfig attribute)

right_eye_driver_name (dna_viewer.builder.config.RigConfig attribute)

right_eye_joint_name (dna_viewer.builder.config.RigConfig attribute)

add_analog_gui() (dna_viewer.builder.rig_builder.RigBuilder method)

add_animated_map_attributes() (dna_viewer.builder.rig_builder.RigBuilder method)

add_ctrl_attributes() (dna_viewer.builder.rig_builder.RigBuilder method)

add_eye_locators() (dna_viewer.builder.rig_builder.RigBuilder method)

add_rig_logic() (dna_viewer.builder.rig_builder.RigBuilder method)

position_gui() (dna_viewer.builder.rig_builder.RigBuilder method)

source_py_file() (dna_viewer.builder.rig_builder.RigBuilder method)

add_blend_shapes() (dna_viewer.builder.maya.mesh.MayaMesh method)

add_skin_cluster() (dna_viewer.builder.maya.mesh.MayaMesh method)

add_texture_coordinates() (dna_viewer.builder.maya.mesh.MayaMesh method)

create_blend_shape() (dna_viewer.builder.maya.mesh.MayaMesh method)

create_blend_shape_node() (dna_viewer.builder.maya.mesh.MayaMesh method)

create_blend_shapes() (dna_viewer.builder.maya.mesh.MayaMesh method)

create_mesh_object() (dna_viewer.builder.maya.mesh.MayaMesh method)

create_neutral_mesh() (dna_viewer.builder.maya.mesh.MayaMesh method)

get_texture_data() (dna_viewer.builder.maya.mesh.MayaMesh method)

set_skin_weights() (dna_viewer.builder.maya.mesh.MayaMesh method)

derived_mesh_names (dna_viewer.builder.maya.mesh.Mesh attribute)

dna_vertex_layout_positions (dna_viewer.builder.maya.mesh.Mesh attribute)

dna_vertex_positions (dna_viewer.builder.maya.mesh.Mesh attribute)

joints (dna_viewer.builder.maya.skin_weights.MayaSkinWeights attribute)

skinning_method (dna_viewer.builder.maya.skin_weights.MayaSkinWeights attribute)

vertices_info (dna_viewer.builder.maya.skin_weights.MayaSkinWeights attribute)

get_file_joint_mappings() (in module dna_viewer.builder.maya.skin_weights)

get_skin_weights_data() (in module dna_viewer.builder.maya.skin_weights)

get_skin_weights_from_scene() (in module dna_viewer.builder.maya.skin_weights)

import_skin_weights() (in module dna_viewer.builder.maya.skin_weights)

set_skin_weights_to_scene() (in module dna_viewer.builder.maya.skin_weights)

get_transform() (dna_viewer.builder.maya.util.Maya static method)

get_translation() (dna_viewer.builder.maya.util.Maya static method)

set_translation() (dna_viewer.builder.maya.util.Maya static method)

AnimatedMapsConditionalTable (class in dna_viewer.dnalib.behavior)

lods (dna_viewer.dnalib.behavior.AnimatedMapsConditionalTable attribute)

get_animated_map_cut_values() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_from_values() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_input_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_lods() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_output_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_slope_values() (dna_viewer.dnalib.behavior.Behavior method)

get_animated_map_to_values() (dna_viewer.dnalib.behavior.Behavior method)

get_blend_shape_channel_lods() (dna_viewer.dnalib.behavior.Behavior method)

get_gui_to_raw_cut_values() (dna_viewer.dnalib.behavior.Behavior method)

get_gui_to_raw_from_values() (dna_viewer.dnalib.behavior.Behavior method)

get_gui_to_raw_input_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_gui_to_raw_output_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_gui_to_raw_to_values() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_column_count() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_count() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_input_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_joint_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_logs() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_output_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_group_values() (dna_viewer.dnalib.behavior.Behavior method)

get_joint_row_count() (dna_viewer.dnalib.behavior.Behavior method)

get_psd_column_indices() (dna_viewer.dnalib.behavior.Behavior method)

get_psd_row_indices() (dna_viewer.dnalib.behavior.Behavior method)

gget_gui_to_raw_slope_values() (dna_viewer.dnalib.behavior.Behavior method)

cut_values (dna_viewer.dnalib.behavior.ConditionalTable attribute)

from_values (dna_viewer.dnalib.behavior.ConditionalTable attribute)

slope_values (dna_viewer.dnalib.behavior.ConditionalTable attribute)

to_values (dna_viewer.dnalib.behavior.ConditionalTable attribute)

joint_column_count (dna_viewer.dnalib.behavior.JointGroups attribute)

joint_row_count (dna_viewer.dnalib.behavior.JointGroups attribute)

add_animated_maps() (dna_viewer.dnalib.definition.Definition method)

add_blend_shape_channels() (dna_viewer.dnalib.definition.Definition method)

add_neutral_joints() (dna_viewer.dnalib.definition.Definition method)

get_animated_map_count() (dna_viewer.dnalib.definition.Definition method)

get_animated_map_name() (dna_viewer.dnalib.definition.Definition method)

get_animated_map_names() (dna_viewer.dnalib.definition.Definition method)

get_blend_shape_channel_count() (dna_viewer.dnalib.definition.Definition method)

get_blend_shape_channel_name() (dna_viewer.dnalib.definition.Definition method)

get_gui_control_count() (dna_viewer.dnalib.definition.Definition method)

get_gui_control_name() (dna_viewer.dnalib.definition.Definition method)

get_joint_count() (dna_viewer.dnalib.definition.Definition method)

get_joint_indices_for_lod() (dna_viewer.dnalib.definition.Definition method)

get_joint_name() (dna_viewer.dnalib.definition.Definition method)

get_joint_parent_index() (dna_viewer.dnalib.definition.Definition method)

get_lod_count() (dna_viewer.dnalib.definition.Definition method)

get_mesh_count() (dna_viewer.dnalib.definition.Definition method)

get_mesh_indices_for_lod() (dna_viewer.dnalib.definition.Definition method)

get_mesh_name() (dna_viewer.dnalib.definition.Definition method)

get_neutral_joint_rotation() (dna_viewer.dnalib.definition.Definition method)

get_neutral_joint_rotation_xs() (dna_viewer.dnalib.definition.Definition method)

get_neutral_joint_rotation_ys() (dna_viewer.dnalib.definition.Definition method)

get_neutral_joint_rotation_zs() (dna_viewer.dnalib.definition.Definition method)

get_neutral_joint_translation() (dna_viewer.dnalib.definition.Definition method)

get_raw_control_count() (dna_viewer.dnalib.definition.Definition method)

get_raw_control_name() (dna_viewer.dnalib.definition.Definition method)

get_raw_control_names() (dna_viewer.dnalib.definition.Definition method)

get_rotation_unit() (dna_viewer.dnalib.definition.Definition method)

get_translation_unit() (dna_viewer.dnalib.definition.Definition method)

lod_indices (dna_viewer.dnalib.definition.GeometryEntity attribute)

add_basic_data() (dna_viewer.dnalib.descriptor.Descriptor method)

add_geometry_data() (dna_viewer.dnalib.descriptor.Descriptor method)

layer_enabled() (dna_viewer.dnalib.descriptor.Descriptor method)

get_all_meshes_grouped_by_lod() (dna_viewer.dnalib.dnalib.DNA method)

get_all_skin_weights_values_for_mesh() (dna_viewer.dnalib.dnalib.DNA method)

get_layouts_for_mesh_index() (dna_viewer.dnalib.dnalib.DNA method)

get_lowest_lod_containing_meshes() (dna_viewer.dnalib.dnalib.DNA method)

get_mesh_id_from_mesh_name() (dna_viewer.dnalib.dnalib.DNA method)

get_polygon_faces_and_connects() (dna_viewer.dnalib.dnalib.DNA method)

get_skin_weight_matrix_for_mesh() (dna_viewer.dnalib.dnalib.DNA method)

get_texture_coordinate_index() (dna_viewer.dnalib.dnalib.DNA method)

get_vertex_positions_for_mesh_index() (dna_viewer.dnalib.dnalib.DNA method)

get_vertex_texture_coordinates_for_mesh() (dna_viewer.dnalib.dnalib.DNA method)

add_face_vertex_layouts() (dna_viewer.dnalib.geometry.Geometry method)

add_mesh_blend_shapes() (dna_viewer.dnalib.geometry.Geometry method)

add_mesh_skin_weights() (dna_viewer.dnalib.geometry.Geometry method)

add_mesh_topology() (dna_viewer.dnalib.geometry.Geometry method)

add_texture_coordinates() (dna_viewer.dnalib.geometry.Geometry method)

get_blend_shape_channel_index() (dna_viewer.dnalib.geometry.Geometry method)

get_blend_shape_target_count() (dna_viewer.dnalib.geometry.Geometry method)

get_blend_shape_target_delta() (dna_viewer.dnalib.geometry.Geometry method)

get_face_vertex_layout_indices() (dna_viewer.dnalib.geometry.Geometry method)

get_maximum_influence_per_vertex() (dna_viewer.dnalib.geometry.Geometry method)

get_skin_weights_joint_indices() (dna_viewer.dnalib.geometry.Geometry method)

get_skin_weights_values() (dna_viewer.dnalib.geometry.Geometry method)

get_vertex_layout() (dna_viewer.dnalib.geometry.Geometry method)

get_vertex_layout_count() (dna_viewer.dnalib.geometry.Geometry method)

get_vertex_position() (dna_viewer.dnalib.geometry.Geometry method)

get_vertex_position_count() (dna_viewer.dnalib.geometry.Geometry method)

get_vertex_texture_coordinate() (dna_viewer.dnalib.geometry.Geometry method)

read_target_deltas() (dna_viewer.dnalib.geometry.Geometry method)

activate_window() (dna_viewer.ui.app.DnaViewerWindow static method)

add_ctrl_attributes_on_root_joint() (dna_viewer.ui.app.DnaViewerWindow method)

create_aas_selector() (dna_viewer.ui.app.DnaViewerWindow method)

create_analog_gui_selector() (dna_viewer.ui.app.DnaViewerWindow method)

create_build_options() (dna_viewer.ui.app.DnaViewerWindow method)

create_dna_selector() (dna_viewer.ui.app.DnaViewerWindow method)

create_extra_build_options() (dna_viewer.ui.app.DnaViewerWindow method)

create_file_chooser() (dna_viewer.ui.app.DnaViewerWindow method)

create_gui_selector() (dna_viewer.ui.app.DnaViewerWindow method)

create_load_dna_button() (dna_viewer.ui.app.DnaViewerWindow method)

create_mesh_selector() (dna_viewer.ui.app.DnaViewerWindow method)

create_progress_bar() (dna_viewer.ui.app.DnaViewerWindow method)

ctrl_attributes_on_root_joint_cb (dna_viewer.ui.app.DnaViewerWindow attribute)

enable_additional_build_options() (dna_viewer.ui.app.DnaViewerWindow method)

get_lod_indices_names() (dna_viewer.ui.app.DnaViewerWindow method)

is_enabled_and_checked() (dna_viewer.ui.app.DnaViewerWindow method)

maya_main_window() (dna_viewer.ui.app.DnaViewerWindow static method)

on_load_dna_clicked() (dna_viewer.ui.app.DnaViewerWindow method)

select_analog_gui_path (dna_viewer.ui.app.DnaViewerWindow attribute)

set_riglogic_cb_enabled() (dna_viewer.ui.app.DnaViewerWindow method)

show_message_dialog() (dna_viewer.ui.app.DnaViewerWindow method)

dnac::ClearBlendShapesCommand::ClearBlendShapesCommand (C++ function)

dnac::ClearBlendShapesCommand::~ClearBlendShapesCommand (C++ function)

dnac::RemoveAnimatedMapCommand::RemoveAnimatedMapCommand (C++ function)

dnac::RemoveAnimatedMapCommand::~RemoveAnimatedMapCommand (C++ function)

dnac::RemoveAnimatedMapCommand::setAnimatedMapIndex (C++ function)

dnac::RemoveAnimatedMapCommand::setAnimatedMapIndices (C++ function)

dnac::RemoveAnimatedMapCommand::Impl::setAnimatedMapIndex (C++ function)

dnac::RemoveAnimatedMapCommand::Impl::setAnimatedMapIndices (C++ function)

dnac::RemoveAnimatedMapCommand::Impl::animatedMapIndices (C++ member)

dnac::RemoveBlendShapeCommand::RemoveBlendShapeCommand (C++ function)

dnac::RemoveBlendShapeCommand::~RemoveBlendShapeCommand (C++ function)

dnac::RemoveBlendShapeCommand::setBlendShapeIndex (C++ function)

dnac::RemoveBlendShapeCommand::setBlendShapeIndices (C++ function)

dnac::RemoveBlendShapeCommand::Impl::setBlendShapeIndex (C++ function)

dnac::RemoveBlendShapeCommand::Impl::setBlendShapeIndices (C++ function)

dnac::RemoveBlendShapeCommand::Impl::blendShapeIndices (C++ member)

dnac::CalculateMeshLowerLODsCommand::setMeshIndex (C++ function)

dnac::CalculateMeshLowerLODsCommand::Impl::setMeshIndex (C++ function)

dnac::CalculateMeshLowerLODsCommand::Impl::meshIndex (C++ member)

dnac::CalculateMeshLowerLODsCommand::Impl::getMeshName (C++ function)

dnac::PruneBlendShapeTargetsCommand::setThreshold (C++ function)

dnac::PruneBlendShapeTargetsCommand::Impl::setThreshold (C++ function)

dnac::PruneBlendShapeTargetsCommand::Impl::threshold (C++ member)

dnac::RemoveJointAnimationCommand::RemoveJointAnimationCommand (C++ function)

dnac::RemoveJointAnimationCommand::~RemoveJointAnimationCommand (C++ function)

dnac::RemoveJointAnimationCommand::setJointIndices (C++ function)

dnac::RemoveJointAnimationCommand::Impl::setJointIndex (C++ function)

dnac::RemoveJointAnimationCommand::Impl::setJointIndices (C++ function)

dnac::RemoveJointAnimationCommand::Impl::jointIndices (C++ member)

dnac::RenameAnimatedMapCommand::RenameAnimatedMapCommand (C++ function)

dnac::RenameAnimatedMapCommand::~RenameAnimatedMapCommand (C++ function)

dnac::RenameAnimatedMapCommand::Impl::getNameCount (C++ function)

dnac::RenameAnimatedMapCommand::Impl::getNameByIndex (C++ function)

dnac::RenameAnimatedMapCommand::Impl::setNameByIndex (C++ function)

dnac::RenameBlendShapeCommand::RenameBlendShapeCommand (C++ function)

dnac::RenameBlendShapeCommand::~RenameBlendShapeCommand (C++ function)

dnac::RenameBlendShapeCommand::Impl::getNameCount (C++ function)

dnac::RenameBlendShapeCommand::Impl::getNameByIndex (C++ function)

dnac::RenameBlendShapeCommand::Impl::setNameByIndex (C++ function)

dnac::RotateCommand::Impl::getRotationTransformationMatrix (C++ function)

dnac::RotateCommand::Impl::rotateBlendShapeTargetDeltas (C++ function)

dnac::ScaleCommand::Impl::scaleBlendShapeTargetDeltas (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::operator= (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::setMeshIndex (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::setBlendShapeTargetIndex (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::setDeltas (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::setVertexIndices (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::setOperation (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::NoVertexIndicesSetError (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::DeltasMasksCountMismatch (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::Impl (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::setMeshIndex (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::setDeltas (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::setVertexIndices (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::setMasks (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::setOperation (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::run (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::deltas (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::vertexIndices (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::masks (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::operation (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::meshIndex (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::blendShapeTargetIndex (C++ member)

dnac::SetBlendShapeTargetDeltasCommand::Impl::densify (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::sparsify (C++ function)

dnac::SetBlendShapeTargetDeltasCommand::Impl::status (C++ member)

dnac::SetNeutralJointRotationsCommand::setRotations (C++ function)

dnac::SetNeutralJointRotationsCommand::Impl::Impl (C++ function)

dnac::SetNeutralJointRotationsCommand::Impl::setRotations (C++ function)

dnac::SetNeutralJointRotationsCommand::Impl::rotations (C++ member)

dnac::SetNeutralJointTranslationsCommand::operator= (C++ function)

dnac::SetNeutralJointTranslationsCommand::setTranslations (C++ function)

dnac::SetNeutralJointTranslationsCommand::Impl::Impl (C++ function)

dnac::SetNeutralJointTranslationsCommand::Impl::setTranslations (C++ function)

dnac::SetNeutralJointTranslationsCommand::Impl::run (C++ function)

dnac::SetNeutralJointTranslationsCommand::Impl::Super (C++ type)

dnac::SetNeutralJointTranslationsCommand::Impl::translations (C++ member)

dnac::SetSkinWeightsCommand::SetSkinWeightsCommand (C++ function)

dnac::SetSkinWeightsCommand::~SetSkinWeightsCommand (C++ function)

dnac::SetSkinWeightsCommand::Impl::setVertexIndex (C++ function)

dnac::SetSkinWeightsCommand::Impl::setJointIndices (C++ function)

dnac::SetVertexPositionsCommand::SetVertexPositionsCommand (C++ function)

dnac::SetVertexPositionsCommand::~SetVertexPositionsCommand (C++ function)

dnac::SetVertexPositionsCommand::PositionsMasksCountMismatch (C++ member)

dnac::SetVertexPositionsCommand::Impl::setMeshIndex (C++ function)

dnac::SetVertexPositionsCommand::Impl::setPositions (C++ function)

dnac::SetVertexPositionsCommand::Impl::setOperation (C++ function)

dnac::SetVertexPositionsCommand::Impl::computeVertexPositions (C++ function)

dnac::TranslateCommand::Impl::translateNeutralJoints (C++ function)

dnac::TranslateCommand::Impl::translateVertexPositions (C++ function)

dnac::RenameResourceCommand::RenameResourceCommand (C++ function)

dnac::RenameResourceCommand::~RenameResourceCommand (C++ function)

dnac::RenameResourceCommand::Configuration::Unconfigured (C++ enumerator)

dnac::RenameResourceCommand::Configuration::SearchAndRename (C++ enumerator)

dnac::RenameResourceCommand::Configuration::RenameByIndex (C++ enumerator)

dnac::DNACalibDNAReaderImpl::DNACalibDNAReaderImpl (C++ function)

dnac::DNACalibDNAReaderImpl::~DNACalibDNAReaderImpl (C++ function)

dnac::DNACalibDNAReaderImpl::setNeutralJointTranslations (C++ function)

dnac::DNACalibDNAReaderImpl::setNeutralJointTranslation (C++ function)

dnac::DNACalibDNAReaderImpl::setNeutralJointRotations (C++ function)

dnac::DNACalibDNAReaderImpl::setNeutralJointRotation (C++ function)

dnac::DNACalibDNAReaderImpl::setBlendShapeTargetDeltas (C++ function)

dnac::DNACalibDNAReaderImpl::setBlendShapeTargetVertexIndices (C++ function)

dnac::DNACalibDNAReaderImpl::pruneBlendShapeTargets (C++ function)

dnac::DNACalibDNAReaderImpl::removeJointAnimations (C++ function)

dnac::ReaderImpl::getBlendShapeChannelIndexListCount (C++ function)

dnac::ReaderImpl::getBlendShapeChannelIndicesForLOD (C++ function)

dnac::ReaderImpl::getMeshBlendShapeChannelMappingCount (C++ function)

dnac::ReaderImpl::getMeshBlendShapeChannelMapping (C++ function)

dnac::ReaderImpl::getMeshBlendShapeChannelMappingIndicesForLOD (C++ function)

dnac::ReaderImpl::getJointVariableAttributeIndices (C++ function)

dnac::ReaderImpl::getBlendShapeChannelOutputIndices (C++ function)

dnac::ReaderImpl::getBlendShapeChannelInputIndices (C++ function)

dnac::ReaderImpl::getVertexTextureCoordinateCount (C++ function)

dnac::ReaderImpl::getVertexLayoutTextureCoordinateIndices (C++ function)

dnac::ReaderImpl::getBlendShapeTargetVertexIndices (C++ function)

dnac::WriterImpl::clearLODBlendShapeChannelMappings (C++ function)

dnac::WriterImpl::clearMeshBlendShapeChannelMappings (C++ function)

dnac::WriterImpl::setMeshBlendShapeChannelMapping (C++ function)

dnac::WriterImpl::setBlendShapeChannelInputIndices (C++ function)

dnac::WriterImpl::setBlendShapeChannelOutputIndices (C++ function)

dnac::WriterImpl::setBlendShapeTargetVertexIndices (C++ function)

dnac::UVBarycentricMapping::TrianglePositionIndicesPair (C++ type)

dnac::UVBarycentricMapping::BarycentricPositionIndicesPair (C++ type)

dnac::DenormalizedData::jointVariableAttributeIndices (C++ member)

dnac::DenormalizedData::meshBlendShapeMappingIndices (C++ member)

dnac::DenormalizedData::populateJointVariableAttributeIndices (C++ function)

dnac::DenormalizedData::populateMeshBlendShapeMappingIndices (C++ function)

dnac::RawBlendShapeChannels::RawBlendShapeChannels (C++ function)

dnac::RawTextureCoordinateVector::RawTextureCoordinateVector (C++ function)

dnac::RawVertexLayoutVector::RawVertexLayoutVector (C++ function)

commands that perform useful calculations or provide additional functionality:

Fix "RuntimeError: Error loading DNA: DNA signature mismatched, expected DNA, got ver?"

fix "runtimeerror: error loading dna: dna signature mismatched, expected dna, got ver?"

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

    @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")

            self._build()

            logging.info(f"{filename} built successfully!")

        except DNAViewerError as e:
            traceback.print_exc()
            raise e
        except Exception as e:
            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(),
            )
                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.

        @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.
        """
            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.

        @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(".", "_")
                    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))

    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...")
        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.

        @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")

            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:
        """
        0 -> degree
        1 -> radian

        @rtype: AngleUnit
        @returns: AngleUnit.degree or AngleUnit.radian
        """

            return AngleUnit.degree
            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"""
        for idx, meshes_per_lod in enumerate(
        ):
            if meshes_per_lod:
                meshes.append(idx)
        return list(set(meshes))

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.

    @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.

    @degree: using degree as unit
    @radian: using radian as unit
    """

    degree = 0
    radian = 1

class Config:
    """
    A class used to represent the config for @Builder

    @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

    @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"

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 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:
                f"<objName>{self.blend_shape_name_postfix}.<attrName>"
            )

        self.animated_map_naming = (
            f"{self.animated_map_attribute_multipliers_name}.<objName>_<attrName>"
        )

from typing import Dict, List

from maya import cmds

class Joint:
    """
    A builder class used for adding joints to the scene

    @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"""

            self.add_joint_to_scene(joint)

from typing import List

from ..builder.maya.mesh import MayaMesh
from .config import Config

class Mesh:
    """

    @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

    @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

    @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.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)

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

from maya.api.OpenMaya import MSpace, MVector

from ..common import ANALOG_GUI_HOLDER, GUI_HOLDER, RIG_LOGIC_PREFIX, DNAViewerError
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_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)
                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:
                    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...")
                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)
            self.config.right_eye_driver_name,
            self.eye_r_pos,
        )
            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],
            ),
        )
            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

            self.config.central_aim,
            MVector(eye_middle[0], eye_middle[1], central_aim_pos[2]),
        )
            self.config.le_aim,
            MVector(self.eye_l_pos[0], self.eye_l_pos[1], eye_l_locator_pos[2]),
        )
            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)

from typing import List, Tuple

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

    """
    A model class for holding data needed in the mesh building process

    @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:
    """

    @param mesh_index: The index of the mesh

    @param dna: Instance of DNA

    @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
    """

        self,
        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.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]:
        """

        @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:
        """

        """

        logging.info("==============================")
        logging.info(f"adding mesh: {mesh_name}")
        self.data.dna_vertex_positions = self.dna.get_vertex_positions_for_mesh_index(
        )
        self.data.dna_vertex_layout_positions = (
            self.dna.get_vertex_layout_positions_for_mesh_index(self.mesh_index)
        )

        (
        ) = 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)

        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(
        )
        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.
        """

        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.

        """

        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
        """

        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.

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

        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.

        @param mesh_name: The mesh name that is used for getting the skin cluster name.

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

from typing import List, Tuple, Union

from maya.api.OpenMaya import MFnMesh, MGlobal
from maya.api.OpenMayaAnim import MFnSkinCluster

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

        @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.

        @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.

    @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.

    @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 mesh_node: MFnMesh
    @param mesh_node: An object for working with functions concerning meshes in maya

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

from typing import Union

from maya.api.OpenMaya import (
    MDagPath,
    MFnDagNode,
    MFnTransform,
    MGlobal,
    MSpace,
    MVector,
)

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

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

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

    def get_translation(element: str, space: int = MSpace.kObject) -> MVector:
        """gets the translation of the element with the given name

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

    def set_translation(
        element: str, translation: MVector, space: int = MSpace.kObject
    ) -> None:
        """sets the translation of the element with the given name

        @param element: The element name that we want to set the translation of

        @type translation: MVector
        @param translation: The new translation value

        @param space: A string value representing the translation space (default is "object")
        """
        element_obj = Maya.get_transform(element)
        element_obj.setTranslation(translation, space)

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

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

        """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(),
            ),
        )

class ConditionalTable:
    """
    A model class for holding various values

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

class PSDMatrix:
    """
    A model class for holding data about Pose Space Deformation

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

class JointGroup:
    """
    A model class for holding data about joint groups

    @type lods: List[int]
    @param lods: A list of lod indices that the joint group is contained within

    @param values: A list of values

    @type joints: List[int]
    @param joints: A list of joint indices

    """

    lods: List[int] = field(default_factory=list)
    joints: List[int] = field(default_factory=list)

class BlendShapesData:
    """
    A model class for holding data about blend shapes

    @param lods: A list of lod indices that the blend shapes are contained within

    """

class AnimatedMapsConditionalTable:
    """
    A model class for holding data about animated maps

    @type conditional_table: ConditionalTable
    @param conditional_table: Data needed for animated maps
    """

    conditional_table: ConditionalTable = field(default_factory=ConditionalTable)

class JointGroups:
    """
    A model class for storing data about joints

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

from typing import Dict, List, Optional, Tuple, cast

from dna import MeshBlendShapeChannelMapping

from ..model import Point3
from .descriptor import Descriptor

class Definition(Descriptor):
    """
    A class used for reading and accessing the definition part of the DNA file

    @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
    """

        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

        return super().is_read() and self.definition_read

        """
        Starts reading in the definition part of the DNA

        @rtype: DefinitionModel
        @returns: the instance of the created definition model
        """

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

    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:
            MeshBlendShapeChannelMapping,
            self.reader.getMeshBlendShapeChannelMapping(index),
        )

    def get_mesh_blend_shape_channel_mapping_for_lod(self, lod: int) -> List[int]:
            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))

    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)
            )
            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))
            self.animated_maps.lod_indices.append(
                self.get_animated_map_indices_for_lod(index)
            )

    def add_blend_shape_channels(self) -> None:

        for index in range(self.get_blend_shape_channel_count()):
            self.blend_shape_channels.names.append(
                self.get_blend_shape_channel_name(index)
            )
            self.blend_shape_channels.lod_indices.append(
                self.get_blend_shape_channel_indices_for_lod(index)
            )

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

class GeometryEntity:
    """
    A model class for holding names and indices

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

class Joints(GeometryEntity):
    """
    A model class for holding data about the joints

    @type parent_index: List[int]
    @param parent_index: List of parent indices for each joint index
    """

    parent_index: List[int] = field(default_factory=list)

from typing import Dict, List, Optional, Tuple

from ..dnalib.layer import Layer

class Descriptor:
    """
    A class used for reading and accessing the descriptor part of the DNA file

    @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
    """

        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

        return self.descriptor_read

    def layer_enabled(self, layer: Layer) -> bool:
        return layer in self.layers or Layer.all in self.layers

        """
        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()

from typing import List, Optional, Tuple

from dna import DataLayer_All, FileStream, Status

from ..model import UV, BlendShape, Joint, Layout, Point3
from .behavior import Behavior
from .geometry import Geometry

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

        return Behavior.is_read(self) and Geometry.is_read(self)

        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)

    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(
    ) -> 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(
    ) -> 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:
                "Number of skin weight values and vertex count don't match!"
            )
        if len(joint_indices) != len(skin_weight_values):
                "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:
                    "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):
                    "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 = []
            polygon_faces.append(len(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)
            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 = []
            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]] = []

            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)

from ..model import UV, BlendShape, Layout, Mesh, Point3, SkinWeightsData, Topology

class Geometry(Definition):
        self.geometry_meshes: List[Mesh] = []
        self.geometry_read = False

        return super().is_read() and self.geometry_read

        """
        Starts reading in the mesh from the geometry part of the DNA
        """

        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]:
            List[float],
            self.reader.getSkinWeightsValues(
                meshIndex=mesh_index, vertexIndex=vertex_index
            ),
        )

    def get_skin_weights_joint_indices(
    ) -> List[int]:
            List[int],
            self.reader.getSkinWeightsJointIndices(
            ),
        )

    def get_vertex_texture_coordinate_count(self, mesh_index: int) -> int:
            int, self.reader.getVertexTextureCoordinateCount(meshIndex=mesh_index)
        )

    def get_vertex_texture_coordinate(
        self, mesh_index: int, texture_coordinate_index: int
    ) -> Tuple[float, float]:
            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
            self.reader.getFaceVertexLayoutIndices(
                meshIndex=mesh_index, faceIndex=face_index
            ),
        )

    def get_vertex_layout(
        self, mesh_index: int, layout_index: int
    ) -> Tuple[int, int, int]:
            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(
    ) -> Tuple[float, float, float]:
            Tuple[float, float, float],
            self.reader.getVertexPosition(
            ),
        )

    def get_blend_shape_target_vertex_indices(
            self.reader.getBlendShapeTargetVertexIndices(
                meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index
            ),
        )

    def get_blend_shape_target_delta_count(
    ) -> int:
            int,
            self.reader.getBlendShapeTargetDeltaCount(
            ),
        )

    def get_blend_shape_target_delta(
        self, mesh_index: int, blend_shape_target_index: int, delta_index: 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(
    ) -> int:
            int,
            self.reader.getBlendShapeChannelIndex(
            ),
        )

    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 = []
            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(
    ) -> 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(
        )
        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)

    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(
                    ),
                )
            )
        return blend_shapes

class Layer(Enum):
    descriptor = 1
    definition = 2
    behavior = 3
    geometry = 4
    all = 5

import os
import webbrowser
from typing import Callable, List

from maya.cmds import confirmDialog
from PySide2.QtCore import QCoreApplication, Qt
from PySide2.QtWidgets import (
    QApplication,
    QCheckBox,
    QGridLayout,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QTabWidget,
    QTreeWidget,
    QTreeWidgetItem,
    QTreeWidgetItemIterator,
    QVBoxLayout,
    QWidget,
)

from .. import DNA, build_rig
from ..builder.config import RigConfig
from ..version import __version__
from .widgets import FileChooser, QHLine

def show() -> None:
    DnaViewerWindow.show_window()

WINDOW_OBJECT = "dnaviewer"
WINDOW_TITLE = "DNA Viewer"
HELP_URL = "https://epicgames.github.io/MetaHuman-DNA-Calibration/"
SPACING = 6
WINDOW_SIZE_WIDTH_MIN = 800
WINDOW_SIZE_WIDTH_MAX = 1200
WINDOW_SIZE_HEIGHT_MIN = 800
WINDOW_SIZE_HEIGHT_MAX = 1000
MARGIN_LEFT = 8
MARGIN_TOP = 8
MARGIN_RIGHT = 8
MARGIN_BOTTOM = 8
MARGIN_HEADER_LEFT = 0
MARGIN_HEADER_TOP = 0
MARGIN_HEADER_RIGHT = 0
MARGIN_HEADER_BOTTOM = 0
MARGIN_BODY_LEFT = 0
MARGIN_BODY_TOP = 0
MARGIN_BODY_RIGHT = 0

class MeshTreeList(QWidget):
    """
    A custom widget that lists out meshes with checkboxes next to them, so these meshes can be selected to be processed. The meshes are grouped by LOD

    @type mesh_tree: QWidget
    @param mesh_tree: The widget that contains the meshes to be selected in a tree list
    """

    def __init__(self, main_window: "DnaViewerWindow") -> None:
        super().__init__()
        self.main_window = main_window

        label = QLabel("Meshes:")
        self.mesh_tree = self.create_mesh_tree()

        layout = QGridLayout()
        layout.addWidget(self.mesh_tree, 0, 0, 4, 1)
        layout.setContentsMargins(
            MARGIN_BODY_LEFT,
            MARGIN_BODY_TOP,
            MARGIN_BODY_RIGHT,
            MARGIN_BOTTOM,
        )

        layout_holder = QVBoxLayout()
        layout_holder.addWidget(label)
        layout_holder.addLayout(layout)
        layout_holder.setContentsMargins(
        )

        self.btn_select_all = QPushButton("Select all meshes")
        self.btn_select_all.setEnabled(False)
        self.btn_select_all.clicked.connect(self.select_all)
        layout_holder.addWidget(self.btn_select_all)

        self.btn_deselect_all = QPushButton("Deselect all meshes")
        self.btn_deselect_all.setEnabled(False)
        self.btn_deselect_all.clicked.connect(self.deselect_all)
        layout_holder.addWidget(self.btn_deselect_all)

        self.setLayout(layout_holder)

    def create_mesh_tree(self) -> QWidget:
        """
        Creates the mesh tree list widget

        @rtype: QWidget
        @returns: The created widget
        """

        mesh_tree = QTreeWidget()
        mesh_tree.setHeaderHidden(True)
        mesh_tree.itemChanged.connect(self.tree_item_changed)
        mesh_tree.setStyleSheet("background-color: #505050")
        mesh_tree.setToolTip("Select mesh or meshes to add to rig")
        return mesh_tree

    def fill_mesh_list(
        self, lod_count: int, names: List[str], indices_names: List[List[int]]
    ) -> None:
        """
        Fills the mesh list with the meshes, and groups them by lods

        @type lod_count: int
        @param lod_count: The LOD count

        @type names: List[str]
        @param names: The names and indices of all the meshes

        @type indices_names: List[List[int]
        @param indices_names: The names and indices of all the meshes
        """

        self.mesh_tree.clear()

        for i in range(lod_count):
            parent = QTreeWidgetItem(self.mesh_tree)
            parent.setText(0, f"LOD {i}")
            parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)

            meshes_in_lod = indices_names[i]

            for mesh_index in meshes_in_lod:
                child = QTreeWidgetItem(parent)
                child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
                child.setText(0, f"{names[mesh_index]}")
                child.setCheckState(0, Qt.Unchecked)

            self.mesh_tree.setItemExpanded(parent, True)

    def get_selected_meshes(self) -> List[int]:
        """
        Gets the selected meshes from the tree widget

        @rtype: List[int]
        @returns: The list of mesh indices that are selected
        """

        meshes = []

        iterator = QTreeWidgetItemIterator(
            self.mesh_tree, QTreeWidgetItemIterator.Checked
        )
        while iterator.value():
            item = iterator.value()
            mesh_name = item.text(0)
            mesh_index = self.main_window.dna.get_mesh_id_from_mesh_name(mesh_name)
            if mesh_index is not None:
                meshes.append(mesh_index)

            iterator += 1

    def select_all(self) -> None:
        """
        Selects all meshes in the tree widget
        """
        self.iterate_over_items(Qt.Checked)

    def deselect_all(self) -> None:
        """
        Deselects all meshes in the tree widget
        """

        self.iterate_over_items(Qt.Unchecked)

    def iterate_over_items(self, state: Qt.CheckState) -> None:
        """
        """

        item = self.mesh_tree.invisibleRootItem()
        for index in range(item.childCount()):
            child = item.child(index)
            child.setCheckState(0, state)

    def tree_item_changed(self) -> None:
        """The method that gets called when a tree item gets its value changed"""

        meshes = self.get_selected_meshes()

        if meshes:
            self.main_window.skin_cb.setEnabled(self.main_window.joints_cb.checkState())
            self.main_window.blend_shapes_cb.setEnabled(True)
            self.main_window.process_btn.setEnabled(True)
            self.main_window.rig_logic_cb.setEnabled(False)

            if len(meshes) == self.main_window.dna.get_mesh_count():
                self.main_window.rig_logic_cb.setEnabled(
                    self.main_window.joints_cb.checkState()
                    and self.main_window.blend_shapes_cb.checkState()
                    and self.main_window.skin_cb.checkState()
                    and self.main_window.select_gui_path.get_file_path() is not None
                    and self.main_window.select_analog_gui_path.get_file_path()
                    is not None
                    and self.main_window.select_aas_path.get_file_path() is not None
                )
        else:
            self.main_window.skin_cb.setEnabled(False)
            self.main_window.blend_shapes_cb.setEnabled(False)
            self.main_window.process_btn.setEnabled(
                self.main_window.joints_cb.checkState()
            )

class DnaViewerWindow(QMainWindow):
    """
    UI Window

    @type select_dna_path: FileChooser
    @param select_dna_path: The FileChooser widget for getting the DNA path

    @type load_dna_btn: QPushButton
    @param load_dna_btn: The button that starts loading in the DNA

    @type mesh_tree_list: QWidget
    @param mesh_tree_list: The widget that contains the meshes to be selected in a tree list

    @type joints_cb: QCheckBox
    @param joints_cb: The checkbox that represents if joints should be added

    @type blend_shapes_cb: QCheckBox
    @param blend_shapes_cb: The checkbox that represents if blend shapes should be added

    @type skin_cb: QCheckBox
    @param skin_cb: The checkbox that represents if skin should be added

    @type rig_logic_cb: QCheckBox
    @param rig_logic_cb: The checkbox that represents if rig logic should be added

    @type ctrl_attributes_on_root_joint_cb: QCheckBox
    @param ctrl_attributes_on_root_joint_cb: The checkbox that represents if control attributes on joint should be added

    @type animated_map_attributes_on_root_joint_cb: QCheckBox
    @param animated_map_attributes_on_root_joint_cb: The checkbox that represents if animated maps attributes on root joint should be added

    @type mesh_name_to_blend_shape_channel_name_cb: QCheckBox
    @param mesh_name_to_blend_shape_channel_name_cb: The checkbox that represents if mesh names to blend shapes channel name should be added

    @type key_frames_cb: QCheckBox
    @param key_frames_cb: The checkbox that represents if key frames should be added

    @type select_gui_path: FileChooser
    @param select_gui_path: The FileChooser widget for getting the gui path

    @type select_analog_gui_path: FileChooser
    @param select_analog_gui_path: The FileChooser widget for getting the analog gui path

    @type select_aas_path: FileChooser
    @param select_aas_path: The FileChooser widget for getting the additional assemble script path

    @type process_btn: QPushButton
    @param process_btn: The button that starts creating the scene and character

    @type progress_bar: QProgressBar
    @param progress_bar: The progress bar that shows the building progress
    """

    _instance: "DnaViewerWindow" = None
    main_widget: QWidget = None
    select_dna_path: FileChooser = None
    load_dna_btn: QPushButton = None
    mesh_tree_list: QWidget = None
    joints_cb: QCheckBox = None
    blend_shapes_cb: QCheckBox = None
    skin_cb: QCheckBox = None
    rig_logic_cb: QCheckBox = None
    ctrl_attributes_on_root_joint_cb: QCheckBox = None
    animated_map_attributes_on_root_joint_cb: QCheckBox = None
    mesh_name_to_blend_shape_channel_name_cb: QCheckBox = None
    key_frames_cb: QCheckBox = None
    select_gui_path: FileChooser = None
    select_analog_gui_path: FileChooser = None
    select_aas_path: FileChooser = None
    process_btn: QPushButton = None
    progress_bar: QProgressBar = None
    dna: DNA = None

    def __init__(self, parent: QWidget = None) -> None:
        super().__init__(parent)
        self.body: QVBoxLayout = None
        self.header: QHBoxLayout = None
        self.build_options: QWidget = None
        self.extra_build_options: QWidget = None

        self.setup_window()
        self.create_ui()

    def setup_window(self) -> None:
        """A method for setting up the window"""

        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowTitleHint
            | Qt.WindowMaximizeButtonHint
            | Qt.WindowMinimizeButtonHint
            | Qt.WindowCloseButtonHint
        )
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setObjectName(WINDOW_OBJECT)
        self.setWindowTitle(WINDOW_TITLE)
        self.setWindowFlags(Qt.Window)
        self.setFocusPolicy(Qt.StrongFocus)

    def create_ui(self) -> None:
        """Fills the window with UI elements"""

        self.main_widget = self.create_main_widget()
        self.setCentralWidget(self.main_widget)
        self.set_size()
        self.setStyleSheet(self.load_css())

    def load_css(self) -> str:
        css = os.path.join(os.path.dirname(__file__), "app.css")
        with open(css, encoding="utf-8") as file:
            return file.read()

    def create_main_widget(self) -> QWidget:
        """
        Creates the widget containing the UI elements

        @rtype: QtWidgets.QWidget
        @returns: the main widget
        """

        header = self.create_header()
        body = self.create_body()

        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.addLayout(header)
        layout.addWidget(QHLine())
        layout.addLayout(body)
        layout.setContentsMargins(MARGIN_LEFT, MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM)
        layout.setSpacing(SPACING)
        return widget

    def set_size(self) -> None:
        """Sets the window size"""

        self.setMaximumSize(WINDOW_SIZE_WIDTH_MAX, WINDOW_SIZE_HEIGHT_MAX)
        self.setMinimumSize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN)
        self.resize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN)

    def show_message_dialog(self) -> bool:
        dlg = QMessageBox()
        dlg.setIcon(QMessageBox.Warning)
        dlg.setWindowTitle("Warning")
        dlg.setText(
            "Unsaved changes exists.\nSave changes and create new scene, discard changes, and create new scene or cancel procesing."
        )
        dlg.setStandardButtons(
            QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
        )
        button = dlg.exec_()

        if button == QMessageBox.Save:
            cmds.SaveScene()
            return not cmds.file(q=True, modified=True)
        if button == QMessageBox.Cancel:

        """Start the build process of creation of scene from provided configuration from the UI"""

        process = True
        if cmds.file(q=True, modified=True):
            process = self.show_message_dialog()

        if process:
            self.set_progress(text="Processing in progress...", value=0)
            config = RigConfig(
                meshes=self.mesh_tree_list.get_selected_meshes(),
                gui_path=self.select_gui_path.get_file_path(),
                analog_gui_path=self.select_analog_gui_path.get_file_path(),
                aas_path=self.select_aas_path.get_file_path(),
                add_rig_logic=self.add_rig_logic(),
                add_joints=self.add_joints(),
                add_blend_shapes=self.add_blend_shapes(),
                add_skin_cluster=self.add_skin_cluster(),
                add_ctrl_attributes_on_root_joint=self.add_ctrl_attributes_on_root_joint(),
                add_animated_map_attributes_on_root_joint=self.add_animated_map_attributes_on_root_joint(),
                add_mesh_name_to_blend_shape_channel_name=self.add_mesh_name_to_blend_shape_channel_name(),
                add_key_frames=self.add_key_frames(),
            )

            self.main_widget.setEnabled(False)

            try:
                self.set_progress(value=33)
                self.dna = DNA(self.select_dna_path.get_file_path())
                self.set_progress(value=66)
                build_rig(dna=self.dna, config=config)
                self.set_progress(text="Processing completed", value=100)
                self.set_progress(text="Processing failed", value=100)
                logging.error(e)
                confirmDialog(message=e, button=["ok"], icon="critical")

            self.main_widget.setEnabled(True)

    def set_progress(self, text: str = None, value: int = None) -> None:
        """Setting text and/or value to progress bar"""

        if text is not None:
            self.progress_bar.setFormat(text)
        if value is not None:
            self.progress_bar.setValue(value)

    def show_window() -> None:
        if DnaViewerWindow._instance is None:
            DnaViewerWindow._instance = DnaViewerWindow(
                parent=DnaViewerWindow.maya_main_window()
            )
        DnaViewerWindow.activate_window()

    def maya_main_window() -> QWidget:
        """
        Gets the MayaWindow instance

        @throws RuntimeError

        @returns: main window instance
        """

        for obj in QApplication.topLevelWidgets():
            if obj.objectName() == "MayaWindow":
                return obj
        raise RuntimeError("Could not find MayaWindow instance")

    def activate_window() -> None:
        """Shows window if minimized"""

        try:
            DnaViewerWindow._instance.show()

            if DnaViewerWindow._instance.windowState() & Qt.WindowMinimized:
                DnaViewerWindow._instance.setWindowState(Qt.WindowActive)

            DnaViewerWindow._instance.raise_()
            DnaViewerWindow._instance.activateWindow()
        except RuntimeError as e:
            logging.info(e)
            if str(e).rstrip().endswith("already deleted."):
                DnaViewerWindow._instance = None
                DnaViewerWindow.show_window()

    def add_joints(self) -> bool:
        return self.is_checked(self.joints_cb)

    def add_blend_shapes(self) -> bool:
        return self.is_checked(self.blend_shapes_cb)

    def add_skin_cluster(self) -> bool:
        return self.is_checked(self.skin_cb)

    def add_rig_logic(self) -> bool:
        return self.is_checked(self.rig_logic_cb)

    def add_ctrl_attributes_on_root_joint(self) -> bool:
        return self.is_checked(self.ctrl_attributes_on_root_joint_cb)

    def add_animated_map_attributes_on_root_joint(self) -> bool:
        return self.is_checked(self.animated_map_attributes_on_root_joint_cb)

    def add_mesh_name_to_blend_shape_channel_name(self) -> bool:
        return self.is_checked(self.mesh_name_to_blend_shape_channel_name_cb)

    def add_key_frames(self) -> bool:
        return self.is_checked(self.key_frames_cb)

    def is_checked(self, checkbox: QCheckBox) -> bool:
        """
        Returns if the provided checkbox is checked and enabled

        @type checkbox: QCheckBox
        @param checkbox: The checkbox thats value needs to be checked and enabled

        @rtype: bool
        @returns: The flag representing if the checkbox is checked and enabled
        """

        return (
            checkbox is not None
            and bool(checkbox.isEnabled())
            and checkbox.checkState() == Qt.CheckState.Checked
        )

    def create_body(self) -> QVBoxLayout:
        """
        Creates the main body layout and adds needed widgets

        @rtype: QVBoxLayout
        @returns: The created vertical box layout with the widgets added
        """

        self.body = QVBoxLayout()
        self.body.setContentsMargins(
        )
        self.body.setSpacing(SPACING)
        self.create_dna_selector()
        self.mesh_tree_list = self.create_mesh_selector()
        self.build_options = self.create_build_options()
        self.extra_build_options = self.create_extra_build_options()

        tab = QTabWidget(self)
        tab.addTab(self.build_options, "Build options")
        tab.addTab(self.extra_build_options, "Extra options")
        layout = QHBoxLayout(widget)
        layout.addWidget(tab)

        self.body.addWidget(widget)

        self.select_gui_path = self.create_gui_selector()
        self.select_analog_gui_path = self.create_analog_gui_selector()
        self.select_aas_path = self.create_aas_selector()
        self.process_btn = self.create_process_btn()
        self.progress_bar = self.create_progress_bar()

        return self.body

    def create_header(self) -> QHBoxLayout:
        """
        Creates and adds to the header widget

        @rtype: QHBoxLayout
        @returns: The created horizontal box layout with the widgets added
        """

        self.header = QHBoxLayout()
        label = QLabel("v" + __version__)
        btn = self.create_help_btn()
        self.header.addWidget(label)
        self.header.addStretch(1)
        self.header.addWidget(btn)
        self.header.setContentsMargins(
            MARGIN_HEADER_LEFT,
            MARGIN_HEADER_TOP,
            MARGIN_HEADER_RIGHT,
            MARGIN_HEADER_BOTTOM,
        )
        self.header.setSpacing(SPACING)
        return self.header

    def create_help_btn(self) -> QWidget:
        """
        Creates the help button widget

        """

        btn = QPushButton(self)
        btn.setText(" ? ")
        btn.setToolTip("Help")
        btn.clicked.connect(self.on_help)
        return btn

    def on_help(self) -> None:
        """The method that gets called when the help button is clicked"""

        if HELP_URL:
            webbrowser.open(HELP_URL)
        else:
            QMessageBox.about(
                self,
                "About",
                "Sorry, this application does not have documentation yet.",
            )

    def create_dna_selector(self) -> QWidget:
        """
        Creates and adds the DNA selector widget

        @returns: The created DNA selector widget
        """

        self.select_dna_path = self.create_dna_chooser()
        self.load_dna_btn = self.create_load_dna_button(self.select_dna_path)

        self.select_dna_path.fc_text_field.textChanged.connect(
            lambda: self.on_dna_selected(self.select_dna_path)
        )

        layout = QVBoxLayout()
        layout.addWidget(self.select_dna_path)
        layout.addWidget(self.load_dna_btn)
        )
        widget.setLayout(layout)

    def on_dna_selected(self, input: FileChooser) -> None:
        """
        The method that gets called when a DNA file gets selected

        @type input: FileChooser
        @param input: The file chooser object corresponding to the DNA selector widget
        """

        enabled = input.get_file_path() is not None
        self.load_dna_btn.setEnabled(enabled)
        self.process_btn.setEnabled(False)

    def create_dna_chooser(self) -> FileChooser:
        """
        Creates and adds the DNA chooser widget

        @rtype: FileChooser
        @returns: Dna chooser widget
        """

        return self.create_file_chooser(
            "Path:",
            "DNA file to load. Required by all gui elements",
            "Select a DNA file",
            "DNA files (*.dna)",
            self.on_dna_changed,
        )

    def on_dna_changed(self, state: int) -> None:  # pylint: disable=unused-argument
        """
        Method that gets called when the checkbox is changed

        @type state: int
        @param state: The changed state of the checkbox
        """
        enabled = False
        if self.dna:
            if self.dna.path == self.select_dna_path.get_file_path():
                enabled = True

        self.mesh_tree_list.btn_select_all.setEnabled(enabled)
        self.mesh_tree_list.btn_deselect_all.setEnabled(enabled)
        self.process_btn.setEnabled(enabled)

    def create_load_dna_button(self, dna_input: FileChooser) -> QWidget:
        """
        Creates and adds the load DNA button widget

        @returns: The created load DNA button widget
        """

        btn = QPushButton("Load DNA")
        btn.setEnabled(False)
        btn.clicked.connect(lambda: self.on_load_dna_clicked(dna_input))
        return btn

    def on_load_dna_clicked(self, input: FileChooser) -> None:
        """

        """

        self.main_widget.setEnabled(False)
        QCoreApplication.processEvents()
        try:
            dna_file_path = input.get_file_path()

            if dna_file_path:
                self.dna = DNA(dna_file_path, [Layer.definition])
                lod_count = self.dna.get_lod_count()
                names = self.get_mesh_names()
                indices_names = self.get_lod_indices_names()
                self.mesh_tree_list.fill_mesh_list(lod_count, names, indices_names)
                self.joints_cb.setEnabled(True)
                self.enable_additional_build_options(True)
                self.process_btn.setEnabled(False)
                self.mesh_tree_list.btn_select_all.setEnabled(True)
                self.mesh_tree_list.btn_deselect_all.setEnabled(True)
            dlg = QMessageBox()
            dlg.setIcon(QMessageBox.Warning)
            dlg.setWindowTitle("Error")
            dlg.setText(str(e))
            dlg.setStandardButtons(QMessageBox.Ok)
            dlg.exec_()

        self.main_widget.setEnabled(True)

    def get_mesh_names(self) -> List[str]:
        names: List[str] = []
            names.append(self.dna.get_mesh_name(index))

    def get_lod_indices_names(self) -> List[List[int]]:
        lod_indices: List[List[int]] = []
        for index in range(self.dna.get_lod_count()):
            lod_indices.append(self.dna.get_mesh_indices_for_lod(index))
        return lod_indices

    def create_mesh_selector(self) -> MeshTreeList:
        """
        Creates and adds a mesh tree list where the entries are grouped by lods, this is used for selecting the meses that need to be processed

        @rtype: MeshTreeList
        @returns: The created mesh tree list widget
        """

        widget = MeshTreeList(self)

    def create_file_chooser(
        self,
        label: str,
        hint: str,
        caption: str,
        filter: str,
        on_changed: Callable[[int], None] = None,
    ) -> FileChooser:
        """
        Creates a file chooser widget that is used for selecting file paths

        @type label: str
        @param label: The label in the FileDialog that pops up

        @type hint: str
        @param hint: The label in the FileDialog that pops up

        @type caption: str
        @param caption: The caption in the FileDialog that pops up

        @type filter: str
        @param filter: The file filter that is used in the FileDialog

        @returns: The created file chooser object
        """

        widget = FileChooser(
            label,
            hint,
            self,
            dialog_caption=caption,
            dialog_filter=filter,
            on_changed=on_changed or self.on_generic_changed,
        )

    def create_gui_selector(self) -> FileChooser:
        """
        Creates the gui selector widget

        @returns: Gui selector widget
        """

            "Gui path:",
            "GUI file to load. Required by RigLogic",
            "Select the gui file",
            "gui files (*.ma)",
        )

    def create_aas_selector(self) -> FileChooser:
        """
        Creates and adds the additional assemble script selector widget

        @returns: Additional assemble script selector widget
        """

            "Additional assemble script path:",
            "Additional assemble script to use. Required by RigLogic",
            "Select the aas file",
            "python script (*.py)",
        )

    def create_analog_gui_selector(self) -> FileChooser:
        """
        Creates and adds the analog gui selector widget

        @returns: Analog gui selector widget
        """

            "Analog gui path:",
            "Analog GUI file to load. Required by RigLogic",
            "Select the analog gui file",
            "analog gui files (*.ma)",
        )

    def create_build_options(self) -> QWidget:
        """Creates and adds the widget containing the build options checkboxes"""
        )

        self.joints_cb = self.create_checkbox(
            "joints",
            "Add joints to rig. Requires: DNA to be loaded",
            layout,
            self.on_joints_changed,
        )
        self.blend_shapes_cb = self.create_checkbox(
            "blend shapes",
            "Add blend shapes to rig. Requires: DNA to be loaded and at least one mesh to be check",
            layout,
            self.on_generic_changed,
        )
        self.skin_cb = self.create_checkbox(
            "skin cluster",
            "Add skin cluster to rig. Requires: DNA to be loaded and at least one mesh and joints to be checked",
            layout,
        )
        self.rig_logic_cb = self.create_checkbox(
            "rig logic",
            "Add RigLogic to rig. Requires: DNA to be loaded, all meshes to be checked, joints, skin, blend shapes to be checked, also gui, analog gui and additional assemble script must be set",
            layout,
        )
        layout.addStretch()

    def create_extra_build_options(self) -> QWidget:
        """Creates and adds the widget containing the extra build options checkboxes"""
        )

        self.ctrl_attributes_on_root_joint_cb = self.create_checkbox(
            "ctrl attributes on root joint",
            layout,
            enabled=True,
            checked=True,
        )
        self.animated_map_attributes_on_root_joint_cb = self.create_checkbox(
            "animated map attributes on root joint",
            layout,
        )
        self.mesh_name_to_blend_shape_channel_name_cb = self.create_checkbox(
            "mesh name to blend shape channel name",
            layout,
        )
        self.key_frames_cb = self.create_checkbox(
            "key frames",
            "Add keyframes to rig",
            layout,
        )

    def enable_additional_build_options(self, enable: bool) -> None:
        self.ctrl_attributes_on_root_joint_cb.setEnabled(enable)
        self.animated_map_attributes_on_root_joint_cb.setEnabled(enable)
        self.mesh_name_to_blend_shape_channel_name_cb.setEnabled(enable)
        self.key_frames_cb.setEnabled(enable)

    def create_checkbox(
        self,
        hint: str,
        layout: QHBoxLayout,
        checked: bool = False,
        enabled: bool = False,
    ) -> QCheckBox:
        """
        Adds a checkbox with given parameters and connects them to the on_changed method

        @param label: The label of the checkbox

        @param hint: The hint of the checkbox

        @type on_changed: Callable[[int], None]
        @param on_changed: The method that will get called when the checkbox changes states

        @type checked: bool
        @param checked: The value representing if the checkbox is checked after creation

        @type enabled: bool
        @param enabled: The value representing if the checkbox is enabled after creation

        @rtype: QCheckBox
        @returns: the created checkbox object
        """

        checkbox = QCheckBox(label, self)
        checkbox.setChecked(checked)
        checkbox.setEnabled(enabled)
        checkbox.setToolTip(hint)
        if on_changed:
            checkbox.stateChanged.connect(on_changed)
        layout.addWidget(checkbox)
        return checkbox

    def on_joints_changed(self, state: int) -> None:
        """
        Method that gets called when the joints checkbox is changed

        """

        if self.joints_cb.isChecked():
            self.process_btn.setEnabled(True)
            if self.mesh_tree_list.get_selected_meshes():
                self.skin_cb.setEnabled(True)
        else:
            self.skin_cb.setEnabled(False)
            if not self.mesh_tree_list.get_selected_meshes():
        self.on_generic_changed(state)

    def create_process_btn(self) -> QPushButton:
        """
        Creates and adds a process button

        @type window: QMainWindow
        @param window: The instance of the window object

        @rtype: QPushButton
        @returns: The created process button
        """

        btn = QPushButton("Process")
        btn.clicked.connect(self.process)

        self.body.addWidget(btn)
        return btn

    def create_progress_bar(self) -> QProgressBar:
        """
        Creates and adds progress bar

        @rtype: QProgressBar
        @returns: The created progress bar
        """

        progress = QProgressBar(self)
        progress.setRange(0, 100)
        progress.setValue(0)
        progress.setTextVisible(True)
        progress.setFormat("")
        self.body.addWidget(progress)
        return progress

    def on_generic_changed(self, state: int) -> None:  # pylint: disable=unused-argument
        """

        """

        self.set_riglogic_cb_enabled()

    def is_enabled_and_checked(self, check_box: QCheckBox) -> bool:
        """
        Method that checks if check box is enabled in same time

        @type check_box: QCheckBox
        @param check_box: The checkbox instance to check
        """

        return (
            check_box is not None
            and bool(check_box.isEnabled())
            and bool(check_box.isChecked())
        )

    def set_riglogic_cb_enabled(self) -> None:
        """Method that sets enable state of riglogic check box"""

        all_total_meshes = False

        if self.dna and self.is_enabled_and_checked(self.blend_shapes_cb):
            if (
                len(self.mesh_tree_list.get_selected_meshes())
                == self.dna.get_mesh_count()
            ):
                all_total_meshes = True

        enabled = (
            self.is_enabled_and_checked(self.joints_cb)
            and self.is_enabled_and_checked(self.blend_shapes_cb)
            and all_total_meshes
            and self.is_enabled_and_checked(self.skin_cb)
            and self.select_gui_path.get_file_path() is not None
            and self.select_analog_gui_path.get_file_path() is not None
            and self.select_aas_path.get_file_path() is not None
        )
        self.rig_logic_cb.setEnabled(enabled)

from typing import Callable, Optional

from PySide2.QtCore import Qt
    QFileDialog,
    QFrame,
    QLabel,
    QLineEdit,
    QWidget,
)

class QLine(QFrame):
    """A widget for creating a horizontal line"""

    def __init__(self, line: QFrame.Shape) -> None:
        self.setFrameShape(line)
        self.setFrameShadow(QFrame.Sunken)

class QHLine(QLine):

    def __init__(self) -> None:
        super().__init__(QFrame.HLine)

class FileChooser(QWidget):
    """
    A custom widget used for selecting a file path using a FileDialog and an input field
    """

        self,
        label_text: str,
        hint: str,
        parent: Optional[QWidget] = None,
        placeholder: str = "",
        dialog_caption: str = "Select a file",
        dialog_filter: str = "All files (*.*)",
        button_text: str = "...",
        dir_selector: bool = False,
    ) -> None:
        super().__init__(parent=parent)

        self._dialog_caption = dialog_caption
        self._dialog_filter = dialog_filter
        self._dir_selector = dir_selector

        layout = QHBoxLayout()
        layout.setMargin(0)

        fc_label = QLabel(label_text)
        fc_label.setMinimumHeight(32)
        fc_label.setToolTip(hint)

        self.fc_text_field = QLineEdit()
        self.fc_text_field.setAlignment(Qt.AlignLeft)
        self.fc_text_field.setPlaceholderText(placeholder)
        self.fc_text_field.textChanged.connect(on_changed)
        self.fc_text_field.setToolTip(hint)

        fc_btn = QPushButton(button_text)
        fc_btn.setToolTip(hint)

        layout.addWidget(fc_label)
        layout.addWidget(self.fc_text_field)
        layout.addWidget(fc_btn)

        fc_btn.clicked.connect(
            self.open_dialog,
        )

        self.setLayout(layout)

    def get_file_path(self) -> str:
        """
        Gets the file path from the text field

        @returns: The file path contained in the text field
        """

        path = str(self.fc_text_field.text())
        if path and Path(path.strip()).exists():
            return path

    def open_dialog(self) -> None:
        """Opens a file dialog, when a path is chosen, the text field gets filled with its value"""

        if self._dir_selector:
            file_name, _ = QFileDialog.getExistingDirectory(
                self,
                self._dialog_caption,
                "",
                QFileDialog.Option.ShowDirsOnly,
            )
            if file_name:
                self.fc_text_field.setText(file_name)
        else:
            file_name, _ = QFileDialog.getOpenFileName(
                self, self._dialog_caption, "", self._dialog_filter
            )