Commands that perform useful calculations or provide additional functionality:

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)

add_eye_locators() (dna_viewer.builder.analog_gui.AnalogGui method)

create() (dna_viewer.builder.character.CharacterBuildResult static method)

nodes (dna_viewer.builder.character.CharacterBuildResult attribute)

add_blend_shapes() (dna_viewer.builder.maya_mesh.MayaMesh method)

create_neutral_mesh() (dna_viewer.builder.maya_mesh.MayaMesh method)

character_results (dna_viewer.builder.scene.SceneResult attribute)

method (dna_viewer.config.aas.AdditionalAssemblyScript attribute)

module_name (dna_viewer.config.aas.AdditionalAssemblyScript attribute)

parameter (dna_viewer.config.aas.AdditionalAssemblyScript attribute)

central_driver_name (dna_viewer.config.analog_gui.AnalogGui attribute)

facial_root_joint_name (dna_viewer.config.analog_gui.AnalogGui attribute)

left_eye_aim_up_name (dna_viewer.config.analog_gui.AnalogGui attribute)

left_eye_driver_name (dna_viewer.config.analog_gui.AnalogGui attribute)

left_eye_joint_name (dna_viewer.config.analog_gui.AnalogGui attribute)

right_eye_aim_up_name (dna_viewer.config.analog_gui.AnalogGui attribute)

right_eye_driver_name (dna_viewer.config.analog_gui.AnalogGui attribute)

right_eye_joint_name (dna_viewer.config.analog_gui.AnalogGui attribute)

with_central_aim() (dna_viewer.config.analog_gui.AnalogGui method)

with_central_driver_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_facial_root_joint_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_left_eye_aim() (dna_viewer.config.analog_gui.AnalogGui method)

with_left_eye_aim_up_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_left_eye_driver_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_left_eye_joint_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_right_eye_aim() (dna_viewer.config.analog_gui.AnalogGui method)

with_right_eye_aim_up_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_right_eye_driver_name() (dna_viewer.config.analog_gui.AnalogGui method)

with_right_eye_joint_name() (dna_viewer.config.analog_gui.AnalogGui method)

add_blend_shapes (dna_viewer.config.character.BuildOptions attribute)

add_key_frames (dna_viewer.config.character.BuildOptions attribute)

add_normals (dna_viewer.config.character.BuildOptions attribute)

analog_gui_options (dna_viewer.config.character.Character attribute)

create_character_node (dna_viewer.config.character.Character attribute)

create_display_layers (dna_viewer.config.character.Character attribute)

rig_logic_config (dna_viewer.config.character.Character attribute)

with_additional_assembly_script() (dna_viewer.config.character.Character method)

with_analog_gui() (dna_viewer.config.character.Character method)

with_analog_gui_path() (dna_viewer.config.character.Character method)

with_angle_modifier() (dna_viewer.config.character.Character method)

with_blend_shapes() (dna_viewer.config.character.Character method)

with_character_node() (dna_viewer.config.character.Character method)

with_key_frames() (dna_viewer.config.character.Character method)

with_linear_modifier() (dna_viewer.config.character.Character method)

angle_modifier (dna_viewer.config.character.SpaceModifiers attribute)

linear_modifier (dna_viewer.config.character.SpaceModifiers attribute)

animated_map_naming (dna_viewer.config.rig_logic.RigLogic attribute)

blend_shape_naming (dna_viewer.config.rig_logic.RigLogic attribute)

with_animated_map_naming() (dna_viewer.config.rig_logic.RigLogic method)

with_blend_shape_naming() (dna_viewer.config.rig_logic.RigLogic method)

with_control_naming() (dna_viewer.config.rig_logic.RigLogic method)

with_dna_file_path() (dna_viewer.config.rig_logic.RigLogic method)

with_joint_naming() (dna_viewer.config.rig_logic.RigLogic method)

destination_path (dna_viewer.config.scene.SaveOptions attribute)

conditional_table (dna_viewer.model.behavior.AnimatedMapsData attribute)

cut_values (dna_viewer.model.behavior.ConditionalTable attribute)

from_values (dna_viewer.model.behavior.ConditionalTable attribute)

slope_values (dna_viewer.model.behavior.ConditionalTable attribute)

to_values (dna_viewer.model.behavior.ConditionalTable attribute)

joint_column_count (dna_viewer.model.behavior.JointsData attribute)

joint_row_count (dna_viewer.model.behavior.JointsData attribute)

animated_maps (dna_viewer.model.definition.Definition attribute)

blend_shape_channels (dna_viewer.model.definition.Definition attribute)

gui_control_names (dna_viewer.model.definition.Definition attribute)

neutral_joint_rotations (dna_viewer.model.definition.Definition attribute)

neutral_joint_translations (dna_viewer.model.definition.Definition attribute)

raw_control_names (dna_viewer.model.definition.Definition attribute)

indices_for_lod (dna_viewer.model.definition.NamesAndIndices attribute)

coordinate_system (dna_viewer.model.descriptor.Descriptor attribute)

db_complexity (dna_viewer.model.descriptor.Descriptor attribute)

rotation_unit (dna_viewer.model.descriptor.Descriptor attribute)

translation_unit (dna_viewer.model.descriptor.Descriptor attribute)

get_all_skin_weights_joint_indices_for_mesh() (dna_viewer.model.dna.DNA method)

get_all_skin_weights_values_for_mesh() (dna_viewer.model.dna.DNA method)

get_blend_shape_target_deltas_with_vertex_id() (dna_viewer.model.dna.DNA method)

get_lowest_lod_containing_meshes() (dna_viewer.model.dna.DNA method)

get_maximum_influence_per_vertex() (dna_viewer.model.dna.DNA method)

get_normal_for_mesh_index_and_layout() (dna_viewer.model.dna.DNA method)

get_polygon_faces_and_connects() (dna_viewer.model.dna.DNA method)

get_skin_weight_matrix_for_mesh() (dna_viewer.model.dna.DNA method)

get_texture_coordinate_index() (dna_viewer.model.dna.DNA method)

get_vertex_layout_positions_for_mesh_index() (dna_viewer.model.dna.DNA method)

get_vertex_positions_for_mesh_index() (dna_viewer.model.dna.DNA method)

get_vertex_texture_coordinates_for_mesh() (dna_viewer.model.dna.DNA method)

skin_weight_joint_indices (dna_viewer.model.face_vertex.FaceVertex attribute)

skin_weight_values (dna_viewer.model.face_vertex.FaceVertex attribute)

texture_coordinate (dna_viewer.model.face_vertex.FaceVertex attribute)

texture_coordinate_index (dna_viewer.model.geometry.Layout attribute)

joint_indices (dna_viewer.model.geometry.SkinWeightsData attribute)

face_vertex_layouts (dna_viewer.model.geometry.Topology attribute)

texture_coordinates (dna_viewer.model.geometry.Topology attribute)

dna_vertex_layout_normals (dna_viewer.model.mesh.Mesh attribute)

dna_vertex_layout_positions (dna_viewer.model.mesh.Mesh attribute)

add_animated_maps() (dna_viewer.reader.behavior.Behavior method)

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

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

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

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

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

read_geometry_for_mesh_index() (dna_viewer.reader.dna.DNA method)

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

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

read_blend_shapes() (dna_viewer.reader.geometry.Geometry method)

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

BuildOptionsWidget (class in dna_viewer.ui.build_options_widget)

create_checkbox() (dna_viewer.ui.build_options_widget.BuildOptionsWidget method)

unchecked (dna_viewer.ui.build_options_widget.Checkbox attribute)

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

build_scene() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

create_main_widget() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

create_ui() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

get_character_config() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

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

process() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

set_progress() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

set_size() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

setup_window() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow method)

show_window() (dna_viewer.ui.dna_viewer_window.DnaViewerWindow static method)

staticMetaObject (dna_viewer.ui.dna_viewer_window.DnaViewerWindow attribute)

window (dna_viewer.ui.dna_viewer_window.DnaViewerWindow attribute)

show_dna_viewer_window() (in module dna_viewer.ui.dna_viewer_window)

create_new_scene() (dna_viewer.ui.elements.Elements static method)

select_analog_gui_path (dna_viewer.ui.elements.Elements attribute)

create_aas_selector() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_body() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_body_widgets() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_build_options() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_dna_chooser() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_dna_selector() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_file_chooser() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_gui_selector() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_header() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_header_widgets() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_help_btn() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_load_dna_button() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_mesh_selector() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_process_btn() (dna_viewer.ui.elements_creator.ElementsCreator method)

create_progress_bar() (dna_viewer.ui.elements_creator.ElementsCreator method)

is_enabled_and_checked() (dna_viewer.ui.elements_creator.ElementsCreator method)

on_dna_selected() (dna_viewer.ui.elements_creator.ElementsCreator method)

on_generic_changed() (dna_viewer.ui.elements_creator.ElementsCreator method)

on_help() (dna_viewer.ui.elements_creator.ElementsCreator method)

on_load_dna_clicked() (dna_viewer.ui.elements_creator.ElementsCreator method)

staticMetaObject (dna_viewer.ui.file_chooser.FileChooser attribute)

create_mesh_tree() (dna_viewer.ui.mesh_tree_list.MeshTreeList method)

fill_mesh_list() (dna_viewer.ui.mesh_tree_list.MeshTreeList method)

get_selected_meshes() (dna_viewer.ui.mesh_tree_list.MeshTreeList method)

staticMetaObject (dna_viewer.ui.mesh_tree_list.MeshTreeList attribute)

tree_item_changed() (dna_viewer.ui.mesh_tree_list.MeshTreeList method)

staticMetaObject (dna_viewer.ui.widgets.MessageDialog attribute)

AdditionalAssemblyScript (class in dna_viewer.util.additional_assembly_script)

run_additional_assembly_script() (dna_viewer.util.additional_assembly_script.AdditionalAssemblyScript static method)

run_script() (dna_viewer.util.additional_assembly_script.AdditionalAssemblyScript static method)

add_analog_gui() (dna_viewer.util.character_creator.CharacterCreator method)

add_gui() (dna_viewer.util.character_creator.CharacterCreator method)

add_joints() (dna_viewer.util.character_creator.CharacterCreator method)

add_key_frames() (dna_viewer.util.character_creator.CharacterCreator method)

add_rig_logic_node() (dna_viewer.util.character_creator.CharacterCreator method)

attach_mesh_to_lod() (dna_viewer.util.character_creator.CharacterCreator method)

create_lod_node() (dna_viewer.util.character_creator.CharacterCreator method)

create_meshes() (dna_viewer.util.character_creator.CharacterCreator method)

create_rig_node() (dna_viewer.util.character_creator.CharacterCreator method)

import_gui() (dna_viewer.util.character_creator.CharacterCreator method)

calculate_angle_modifier() (dna_viewer.util.conversion.Conversion static method)

calculate_linear_modifier() (dna_viewer.util.conversion.Conversion static method)

get_angle_unit_from_int() (dna_viewer.util.conversion.Conversion static method)

get_linear_unit_from_int() (dna_viewer.util.conversion.Conversion static method)

source_py_file() (dna_viewer.util.import_util.Import static method)

get_translation() (dna_viewer.util.maya_util.Maya static method)

set_translation() (dna_viewer.util.maya_util.Maya static method)

get_mesh_indices_containing_string() (in module dna_viewer.util.mesh)

create_all_derived_meshes() (dna_viewer.util.mesh_blend_shape.MeshBlendShape static method)

create_blend_shape_node() (dna_viewer.util.mesh_blend_shape.MeshBlendShape static method)

print_mesh_indices_containing_string() (in module dna_viewer.util.mesh_helper)

add_texture_coordinates() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

create_mesh_object() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

get_texture_data() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

get_vertex_positions_from_dna_vertex_positions() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

prepare_mesh() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

rename_mesh() (dna_viewer.util.mesh_neutral.MeshNeutral static method)

add_normals() (dna_viewer.util.mesh_normals.MeshNormals static method)

add_skin_cluster() (dna_viewer.util.mesh_skin.MeshSkin static method)

prepare_joints() (dna_viewer.util.mesh_skin.MeshSkin static method)

set_skin_weights() (dna_viewer.util.mesh_skin.MeshSkin static method)

create_stream_reader() (dna_viewer.util.reader.Reader static method)

geometry_readers (dna_viewer.util.reference.Reference attribute)

get_geometry_reader() (dna_viewer.util.reference.Reference static method)

set_geometry_reader() (dna_viewer.util.reference.Reference static method)

add_rig_logic() (dna_viewer.util.rig_logic.RigLogic static method)

LOD_PREFIX (dna_viewer.util.scene_builder.SceneBuilder attribute)

add_display_layers() (dna_viewer.util.scene_builder.SceneBuilder static method)

new_scene() (dna_viewer.util.scene_builder.SceneBuilder static method)

save_scene() (dna_viewer.util.scene_builder.SceneBuilder static method)

set_units() (dna_viewer.util.scene_builder.SceneBuilder static method)

default_lambert_shader() (dna_viewer.util.shader.Shader static method)

create() (dna_viewer.util.skin_weights_maya.MayaSkinWeights static method)

get_skin_cluster_influence() (dna_viewer.util.skin_weights_maya.MayaSkinWeights static method)

get_skin_weights_for_mesh_name() (dna_viewer.util.skin_weights_maya.MayaSkinWeights static method)

joints (dna_viewer.util.skin_weights_maya.MayaSkinWeights attribute)

no_of_influences (dna_viewer.util.skin_weights_maya.MayaSkinWeights attribute)

skinning_method (dna_viewer.util.skin_weights_maya.MayaSkinWeights attribute)

vertices_info (dna_viewer.util.skin_weights_maya.MayaSkinWeights attribute)

import_skin_weights() (dna_viewer.util.skin_weights_maya.SkinWeightsMaya method)

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

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

dnac::CalculateMeshLowerLODsCommand::Impl::getMeshName (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::Impl::setJointIndex (C++ function)

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

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

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

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::ScaleCommand::Impl::scaleBlendShapeTargetDeltas (C++ function)

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

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

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

dnac::SetBlendShapeTargetDeltasCommand::Impl::setDeltas (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::masks (C++ member)

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

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

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

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::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::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::pruneBlendShapeTargets (C++ function)

dnac::DNACalibDNAReaderImpl::removeJointAnimation (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:

from typing import Optional

from maya.api.OpenMaya import MVector

from ..config.analog_gui import AnalogGui as AnalogGuiConfig
from ..util.error import DNAViewerError
from ..util.maya_util import Maya

class AnalogGui:
    """
    A builder class used for manipulating the AnalogGuiConfig used in the character building process

    Attributes
    ----------
    @type gui_path: str
    @param gui_path: The location of the gui file
    """

    def __init__(self, options: Optional[AnalogGuiConfig] = None) -> None:
        self.options = options or AnalogGuiConfig()
        self.eye_l_pos = Maya.get_translation(self.options.left_eye_joint_name)
        self.eye_r_pos = Maya.get_translation(self.options.right_eye_joint_name)

    def build(self) -> None:
        """Start building the analog gui"""

        if self.options.gui_path is None:
            raise DNAViewerError(
                "Unable to import analog gui, must provide gui path..."
            )

        self.add_eyes()
        self.add_eye_locators()

    def add_eyes(self) -> None:
        """Add eyes to the analog gui"""

        Maya.set_translation(
            self.options.central_driver_name,
            Maya.get_translation(self.options.facial_root_joint_name),
        )

        delta_l = Maya.get_translation(
            self.options.left_eye_aim_up_name
        ) - Maya.get_translation(self.options.left_eye_driver_name)
        delta_r = Maya.get_translation(
            self.options.right_eye_aim_up_name
        ) - Maya.get_translation(self.options.right_eye_driver_name)

        Maya.set_translation(self.options.left_eye_driver_name, self.eye_l_pos)
            self.options.right_eye_driver_name,
            self.eye_r_pos,
        )
            self.options.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.options.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.options.le_aim)
        eye_r_locator_pos = Maya.get_translation(self.options.re_aim)
        central_aim_pos = Maya.get_translation(self.options.central_aim)

        eye_middle_delta = (self.eye_l_pos - self.eye_r_pos) / 2

        eye_middle = self.eye_r_pos + eye_middle_delta

            self.options.central_aim,
            MVector(eye_middle[0], eye_middle[1], central_aim_pos[2]),
        )
            self.options.le_aim,
            MVector(self.eye_l_pos[0], self.eye_l_pos[1], eye_l_locator_pos[2]),
        )
            self.options.re_aim,
            MVector(self.eye_r_pos[0], self.eye_r_pos[1], eye_r_locator_pos[2]),
        )

import logging
from typing import Dict, List, Optional

from maya.api.OpenMaya import MObject

from ..config.character import Character as CharacterConfig
from ..model.dna import DNA
from ..util.character_creator import CharacterCreator

class CharacterBuildResult:
    """
    A class used for returning data after finishing the character build process

    @type nodes: Dict[int, List[MObject]]
    @param nodes: The PyNode objects created in the build process by LOD number
    """

    nodes: Dict[int, List[MObject]] = {}

    @staticmethod
    def create(nodes: Dict[int, List[MObject]]) -> "CharacterBuildResult":
        """
        Character creator method returning a character build result object

        @type nodes: Dict[int, List[MObject]]
        @param nodes: The PyNode objects created in the build process by LOD number
        """

        result = CharacterBuildResult()
        result.nodes = nodes
        return result

class Character:
    """
    A builder class used for building the character

    @type config: CharacterConfig
    @param config: The character configuration options used for building the character
    """

    def __init__(self, dna: DNA, config: Optional[CharacterConfig] = None) -> None:
        self.dna = dna
        self.config = config or CharacterConfig()

    def build(self) -> CharacterBuildResult:
        """Builds the character"""

        if self.config.dna is None:
            raise DNAViewerError("must provide DNA path")

        creator = CharacterCreator(config=self.config, dna=self.dna)

        logging.info("******************************")
        logging.info(creator.character_name)

        creator.create_character_node()
        creator.add_joints_to_character()
        creator.create_ctrl_attributes_on_joint()
        creator.create_animated_map_attributes()
        creator.add_key_frames()
        creator.create_geometry_node()
        creator.create_rig_node()
        creator.create_character_meshes()

        creator.add_gui()
        creator.add_analog_gui()
        creator.add_rig_logic_node()
        creator.run_additional_assembly_script()

        logging.info(f"{creator.character_name} built successfully!")
        return CharacterBuildResult.create(nodes=creator.meshes)

from maya import cmds

from ..config.gui import Gui as GuiConfig
from ..const.naming import GUI_HOLDER
from ..const.space import OBJECT

class Gui:
    """
    A builder class used for manipulating the GuiConfig used in the character building process

    """

    def __init__(self, options: Optional[GuiConfig] = None) -> None:
        self.options = options or GuiConfig()

        """Start building the gui"""

            raise DNAViewerError("Unable to import gui, must provide gui path...")

        self.position_gui(GUI_HOLDER)

    def position_gui(self, group_name: str) -> None:
        """Sets the gui position to align with the character eyes"""

        if not cmds.objExists(self.options.eye_gui_name) or not cmds.objExists(
            self.options.left_eye_joint_name
        ):
            logging.warning(
                "could not find nodes needed for positioning the gui, leaving it at its default position..."
            )
            return

        gui_y = Maya.get_transform(self.options.eye_gui_name).translation(OBJECT).y
        eyes_y = (
            Maya.get_transform(self.options.left_eye_joint_name).translation(OBJECT).y
        )
        delta_y = eyes_y - gui_y

        if isinstance(self.options.gui_translate_x, str):
            try:
                logging.warning(
                    "gui_translate_x should be a float, trying to cast the value to float..."
                )
                self.options.gui_translate_x = float(self.options.gui_translate_x)
            except ValueError:
                logging.error("could not cast string value to float")
                return

        Maya.get_transform(group_name).translateBy(
            MVector(self.options.gui_translate_x, delta_y, 0), OBJECT
        )

from typing import Dict, List

from ..model.joint import Joint as JointModel

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

    @type joints: List[JointModel]
    @param joints: data representing the joints

    @type linear_modifier: float
    @param linear_modifier: The linear modifier that should be applied to the joints

    @type angle_modifier: float
    @param angle_modifier: The linear modifier that should be applied to 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], linear_modifier: float, angle_modifier: float
    ) -> None:
        self.joints = joints
        self.linear_modifier = linear_modifier
        self.angle_modifier = angle_modifier
        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 = (
            self.linear_modifier * joint.translation.x,
            self.linear_modifier * joint.translation.y,
            self.linear_modifier * joint.translation.z,
        )
        orientation = (
            self.angle_modifier * joint.orientation.x,
            self.angle_modifier * joint.orientation.y,
            self.angle_modifier * joint.orientation.z,
        )
        cmds.joint(
            p=position,
            o=orientation,
            n=joint.name,
            r=in_parent_space,
            a=not in_parent_space,
        )
        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 maya.api.OpenMaya import MDagModifier, MFnMesh, MObject

from ..config.mesh import Mesh as MeshConfig
from ..model.mesh import Mesh as MeshModel
from ..util.mesh_blend_shape import MeshBlendShape
from ..util.mesh_neutral import MeshNeutral
from ..util.mesh_normals import MeshNormals
from ..util.mesh_skin import MeshSkin

class MayaMesh:
    """

    @type config: MeshConfig
    @param config: The mesh configuration options used for creating meshes

    @type data: MeshModel
    @param data: mesh data used in the mesh creation process

    @type fn_mesh: om.MFnMesh
    @param fn_mesh: OpenMaya class used for creating the mesh

    @type mesh_object: om.MObject
    @param mesh_object: the object representing the mesh

    @type dag_modifier: om.MDagModifier
    @param dag_modifier: OpenMaya class used for naming the mesh
    """

    def __init__(self, config: MeshConfig, dna: DNA) -> None:
        self.config = config
        self.data: MeshModel = MeshModel()
        self.fn_mesh: MFnMesh = None
        self.mesh_object: MObject = None
        self.dag_modifier: MDagModifier = None

    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
        """
        MeshNeutral.prepare_mesh(self.config, self.dna, self.data)
        self.fn_mesh, self.mesh_object = MeshNeutral.create_mesh_object(
            self.config, self.data
        )
        self.dag_modifier = MeshNeutral.rename_mesh(
            self.config, self.dna, self.mesh_object
        )
        MeshNeutral.add_texture_coordinates(
            self.config, self.dna, self.data, self.fn_mesh
        )
        return self.mesh_object

    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.config.mesh_index):
            MeshBlendShape.create_all_derived_meshes(
                self.config,
                self.dna,
                self.data,
                self.fn_mesh,
                self.dag_modifier,
                add_mesh_name_to_blend_shape_channel_name,
            )
            MeshBlendShape.create_blend_shape_node(
                self.dna.get_mesh_name(self.config.mesh_index),
                self.data.derived_mesh_names,
            )

    def add_skin(self, joint_names: List[str], joint_ids: List[int]) -> None:
        """
        Adds skin to the mesh

        @type joint_names: List[str]
        @param joint_names: Joint names needed for adding the skin cluster

        @type joint_ids: List[int]
        @param joint_ids: Joint indices needed for setting skin weights
        """

        mesh_name = self.dna.get_mesh_name(self.config.mesh_index)

        MeshSkin.add_skin_cluster(
            self.dna, self.config.mesh_index, mesh_name, joint_names
        )
        MeshSkin.set_skin_weights(
            self.dna, self.config.mesh_index, mesh_name, joint_ids
        )

    def add_normals(self) -> None:
        """Add normals to the mesh"""

        MeshNormals.add_normals(self.config, self.dna, self.data, self.fn_mesh)

from ..builder.maya_mesh import MayaMesh as MayaMeshBuilder
from ..config.character import BuildOptions, Character, SpaceModifiers
from ..util.reference import get_geometry_reader

class Mesh:
    """

    @type dna: DNA
    @param dna: The location of the DNA file

    @type mesh_index: int
    @param mesh_index: The mesh index we are working with

    @type joint_ids: List[int]
    @param joint_ids: The joint indices used for adding skin

    @type joint_names: List[str]
    @param joint_names: The joint names used for adding skin

    @type options: BuildOptions
    @param options: The build options that will be applied when creating the mesh

    @type modifiers: SpaceModifiers
    @param modifiers: Space modifiers that should be applied when creating the mesh

    @type config: Character
    @param config: The character configuration options used for getting information about the DNA path, build options and space modifiers

    @type mesh: MayaMeshBuilder
    @param mesh: The builder class object for creating the meshes

    @param dna: The DNA object that was loaded in
    """

        self,
        character_config: Character,
        dna: DNA,
        mesh_index: int,
    ) -> None:
        self.mesh_index: int = mesh_index
        self.joint_ids: List[int] = []
        self.joint_names: List[str] = []

        self.options: BuildOptions = character_config.options
        self.modifiers: SpaceModifiers = character_config.modifiers
        self.config = MeshConfig(
            mesh_index=self.mesh_index,
            linear_modifier=self.modifiers.linear_modifier,
        )
        self.mesh = MayaMeshBuilder(self.config, self.dna)

        """Starts the build process, creates the neutral mesh, then adds normals, blends shapes and skin if needed"""

        self.create_neutral_mesh()
        self.add_normals()
        self.add_blend_shapes()
        self.add_skin()

    def create_neutral_mesh(self) -> None:
        """Creates the neutral mesh"""

        self.mesh.create_neutral_mesh()

        """Adds normals to the mesh if it is set in the build options"""

        if self.options.add_normals:
            logging.info("adding normals...")
            self.mesh.add_normals()

    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.options.add_blend_shapes:
            logging.info("reading blend shapes...")
            geometry_reader = get_geometry_reader(self.mesh_index, self.dna.path)
            geometry_reader.read_blend_shapes(
                self.dna.get_mesh_data(self.mesh_index), self.mesh_index
            )
            logging.info("adding blend shapes...")
            self.mesh.add_blend_shapes(
                self.options.add_mesh_name_to_blend_shape_channel_name
            )

    def add_skin(self) -> None:
        """Adds skin to the mesh if it is set in the build options"""

        if self.options.add_skin and self.options.add_joints:
            self.joint_ids, self.joint_names = MeshSkin.prepare_joints(
                self.dna,
                self.mesh_index,
            )
            self.mesh.add_skin(self.joint_names, self.joint_ids)

from ..config.rig_logic import RigLogic as RigLogicConfig

class RigLogic:
    """

    @type config: RigLogicConfig
    @param config: The rig logic configuration containing naming options used for creating the execution string that will be called from maya to create the rig logic
    """

    def __init__(self, config: RigLogicConfig = None) -> None:
        self.config = config or RigLogicConfig()

    def build_command(self) -> str:
        """
        Creates the command string that will be executed in maya to create the rig logic

        @rtype: str
        @returns: the string representing the command that needs to be executed
        """

        if self.config.name is None:
            raise DNAViewerError("Must provide a node name for the rig logic")

        if self.config.dna_file_path is None:
            raise DNAViewerError("Must provide a DNA file path")

        string_to_be_executed = self.config.command
        string_to_be_executed += f' -n "{self.config.name}"'
        string_to_be_executed += f' -dfp "{self.config.dna_file_path}"'
        string_to_be_executed += f' -cn "{self.config.control_naming}"'
        string_to_be_executed += f' -jn "{self.config.joint_naming}"'
        string_to_be_executed += f' -bsn "{self.config.blend_shape_naming}"'
        string_to_be_executed += f' -amn "{self.config.animated_map_naming}"; '
        return string_to_be_executed

import traceback
from typing import List, Optional

from ..builder.character import Character, CharacterBuildResult
from ..config.scene import Scene as SceneConfig
from ..util.conversion import Conversion
from ..util.scene_builder import SceneBuilder

class SceneError(DNAViewerError):
    """An error class that inherits DNAViewerError and gets raised in the scene building process"""

    def __init__(self, message: str) -> None:
        super().__init__(f"Scene creation failed! Reason: {message}")

class SceneResult:
    """
    A class used for returning data after finishing the scene build process

    @type character_results: List[CharacterBuildResult]
    @param character_results: A list of all character build results from the scene building process
    """

    character_results: List[CharacterBuildResult] = []

class Scene:
    """

    @type config: Optional[SceneConfig]
    @param config: The scene configuration options used for building the scene
    """

    def __init__(self, config: Optional[SceneConfig] = None) -> None:
        self.config = config or SceneConfig()
        self.dna: Optional[DNA] = None

    def build(self) -> SceneResult:
        """Starts the scene building process"""

        logging.info("Started building scene")
        try:
            result = SceneResult()
            if self.config.create_new_scene:
                SceneBuilder.new_scene()
            for character_config in self.config.characters:
                self.dna = character_config.dna
                self.set_units()
                if any(
                    character_config.create_display_layers
                    for character_config in self.config.characters
                ):
                    self.add_display_layers()
                self.build_scene(result)
            self.save_scene()
            return result
        except SceneError as e:
            traceback.print_exc()
            raise e
        except Exception as e:
            logging.error(f"Unhandled exception, {e}")
            raise SceneError(str(e)) from e

    def save_scene(self) -> None:
        """Saves the scene from @config"""

        if self.config.save_options:
            SceneBuilder.save_scene(self.config.save_options)

    def set_units(self) -> None:
        """Sets the translation and rotation units of the scene from @config"""

        SceneBuilder.set_units(self.dna, self.config.units)

    def add_display_layers(self) -> None:
        """Adds the display layers to the scene"""

        display_layers_needed = self.get_display_layers()
        SceneBuilder.add_display_layers(display_layers_needed)

    def get_display_layers(self) -> List[int]:
        """Gets a lod id list that need to be created for the meshes from @config"""
        meshes: List[int] = []
        for char_config in self.config.characters:
            for idx, meshes_per_lod in enumerate(
                self.dna.get_meshes_by_lods(char_config.meshes)
            ):
                if meshes_per_lod:
                    meshes.append(idx)
        return list(set(meshes))

    def build_scene(self, build_result: SceneResult) -> None:
        """Builds the scene for the configuration from @config

        @type build_result: SceneResult
        @param build_result: The SceneResult object that will contain results
        """
        linear_modifier = Conversion.calculate_linear_modifier(
            self.config.units.linear_unit
        )
        angle_modifier = Conversion.calculate_angle_modifier(
            self.config.units.angle_unit
        )

        results: List[CharacterBuildResult] = []
        for character_config in self.config.characters:
            character_config.with_linear_modifier(linear_modifier).with_angle_modifier(
                angle_modifier
            )
            results.append(Character(self.dna, character_config).build())

        build_result.character_results = results

from dataclasses import dataclass, field
from typing import Any, Dict, Optional

@dataclass
class AdditionalAssemblyScript:
    """
    A class used to represent the additional assembly script config

    @type path: Optional[str]
    @param path: The location of the script file

    @type module_name: Optional[str]
    @param module_name: The module name to be used

    @type method: str
    @param method: The method that should be called

    @type parameter: Dict[Any, Any]
    @param parameter: The parameters that will be passed as the method arguments
    """

    path: Optional[str] = field(default=None)
    module_name: Optional[str] = field(default=None)
    method: str = "run_after_assemble"
    parameter: Dict[Any, Any] = field(default_factory=dict)

    """
    A class used to represent the analog gui config

    @type facial_root_joint_name: str
    @param facial_root_joint_name: The name of the facial root joint

    @type left_eye_joint_name: str
    @param left_eye_joint_name: The name of the left eye joint

    @type right_eye_joint_name: str
    @param right_eye_joint_name: The name of the right eye joint

    @type central_driver_name: str
    @param central_driver_name: The name of the central driver

    @type left_eye_driver_name: str
    @param left_eye_driver_name: The name of the left eye driver

    @type right_eye_driver_name: str
    @param right_eye_driver_name: The name of the right eye driver

    @type central_aim: str
    @param central_aim: The name of the central aim

    @type le_aim: str
    @param le_aim: The name of the left eye aim

    @type re_aim: str
    @param re_aim: The name of the right eye aim
    """

    gui_path: str = field(default=None)

    facial_root_joint_name: str = "FACIAL_C_FacialRoot"
    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"

    def with_gui_path(self, gui_path: str) -> "AnalogGui":
        """Set the gui path

        @type gui_path: str
        @param gui_path: The location of the gui file

        @rtype: AnalogGui
        @returns: The instance of the changed object
        """

        self.gui_path = gui_path
        return self

    def with_facial_root_joint_name(self, joint_name: str) -> "AnalogGui":
        """Set the facial root joint name

        @type joint_name: str
        @param joint_name: The value to set the facial root joint name to

        """

        self.facial_root_joint_name = joint_name

    def with_left_eye_joint_name(self, joint_name: str) -> "AnalogGui":
        """Set the left eye joint name

        @param joint_name: The value to set the left eye joint name to

        """

        self.left_eye_joint_name = joint_name

    def with_right_eye_joint_name(self, joint_name: str) -> "AnalogGui":
        """Set the right eye joint name

        @param joint_name: The value to set the right eye joint name to

        """

        self.right_eye_joint_name = joint_name

    def with_central_driver_name(self, driver_name: str) -> "AnalogGui":
        """Set the central driver name

        @type driver_name: str
        @param driver_name: The value to set the central driver name to

        """

        self.central_driver_name = driver_name

    def with_left_eye_driver_name(self, driver_name: str) -> "AnalogGui":
        """Set the left eye driver name

        @param driver_name: The value to set the left eye driver name to

        """

        self.left_eye_driver_name = driver_name

    def with_right_eye_driver_name(self, driver_name: str) -> "AnalogGui":
        """Set right eye driver name

        """

        self.right_eye_driver_name = driver_name

    def with_left_eye_aim_up_name(self, name: str) -> "AnalogGui":
        """Set the left eye aim up name

        @type name: str
        @param name: The value to set the left eye aim up name to

        """

        self.left_eye_aim_up_name = name

    def with_right_eye_aim_up_name(self, name: str) -> "AnalogGui":
        """Set the right eye aim up name

        @param name: The value to set the right eye aim up name to

        """

        self.right_eye_aim_up_name = name

    def with_central_aim(self, name: str) -> "AnalogGui":
        """Set the central aim name

        @param name: The value to set the central aim name to

        """

        self.central_aim = name

    def with_left_eye_aim(self, name: str) -> "AnalogGui":
        """Set the left eye aim name

        @param name: The value to set the left eye aim name to

        """

        self.le_aim = name

    def with_right_eye_aim(self, name: str) -> "AnalogGui":
        """Set the right eye aim name

        """

        self.re_aim = name

from pathlib import Path

from ..config.aas import AdditionalAssemblyScript
from ..config.analog_gui import AnalogGui
from ..config.gui import Gui
from ..config.rig_logic import RigLogic

class SpaceModifiers:
    """
    A class used to represent the character config

    @param linear_modifier: The linear modifier that needs to be applied to the character

    @param angle_modifier: The angle modifier that needs to be applied to the character
    """

    linear_modifier: float = field(default=None)
    angle_modifier: float = field(default=None)

class BuildOptions:
    """
    A class used to represent the build options we want to apply while building the character

    @type add_joints: bool
    @param add_joints: A flag representing whether joints should be added

    @type add_normals: bool
    @param add_normals: A flag representing whether normals should be added

    @type add_blend_shapes: bool
    @param add_blend_shapes: A flag representing whether blend shapes should be added

    @type add_skin: bool
    @param add_skin: 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 mash name of blend shape channel is added to name when creating it
    """

    add_joints: bool = field(default=False)
    add_normals: bool = field(default=False)
    add_blend_shapes: bool = field(default=False)
    add_skin: bool = field(default=False)
    add_ctrl_attributes_on_root_joint: bool = field(default=False)
    add_animated_map_attributes_on_root_joint: bool = field(default=False)
    add_key_frames: bool = field(default=False)
    add_mesh_name_to_blend_shape_channel_name: bool = field(default=False)

    """

    @param dna: Instance of DNA object

    @param options: The configuration containing the build options

    @param modifiers: The configuration containing space modifiers for the character

    @type meshes: List[int]
    @param meshes: The list of mesh ids that need to be processed

    @type rig_logic_config: RigLogic
    @param rig_logic_config: The rig logic configuration file containing naming patterns

    @type gui_options: Gui
    @param gui_options: The configuration containing the gui options

    @type analog_gui_options: AnalogGui
    @param analog_gui_options: The configuration containing the analog gui options

    @type create_character_node: bool
    @param create_character_node: A flag representing whether the character should be parented to a character transform node in the scene hierarchy

    @type create_display_layers: bool
    @param create_display_layers: A flag representing whether the created meshes should be assigned to a display layer

    @type aas: AdditionalAssemblyScript
    @param aas: The configuration containing the additional assembly script options

    """

    dna: DNA = field(default=None)
    options: BuildOptions = field(default_factory=BuildOptions)
    modifiers: SpaceModifiers = field(default_factory=SpaceModifiers)
    meshes: List[int] = field(default_factory=list)
    rig_logic_config: RigLogic = field(default=None)
    gui_options: Gui = field(default=None)
    analog_gui_options: AnalogGui = field(default=None)
    create_character_node: bool = field(default=True)
    create_display_layers: bool = field(default=True)
    aas: AdditionalAssemblyScript = field(default=None)

    def with_dna(self, dna: DNA) -> "Character":
        """
        Set the DNA file path

        @type dna: DNA
        @param dna: The location of the DNA file

        @rtype: Character
        """

    def with_character_node(self, value: bool) -> "Character":
        """
        Set the flag that represents if the result character rig should be parented to a character transform

        @type value: bool
        @param value: The flag that represents if the result character rig should be parented to a character transform

        """
        self.create_character_node = value

    def with_add_meshes_to_display_layers(self, value: bool) -> "Character":
        """
        Set the flag that represents if the created meshes should be put in their corresponding display layer

        @param value: The flag that represents if the created meshes should be put in their corresponding display layer

        """
        self.create_display_layers = value

    def with_joints(self) -> "Character":
        """
        Set the flag that represents if joints should be created

        """

        self.options.add_joints = True

    def with_gui_path(self, gui_path: str) -> "Character":
        """
        Set the gui path

        """

        self.gui_options = Gui(gui_path=gui_path)

    def with_gui(self, gui: Gui) -> "Character":
        """
        Set the gui configuration

        @type gui: Gui
        @param gui: The gui configuration file

        """

        self.gui_options = gui

    def with_analog_gui(self, analog_gui_config: AnalogGui) -> "Character":
        """
        Set the analog gui configuration

        @type analog_gui_config: AnalogGui
        @param analog_gui_config: The configuration containing the analog gui options

        """

        self.analog_gui_options = analog_gui_config

    def with_analog_gui_path(
        self, analog_gui_path: Optional[str] = None
    ) -> "Character":
        """
        Set the analog gui path

        @type analog_gui_path: Optional[str]
        @param analog_gui_path: The configuration containing the analog gui options

        """

        self.analog_gui_options = AnalogGui(gui_path=analog_gui_path)

    def with_blend_shapes(self) -> "Character":
        """
        Set the flag that represents if blend shapes should be created

        """

        self.options.add_blend_shapes = True

    def with_skin(self) -> "Character":
        """
        Set the flag that represents if skin should be created

        """

        self.options.add_skin = True

    def with_normals(self) -> "Character":
        """
        Set the flag that represents if normals should be created

        """

        self.options.add_normals = True

    def with_meshes(self, meshes: List[int]) -> "Character":
        """
        Set the meshes that need to be processed

        @type meshes: List[int]
        @param meshes: The list of mesh ids that need to be processed

        """

        self.meshes = meshes
        self.meshes.sort()

    def with_linear_modifier(self, modifier: float) -> "Character":
        """
        Set the linear modifier

        @type modifier: float
        @param modifier: The value of the linear modifier

        """

        self.modifiers.linear_modifier = modifier

    def with_angle_modifier(self, modifier: float) -> "Character":
        """
        Set the angle modifier

        @param modifier: The value of the angle modifier

        """

        self.modifiers.angle_modifier = modifier

    def with_rig_logic(self, rig_logic_node_config: RigLogic = None) -> "Character":
        """
        Set the rig logic configuration

        @type rig_logic_node_config: RigLogic
        @param rig_logic_node_config: The rig logic configuration file containing naming patterns

        """

        self.rig_logic_config = rig_logic_node_config or RigLogic()

    def with_additional_assembly_script(
        self, aas_config: AdditionalAssemblyScript
        """
        Set the additional assembly script configuration

        @type aas_config: AdditionalAssemblyScript
        @param aas_config: The configuration containing the additional assembly script options

        """

        if aas_config.path is None:
            logging.error(
                "No AAS path provided, skipping additional assembly script execution"
            )
            return self

        if aas_config.module_name is None:
            aas_config.module_name = Path(aas_config.path).stem

        self.aas = aas_config

    def with_ctrl_attributes_on_root_joint(self) -> "Character":
        """
        Set the flag that represents if control attributes should be added on the root joint

        """

        self.options.add_ctrl_attributes_on_root_joint = True

    def with_animated_map_attributes_on_root_joint(self) -> "Character":
        """
        Set the flag that represents if animated map attributes should be added on the root joint

        """

        self.options.add_animated_map_attributes_on_root_joint = True

    def with_key_frames(self) -> "Character":
        """
        Set the flag that represents if key frames should be added

        """

        self.options.add_key_frames = True

from ..const.gui import EYE_GUI, EYE_JOINT, GUI_TRANSLATE_X

class Gui:
    """
    A class used to represent the gui config

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

    left_eye_joint_name: str = EYE_JOINT
    eye_gui_name: str = EYE_GUI
    gui_translate_x: float = GUI_TRANSLATE_X

    def with_gui_path(self, gui_path: str) -> "Gui":
        """

        @rtype: Gui
        """

    def with_left_eye_joint_name(self, name: str) -> "Gui":
        """
        Set name of the left eye joint

        @param name: The name of the joint

        """

        self.left_eye_joint_name = name

    def with_eye_gui_name_name(self, name: str) -> "Gui":
        """
        Set name of the eye element in the gui

        @param name: The name of the element in the gui

        """

        self.eye_gui_name = name

    def with_gui_translate_x(self, value: float) -> "Gui":
        """
        Set the value the gui should be additionally translated on the X axis

        @type value: float
        @param value: The value the gui should be translated on the X axis

        """

        self.gui_translate_x = value

    """
    A class used to represent the mesh configuration

    @param mesh_index: The index of the mesh

    """

    mesh_index: Optional[int] = field(default=None)
    linear_modifier: Optional[float] = field(default=None)

from ..const.naming import BLEND_SHAPE_NAME_POSTFIX, FRM_MULTIPLIERS_NAME

    """
    A class used to represent the rig logic naming configuration

    @type command: str
    @param command: The command used to start creating the rig logic using the plugin

    @type name: str
    @param name: The name of the rig logic node

    @type dna_file_path: str
    @param dna_file_path: The location of the DNA file

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

    command: str = field(default="createEmbeddedNodeRL4")
    name: str = field(default="")
    dna_file_path: str = field(default="")
    control_naming: str = field(default="<objName>.<attrName>")
    joint_naming: str = field(default="<objName>.<attrName>")
    blend_shape_naming: str = field(
        default=f"<objName>{BLEND_SHAPE_NAME_POSTFIX}.<attrName>"
    )
    animated_map_naming: str = field(
        default=f"{FRM_MULTIPLIERS_NAME}.<objName>_<attrName>"
    )

    def with_name(self, name: str) -> "RigLogic":
        """
        Set the name of the rig logic node

        @param name: The name of the rig logic node

        @rtype: RigLogic
        """

        self.name = name

    def with_dna_file_path(self, dna_file_path: str) -> "RigLogic":
        """
        Set the location of the DNA file

        @type dna_file_path: str
        @param dna_file_path: The location of the DNA file

        """

        self.dna_file_path = dna_file_path

    def with_control_naming(self, control_naming: str) -> "RigLogic":
        """
        Set the naming pattern of controls

        @type control_naming: str
        @param control_naming: The naming pattern of controls

        """

        self.control_naming = control_naming

    def with_joint_naming(self, joint_naming: str) -> "RigLogic":
        """
        Set the naming pattern of joints

        @type joint_naming: str
        @param joint_naming: The naming pattern of joints

        """

        self.joint_naming = joint_naming

    def with_blend_shape_naming(self, blend_shape_naming: str) -> "RigLogic":
        """
        Set the naming pattern of blend shapes

        @type blend_shape_naming: str
        @param blend_shape_naming: The naming pattern of blend shapes

        """

        self.blend_shape_naming = blend_shape_naming

    def with_animated_map_naming(self, animated_map_naming: str) -> "RigLogic":
        """
        Set the naming pattern of animated maps

        @type animated_map_naming: str
        @param animated_map_naming: The naming pattern of animated maps

        """

        self.animated_map_naming = animated_map_naming

import pathlib

from ..config.character import Character
from .units import AngleUnit, LinearUnit, Unit

class SaveOptions:
    """
    A class used to represent the save options

    @param name: The name of the scene

    @type extension: str
    @param extension: The extension

    @type destination_path: str
    @param destination_path: The location where the scene should be saved, if no value provided it defaults to the current working directory
    """

    name: str = field(default=None)
    extension: str = field(default=None)
    destination_path: str = field(default=str(pathlib.Path.cwd()))

    def get_path(self) -> str:
        """
        Returns the file path

        @returns: The file path
        """
        return f"{self.destination_path}/{self.name}.{self.extension}"

    """
    A class used to represent the scene config

    @type dna_path: str
    @param dna_path: The location of the DNA file

    @type characters: List[Character]
    @param characters: The list of character configuration objects that need to be processed for the scene

    @type units: Unit
    @param units: The configuration containing the units for the scene

    @type create_new_scene: bool
    @param create_new_scene: A flag representing whether a new should be created when the build process starts

    @type save_options: SaveOptions
    @param save_options: A flag representing whether the created meshes should be assigned to a display layer
    """

    dna_path: str = field(default=None)
    characters: List[Character] = field(default_factory=list)
    units: Unit = field(default_factory=Unit)
    create_new_scene: bool = field(default=True)
    save_options: Optional[SaveOptions] = field(default=None)

    def with_character(self, character: Character) -> "Scene":
        """
        Adds a character to the list of characters that need to be processed

        @type character: Character
        @param character: The character configuration object

        @rtype: Scene
        """

        self.characters.append(character)

    def with_linear_unit(self, unit: LinearUnit) -> "Scene":
        """
        Set the linear unit for the scene

        @type unit: str
        @param unit: The linear unit name

        """

        self.units.linear_unit = unit

    def with_angle_unit(self, unit: AngleUnit) -> "Scene":
        """
        Set the angle unit for the scene

        @param unit: The angle unit name

        """
        self.units.angle_unit = unit

    def with_create_new_scene(self, create_new_scene: bool) -> "Scene":
        """
        Set the flag representing whether a new should be created when the build process starts

        @type create_new_scene: bool
        @param create_new_scene: The flag representing whether a new should be created when the build process starts

        """

        self.create_new_scene = create_new_scene

    def with_scene_file_path(
        self,
        name: str = "untitled_scene",
        extension: str = "mb",
        destination_path: str = None,
    ) -> "Scene":
        """
        Set the scene save configuration

        @param name: The name of the scene

        @type extension: str
        @param extension: The extension

        @type destination_path: str
        @param destination_path: The location where the scene should be saved

        """

        if destination_path is None:
            destination_path = str(pathlib.Path.cwd())

        self.save_options = SaveOptions(
            name=name, extension=extension, destination_path=destination_path
        )

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 AnimatedMapsData:
    """
    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 JointsData:
    """
    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)

class Behavior:
    """
    A model class for holding data about the behavior part of the DNA

    @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: AnimatedMapsData
    @param animated_maps: The data representing animated maps

    @type joints: JointsData
    @param joints: The data representing joints
    """

    gui_to_raw: ConditionalTable = field(default_factory=ConditionalTable)
    psd: PSDMatrix = field(default_factory=PSDMatrix)
    blend_shapes: BlendShapesData = field(default_factory=BlendShapesData)
    animated_maps: AnimatedMapsData = field(default_factory=AnimatedMapsData)
    joints: JointsData = field(default_factory=JointsData)

from typing import List, Tuple

from ..model.geometry import Point3

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

    @type names: List[str]
    @param names: List of names

    @type indices_for_lod: List[List[int]]
    @param indices_for_lod: List of indices per lod
    """

    names: List[str] = field(default_factory=list)
    indices_for_lod: List[List[int]] = field(default_factory=list)

class Joints(NamesAndIndices):
    """
    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)

class Definition:
    """
    A model class for holding data about the definition part of the DNA

    @type joints: Joints
    @param joints: The data about joints

    @type blend_shape_channels: NamesAndIndices
    @param blend_shape_channels: The names and indices of blend shape channels

    @type animated_maps: NamesAndIndices
    @param animated_maps: The names and indices of animated maps

    @type meshes: NamesAndIndices
    @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
    """

    joints: Joints = field(default_factory=Joints)
    blend_shape_channels: NamesAndIndices = field(default_factory=NamesAndIndices)
    animated_maps: NamesAndIndices = field(default_factory=NamesAndIndices)
    meshes: NamesAndIndices = field(default_factory=NamesAndIndices)

    gui_control_names: List[str] = field(default_factory=list)
    raw_control_names: List[str] = field(default_factory=list)

    mesh_blend_shape_channel_mapping: List[Tuple[int, int]] = field(
        default_factory=list
    )
    mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] = field(
    )

    neutral_joint_translations: List[Point3] = field(default_factory=list)
    neutral_joint_rotations: List[Point3] = field(default_factory=list)

from typing import Dict, Optional, Tuple

class Descriptor:
    """

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

    archetype: int = field(default=None)
    gender: int = field(default=None)
    age: int = field(default=None)
    metadata: Dict[str, str] = field(default_factory=dict)

    translation_unit: int = field(default=None)
    rotation_unit: int = field(default=None)

    coordinate_system: Optional[Tuple[int, int, int]] = field(default=None)

    lod_count: int = field(default=None)
    db_max_lod: int = field(default=None)
    db_complexity: str = field(default=None)
    db_name: str = field(default=None)

from typing import List, Optional, Tuple

from ..config.units import AngleUnit, LinearUnit
from ..model.behavior import Behavior
from ..model.definition import Definition
from ..model.descriptor import Descriptor
from ..model.geometry import UV, BlendShape, Geometry, Layout, Mesh, Point3
from ..model.joint import Joint

class BaseDNA:
    """
    A model class for holding data about the DNA

    @type descriptor: Descriptor
    @param descriptor: Data representing the descriptor part of the DNA

    @type definition: Definition
    @param definition: Data representing the definition part of the DNA

    @type behavior: Behavior
    @param behavior: Data representing the behavior part of the DNA

    @type geometry: Geometry
    @param geometry: Data representing the geometry part of the DNA
    """

    descriptor: Optional[Descriptor] = field(default=None)
    definition: Optional[Definition] = field(default=None)
    behavior: Optional[Behavior] = field(default=None)
    geometry: Optional[Geometry] = field(default=None)

class DNA(BaseDNA):
    """
    Used for getting data stored in the models

    @type path: str
    @param path: The path to the DNA file
    """

    def __init__(self, path: str) -> None:
        super().__init__()
        self.path = path

    def read_all_neutral_joints(self) -> List[Joint]:
        joints = []

        for i, name in enumerate(self.definition.joints.names):
            translation = self.definition.neutral_joint_translations[i]
            orientation = self.definition.neutral_joint_rotations[i]
            parent_name = self.definition.joints.names[
                self.definition.joints.parent_index[i]
            ]

            joint = Joint(
                name=name,
                translation=translation,
                orientation=orientation,
                parent_name=parent_name,
            )

            joints.append(joint)

        return joints

    def get_all_skin_weights_joint_indices_for_mesh(
        self, mesh_index: int
    ) -> List[List[int]]:
        return self.geometry.meshes[mesh_index].skin_weights.joint_indices

    def get_blend_shape_target_deltas_with_vertex_id(
        self, mesh_index: int, blend_shape_target_index: int
    ) -> List[Tuple[int, Point3]]:
        blend_shape = self.geometry.meshes[mesh_index].blend_shapes[
            blend_shape_target_index
        ]
        indices = list(blend_shape.deltas.keys())

        deltas: List[Point3] = []
        for i in indices:
            deltas.append(blend_shape.deltas[i])

        if not deltas:
            return []

        return list(zip(indices, deltas))

    def get_all_skin_weights_values_for_mesh(
    ) -> 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:
                "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:
                raise DNAViewerError(
                    "JointIndexArray for vertex can't be less than one!"
                )
            vertex_weights = []
            for joint_index, skin_weight_value in zip(indices, values):
                vertex_weights.append((joint_index, skin_weight_value))
            weight_matrix.append(vertex_weights)
        return weight_matrix

    def get_vertex_texture_coordinates_for_mesh(self, mesh_index: int) -> List[UV]:
        return self.geometry.meshes[mesh_index].topology.texture_coordinates

    def get_vertex_normals_for_mesh(self, mesh_index: int) -> List[Point3]:
        return self.geometry.meshes[mesh_index].topology.normals

    def get_raw_control_names(self) -> List[str]:
        return self.definition.raw_control_names

    def get_animated_map_names(self) -> List[str]:
        return self.definition.animated_maps.names

    def get_vertex_positions_for_mesh_index(self, mesh_index: int) -> List[Point3]:
        return self.geometry.meshes[mesh_index].topology.positions

    def get_vertex_layout_positions_for_mesh_index(self, mesh_index: int) -> List[int]:
        return [
            item.position_index
            for item in self.geometry.meshes[mesh_index].topology.layouts
        ]

    def get_faces(self, mesh_index: int) -> List[List[int]]:
        return self.geometry.meshes[mesh_index].topology.face_vertex_layouts

    def get_polygon_faces_and_connects(
        self,
        mesh_index: int = None,
        dna_faces: List[List[int]] = None,
        dna_vertex_layout_positions: List[int] = None,
    ) -> Tuple[List[int], List[int]]:
        if mesh_index is None:
            if None in (dna_faces, dna_vertex_layout_positions):
                    "get_polygon_faces_and_connects -> Must provide either mesh_index or dna_faces and dna_vertex_layout_positions"
                )
        if dna_faces is None:
            dna_faces = self.get_faces(mesh_index)
        if dna_vertex_layout_positions is None:
            dna_vertex_layout_positions = (
                self.get_vertex_layout_positions_for_mesh_index(mesh_index)
            )

        polygon_faces = []
        polygon_connects = []
        for vertices_layout_index_array in dna_faces:
            polygon_faces.append(len(vertices_layout_index_array))
            for vertex_layout_index_array in vertices_layout_index_array:
                polygon_connects.append(
                    dna_vertex_layout_positions[vertex_layout_index_array]
                )

        return polygon_faces, polygon_connects

    def get_layouts_for_mesh_index(self, mesh_index: int) -> List[Layout]:
        return self.geometry.meshes[mesh_index].topology.layouts

    def get_texture_coordinate_index(self, mesh_index: int, layout_id: int) -> int:
        return (
            self.geometry.meshes[mesh_index]
            .topology.layouts[layout_id]
            .texture_coordinate_index
        )

    def get_normal_for_mesh_index_and_layout(
        self, mesh_index: int, layout: Layout
    ) -> Point3:
        return self.geometry.meshes[mesh_index].topology.normals[layout.normal_index]

    def has_blend_shapes(self, mesh_index: int) -> bool:
        return (
            len([bs.channel for bs in self.geometry.meshes[mesh_index].blend_shapes])
            > 0
        )

    def get_maximum_influence_per_vertex(self, mesh_index: int) -> int:
        return self.geometry.meshes[
            mesh_index
        ].skin_weights.maximum_influence_per_vertex

    def get_blend_shape_name(self, blend_shape_channel: int) -> str:
        return self.definition.blend_shape_channels.names[blend_shape_channel]

    def get_geometry(self) -> Geometry:
        return self.geometry

    def get_mesh_name(self, mesh_index: int) -> str:
        return self.definition.meshes.names[mesh_index]

    def get_mesh_names(self) -> List[str]:
        return self.definition.meshes.names

    def get_translation_unit_int(self) -> int:
        return self.descriptor.translation_unit

    def get_linear_unit(self) -> LinearUnit:
        return Conversion.get_linear_unit_from_int(self.get_translation_unit_int())

    def get_angle_unit_int(self) -> int:
        return self.descriptor.rotation_unit

    def get_angle_unit(self) -> AngleUnit:
        return Conversion.get_angle_unit_from_int(self.get_angle_unit_int())

    def get_lod_count(self) -> int:
        return self.descriptor.lod_count

    def get_mesh_indices_for_lod(self, lod: int) -> List[int]:
        return self.definition.meshes.indices_for_lod[lod]

    def get_joint_indices_for_lod(self, lod: int) -> List[int]:
        return self.definition.joints.indices_for_lod[lod]

    def get_lowest_lod_containing_meshes(
        self, mesh_indices: List[int]
    ) -> Optional[int]:
        unique_mesh_indices = set(mesh_indices)
        for lod in range(self.get_lod_count()):
            if any(list(unique_mesh_indices & set(self.get_mesh_indices_for_lod(lod)))):
                return lod
        return None

    def get_character_name(self) -> str:
        return self.descriptor.name

    def get_meshes_by_lods(self, mesh_indices: List[int]) -> List[List[int]]:
        result_list = []
            temp = list(set(mesh_indices) & set(self.get_mesh_indices_for_lod(lod)))
            result_list.append(temp)
        return result_list

    def get_blend_shapes(self, mesh_index: int) -> List[BlendShape]:
        return self.geometry.meshes[mesh_index].blend_shapes

    def get_mesh_data(self, mesh_index: int) -> Mesh:
        return self.geometry.meshes[mesh_index]

    def get_mesh_id_from_mesh_name(self, mesh_name: str) -> Optional[int]:
        for mesh_id, mesh in enumerate(self.geometry.meshes.values()):
            if mesh.name == mesh_name:
                return mesh_id

    def get_mesh_count(self) -> int:
        return len(self.geometry.meshes.values())

from ..model.geometry import UV, Layout, Point3

class FaceVertex:
    """
    A model class for holding data about face vertices

    @type position: Point3
    @param position: Represents position as a point in a 3 dimensional space

    @type texture_coordinate: UV
    @param texture_coordinate: Represents the UV values of the texture coordinate

    @type normal: Point3
    @param normal: Represents the normal as a 3 dimensional vector

    @type layout: Layout
    @param layout: The layout this vertex is contained within

    @type skin_weight_values: List[float]
    @param skin_weight_values: The list of the skin weight values

    @type skin_weight_joint_indices: List[int]
    @param skin_weight_joint_indices: List of the skin weight joint indices
    """

    position: Optional[Point3] = field(default=None)
    texture_coordinate: Optional[UV] = field(default=None)
    normal: Optional[Point3] = field(default=None)
    layout: Optional[Layout] = field(default=None)
    skin_weight_values: List[float] = field(default_factory=list)
    skin_weight_joint_indices: List[int] = field(default_factory=list)

class Point3:
    """
    A model class for representing a 3 dimensional point

    @type x: float
    @param x: The value of x

    @type y: float
    @param y: The value of y

    @type z: float
    @param z: The value of z
    """

    x: float = field(default=0.0)
    y: float = field(default=0.0)
    z: float = field(default=0.0)

class UV:
    """
    A model class for holding data about the UV

    @type u: float
    @param u: The value of u

    @type v: float
    @param v: The value of v
    """

    u: float = field(default=0.0)
    v: float = field(default=0.0)

class Layout:
    """
    A model class for holding data about a single layout

    @type position_index: int
    @param position_index: An index representing position

    @type texture_coordinate_index: int
    @param texture_coordinate_index: A value representing the texture coordinate index

    @type normal_index: int
    @param normal_index: A value representing the normal index
    """

    position_index: int = field(default=0)
    texture_coordinate_index: int = field(default=0)
    normal_index: int = field(default=0)

class Topology:
    """
    A model class for holding data about the topology

    @type positions: List[Point3]
    @param positions: List of points in space representing the positions

    @type texture_coordinates: List[UV]
    @param texture_coordinates: List of UVs representing the positions

    @type normals: List[Point3]
    @param normals: List of 3 dimensional vectors representing the normals

    @type layouts: List[Layout]
    @param layouts: The list of Layout mappings

    @type face_vertex_layouts: List[List[int]]
    @param face_vertex_layouts: List of face vertex layout indices by face index
    """

    positions: List[Point3] = field(default_factory=list)
    texture_coordinates: List[UV] = field(default_factory=list)
    normals: List[Point3] = field(default_factory=list)
    layouts: List[Layout] = field(default_factory=list)
    face_vertex_layouts: List[List[int]] = field(default_factory=list)

class BlendShape:
    """
    A model class for holding data about the blend shape

    @type channel: int
    @param channel: The index pointing to the blend shape name

    @type deltas: Dict[int, Point3]
    @param deltas: A mapping of blend shape indices to the coordinate differences that are made by the blend shape
    """

    channel: int = field(default=None)
    deltas: Dict[int, Point3] = field(default_factory=dict)

class SkinWeightsData:
    """
    A model class for holding data about the skin weights

    @type maximum_influence_per_vertex: int
    @param maximum_influence_per_vertex: Number of maximum influences per vertex

    @type values: List[List[float]]
    @param values: The skin weight values per vertex index

    @type joint_indices: List[List[int]]
    @param joint_indices: The joint indces per vertex index
    """

    maximum_influence_per_vertex: int = field(default=None)
    values: List[List[float]] = field(default_factory=list)
    joint_indices: List[List[int]] = field(default_factory=list)

    """
    A model class for holding data about the mesh

    @param name: The name of the mesh

    @type topology: Topology
    @param topology: Data containing the topology of the mesh

    @type skin_weights: SkinWeightsData
    @param skin_weights: Data representing skin weights

    @type blend_shapes: List[BlendShape]
    @param blend_shapes: The list of blend shapes for the mesh
    """

    topology: Topology = field(default_factory=Topology)
    skin_weights: SkinWeightsData = field(default_factory=SkinWeightsData)
    blend_shapes: List[BlendShape] = field(default_factory=list)

class Geometry:
    """
    A model class for holding data about the geometry part of the dna

    @type meshes: Dict[int, Mesh]
    @param meshes: Mapping of mesh indices to mesh models
    """

    meshes: Dict[int, Mesh] = field(default_factory=dict)

    """
    A model class for holding data about a single joint

    @param name: The name of the joint

    @type translation: Point3
    @param translation: A point in 3 dimensional space which represents the translation of the joint

    @type orientation: Point3
    @param orientation: A point in 3 dimensional space which represents the orientation of the joint

    @type parent_name: str
    @param parent_name: The name of the parent joint
    """

    translation: Point3 = field(default_factory=Point3)
    orientation: Point3 = field(default_factory=Point3)
    parent_name: str = field(default=None)

    """
    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 dna_vertex_layout_normals: List[Point3]
    @param dna_vertex_layout_normals: Data representing layout normals 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 vertex_normals: List[Point3]
    @param vertex_normals: List of vertex layout normal 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)
    dna_vertex_layout_normals: List[Point3] = field(default_factory=list)
    polygon_faces: List[int] = field(default_factory=list)
    polygon_connects: List[int] = field(default_factory=list)
    vertex_normals: List[Point3] = field(default_factory=list)
    derived_mesh_names: List[str] = field(default_factory=list)

from dna import BinaryStreamReader

from ..model.behavior import AnimatedMapsData
from ..model.behavior import Behavior as BehaviorModel
from ..model.behavior import BlendShapesData, ConditionalTable, JointGroup, PSDMatrix

    """
    A class used for reading the behavior part of the DNA file

    @type reader: BinaryStreamReader
    @param reader: The stream reader being used

    @type behavior: BehaviorModel
    @param behavior: The object that holds the behavior data read from the DNA file
    """

    def __init__(self, stream_reader: BinaryStreamReader) -> None:
        self.reader = stream_reader
        self.behavior: Optional[BehaviorModel] = None

    def read(self) -> BehaviorModel:
        """
        Starts reading in the behavior part of the DNA

        @rtype: BehaviorModel
        @returns: the instance of the created behavior model
        """

        self.behavior = BehaviorModel()
        self.add_gui_to_raw()
        self.add_psd()
        self.add_joints()
        self.add_animated_maps()
        return self.behavior

    def add_gui_to_raw(self) -> None:
        """Reads in the gui to raw mapping"""

        self.behavior.gui_to_raw = ConditionalTable(
            inputs=self.reader.getGUIToRawInputIndices(),
            outputs=self.reader.getGUIToRawOutputIndices(),
            from_values=self.reader.getGUIToRawFromValues(),
            to_values=self.reader.getGUIToRawToValues(),
            slope_values=self.reader.getGUIToRawSlopeValues(),
            cut_values=self.reader.getGUIToRawCutValues(),
        )

    def add_psd(self) -> None:
        """Reads in the PSD part of the behavior"""

        self.behavior.psd = PSDMatrix(
            count=self.reader.getPSDCount(),
            rows=self.reader.getPSDRowIndices(),
            columns=self.reader.getPSDColumnIndices(),
            values=self.reader.getPSDValues(),
        )

    def add_joints(self) -> None:
        """Reads in the joints part of the behavior"""

        self.behavior.joints.joint_row_count = self.reader.getJointRowCount()
        self.behavior.joints.joint_column_count = self.reader.getJointColumnCount()
        for lod in range(self.reader.getLODCount()):
            self.behavior.joints.joint_variable_attribute_indices.append(
                self.reader.getJointVariableAttributeIndices(lod)
            )
        for joint_group_index in range(self.reader.getJointGroupCount()):
            self.behavior.joints.joint_groups.append(
                JointGroup(
                    lods=self.reader.getJointGroupLODs(joint_group_index),
                    inputs=self.reader.getJointGroupInputIndices(joint_group_index),
                    outputs=self.reader.getJointGroupOutputIndices(joint_group_index),
                    values=self.reader.getJointGroupValues(joint_group_index),
                    joints=self.reader.getJointGroupJointIndices(joint_group_index),
                )
            )

        """Reads in the blend shapes part of the behavior"""

        self.behavior.blend_shapes = BlendShapesData(
            lods=self.reader.getBlendShapeChannelLODs(),
            inputs=self.reader.getBlendShapeChannelInputIndices(),
            outputs=self.reader.getBlendShapeChannelOutputIndices(),
        )

    def add_animated_maps(self) -> None:
        """Reads in the animated maps part of the behavior"""

        self.behavior.animated_maps = AnimatedMapsData(
            lods=self.reader.getAnimatedMapLODs(),
            conditional_table=ConditionalTable(
                from_values=self.reader.getAnimatedMapFromValues(),
                to_values=self.reader.getAnimatedMapToValues(),
                slope_values=self.reader.getAnimatedMapSlopeValues(),
                cut_values=self.reader.getAnimatedMapCutValues(),
                inputs=self.reader.getAnimatedMapInputIndices(),
                outputs=self.reader.getAnimatedMapOutputIndices(),
            ),
        )

from ..model.definition import Definition as DefinitionModel

    """
    A class used for reading the definition part of the DNA file

    @type definition: DefinitionModel
    @param definition: The object that holds the definition data read from the DNA file
    """

        self.definition: Optional[DefinitionModel] = None

    def read(self) -> DefinitionModel:
        """
        Starts reading in the definition part of the DNA

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

        self.definition = DefinitionModel()

        self.add_controls()
        self.add_blend_shape_channels()
        self.add_meshes()
        self.add_mesh_blend_shape_channel_mapping()
        self.add_neutral_joints()

        return self.definition

    def add_neutral_joints(self) -> None:
        """Reads in the neutral joints part of the definition"""

        neutral_joint_translation_xs = self.reader.getNeutralJointTranslationXs()
        neutral_joint_translation_ys = self.reader.getNeutralJointTranslationYs()
        neutral_joint_translation_zs = self.reader.getNeutralJointTranslationZs()
        neutral_joint_translation_count_x = len(neutral_joint_translation_xs)
        for index in range(neutral_joint_translation_count_x):
            self.definition.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.reader.getNeutralJointRotationXs()
        neutral_joint_rotation_ys = self.reader.getNeutralJointRotationYs()
        neutral_joint_rotation_zs = self.reader.getNeutralJointRotationZs()
        neutral_joint_rotation_count_x = len(neutral_joint_rotation_xs)
        for index in range(neutral_joint_rotation_count_x):
            self.definition.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.reader.getMeshBlendShapeChannelMappingCount()):
            mapping = self.reader.getMeshBlendShapeChannelMapping(index)
            self.definition.mesh_blend_shape_channel_mapping.append(
                (mapping.meshIndex, mapping.blendShapeChannelIndex)
            )
            self.definition.mesh_blend_shape_channel_mapping_indices_for_lod.append(
                self.reader.getMeshBlendShapeChannelMappingIndicesForLOD(lod)
            )

    def add_meshes(self) -> None:
        """Reads in the meshes of the definition"""

        for index in range(self.reader.getMeshCount()):
            self.definition.meshes.names.append(self.reader.getMeshName(index))
        for index in range(self.reader.getLODCount()):
            self.definition.meshes.indices_for_lod.append(
                self.reader.getMeshIndicesForLOD(index)
            )

        """Reads in the animated maps of the definition"""

        for index in range(self.reader.getAnimatedMapCount()):
            self.definition.animated_maps.names.append(
                self.reader.getAnimatedMapName(index)
            )
            self.definition.animated_maps.indices_for_lod.append(
                self.reader.getAnimatedMapIndicesForLOD(index)
            )

    def add_blend_shape_channels(self) -> None:

        for index in range(self.reader.getBlendShapeChannelCount()):
            self.definition.blend_shape_channels.names.append(
                self.reader.getBlendShapeChannelName(index)
            )
            self.definition.blend_shape_channels.indices_for_lod.append(
                self.reader.getBlendShapeChannelIndicesForLOD(index)
            )

        """Reads in the joints of the definition"""

        for index in range(self.reader.getJointCount()):
            self.definition.joints.names.append(self.reader.getJointName(index))
            self.definition.joints.parent_index.append(
                self.reader.getJointParentIndex(index)
            )
            self.definition.joints.indices_for_lod.append(
                self.reader.getJointIndicesForLOD(index)
            )

    def add_controls(self) -> None:
        """Reads in the gui and raw controls of the definition"""

        for index in range(self.reader.getGUIControlCount()):
            self.definition.gui_control_names.append(
                self.reader.getGUIControlName(index)
            )
        for index in range(self.reader.getRawControlCount()):
            self.definition.raw_control_names.append(
                self.reader.getRawControlName(index)
            )

from ..model.descriptor import Descriptor as DescriptorModel

    """
    A class used for reading the descriptor part of the DNA file

    @type descriptor: DescriptorModel
    @param descriptor: The object that holds the descriptor data read from the DNA file
    """

        self.descriptor: Optional[DescriptorModel] = None

    def read(self) -> DescriptorModel:
        """
        Starts reading in the descriptor part of the DNA

        @rtype: DescriptorModel
        @returns: the instance of the created descriptor model
        """

        self.descriptor = DescriptorModel()

        self.add_basic_data()
        self.add_metadata()
        self.add_geometry_data()
        self.add_db_data()

        return self.descriptor

    def add_basic_data(self) -> None:
        """Reads in the character name, archetype, gender and age"""

        self.descriptor.name = self.reader.getName()
        self.descriptor.archetype = self.reader.getArchetype()
        self.descriptor.gender = self.reader.getGender()
        self.descriptor.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()):
            self.descriptor.metadata[
                self.reader.getMetaDataKey(i)
            ] = self.reader.getMetaDataValue(self.reader.getMetaDataKey(i))

    def add_geometry_data(self) -> None:
        """Sets the translation unit, rotation unit, and coordinate system from the DNA file"""

        self.descriptor.translation_unit = self.reader.getTranslationUnit()
        self.descriptor.rotation_unit = self.reader.getRotationUnit()
        coordinate_system = self.reader.getCoordinateSystem()
        self.descriptor.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.descriptor.lod_count = self.reader.getLODCount()
        self.descriptor.db_max_lod = self.reader.getDBMaxLOD()
        self.descriptor.db_complexity = self.reader.getDBComplexity()
        self.descriptor.db_name = self.reader.getDBName()

from ..model.dna import DNA as DNAModel
from ..model.geometry import Geometry as GeometryModel
from ..model.geometry import Mesh
from ..reader.behavior import Behavior as BehaviorReader
from ..reader.definition import Definition as DefinitionReader
from ..reader.descriptor import Descriptor as DescriptorReader
from ..reader.geometry import Geometry as GeometryReader
from ..util.reader import Reader
from ..util.reference import set_geometry_reader

def load_dna(dna_path: str = None) -> DNAModel:
    return DNA.load_dna(path=dna_path)

class DNA:
    """
    """

    def load_dna(path: str) -> DNAModel:
        """
        Loads in the DNA from the given file path

        @type dna: DNAModel
        @param dna: The DNA data

        @rtype: DNA
        @returns: An object representing the DNA data
        """

        logging.info(f"loading DNA {path}")
        reader = DNA(path)
        return reader.read()

        self.stream_reader = Reader.create_stream_reader(path)
        self.dna: Optional[DNAModel] = None

    def read(self) -> DNAModel:
        """
        Reads in the base DNA data as well as the meshes

        @rtype: DNAModel
        @returns: The DNA data
        """

        self.dna = DNAModel(path=self.path)
        self.read_base()
        self.load_meshes()
        return self.dna

    def read_base(self) -> None:
        """Reads in the base DNA data without the mesh data"""

        self.dna.descriptor = self.read_descriptor()
        self.dna.definition = self.read_definition()
        self.dna.behavior = self.read_behavior()
        self.dna.geometry = GeometryModel()

    def load_meshes(self) -> None:
        """Loads all the mesh data"""

        for lod in range(self.dna.descriptor.lod_count):
            for mesh_index in self.dna.definition.meshes.indices_for_lod[lod]:
                self.dna.geometry.meshes[mesh_index] = self.load_mesh(
                    mesh_index=mesh_index
                )

    def load_mesh(self, mesh_index: int) -> Mesh:
        """
        Loads geometry data for a single mesh at the given index

        @type mesh_index: int
        @param mesh_index: mesh index

        @rtype: Mesh
        @returns: mesh data
        """

        return self.read_geometry_for_mesh_index(mesh_index=mesh_index)

    def read_descriptor(self) -> DescriptorModel:
        """
        Reads the descriptor part of the DNA

        @returns: Descriptor data from the DNA
        """

        return DescriptorReader(self.stream_reader).read()

    def read_definition(self) -> DefinitionModel:
        """
        Reads the definition part of the DNA

        @returns: Definition data from the DNA
        """

        return DefinitionReader(self.stream_reader).read()

    def read_behavior(self) -> BehaviorModel:
        """

        """

        return BehaviorReader(self.stream_reader).read()

    def read_geometry_for_mesh_index(self, mesh_index: int) -> Mesh:
        """
        Reads the geometry for a given mesh index

        @returns: Mesh data
        """

        reader = GeometryReader(stream_reader=self.stream_reader, mesh_index=mesh_index)
        set_geometry_reader(dna_path=self.path, geometry_reader=reader)

from typing import Dict, Optional

from ..const.printing import BLEND_SHAPE_PRINT_RANGE
from ..model.geometry import UV, BlendShape, Layout, Mesh, Point3

    """
    A class used for reading the geometry part of the DNA file

    @type mesh: Mesh
    @param mesh: The mesh model

    @param mesh_index: The mesh index
    """

    def __init__(self, stream_reader: BinaryStreamReader, mesh_index: int) -> None:
        self.mesh: Optional[Mesh] = None
        self.mesh_index = mesh_index

    def read(self) -> Mesh:
        """
        Starts reading in the mesh from the geometry part of the DNA

        @returns: The instance of the mesh model
        """

        self.mesh = Mesh()

        self.add_mesh_name()
        self.add_topology()
        self.add_skin_weights()
        return self.mesh

    def add_mesh_name(self) -> None:
        """Reads in the mesh name"""

        self.mesh.name = self.reader.getMeshName(self.mesh_index)

    def add_skin_weights(self) -> None:
        """Reads in the skin weights"""

        self.mesh.skin_weights.maximum_influence_per_vertex = (
            self.reader.getMaximumInfluencePerVertex(self.mesh_index)
        )
        for vertex_index in range(self.reader.getVertexPositionCount(self.mesh_index)):
            self.mesh.skin_weights.values.append(
                self.reader.getSkinWeightsValues(self.mesh_index, vertex_index)
            )
            self.mesh.skin_weights.joint_indices.append(
                self.reader.getSkinWeightsJointIndices(self.mesh_index, vertex_index)
            )

    def add_topology(self) -> None:
        """Reads in the positions, texture coordinates, normals, layouts and face vertex layouts"""

        self.add_positions()
        self.add_texture_coordinates()
        self.add_layouts()
        self.add_face_vertex_layouts()

    def add_face_vertex_layouts(self) -> None:
        """Reads in the face vertex layouts"""

        for face_index in range(self.reader.getFaceCount(self.mesh_index)):
            self.mesh.topology.face_vertex_layouts.append(
                self.reader.getFaceVertexLayoutIndices(self.mesh_index, face_index)
            )

    def add_layouts(self) -> None:
        """Reads in the vertex layouts"""

        for layout_index in range(self.reader.getVertexLayoutCount(self.mesh_index)):
            position_id, texture_coordinate_id, normal_id = self.reader.getVertexLayout(
                self.mesh_index, layout_index
            )
            self.mesh.topology.layouts.append(
                Layout(
                    position_index=position_id,
                    texture_coordinate_index=texture_coordinate_id,
                    normal_index=normal_id,
                )
            )

        """Reads in the normals"""

        for normal_index in range(self.reader.getVertexNormalCount(self.mesh_index)):
            x, y, z = self.reader.getVertexNormal(self.mesh_index, normal_index)
            self.mesh.topology.normals.append(Point3(x=x, y=y, z=z))

    def add_texture_coordinates(self) -> None:
        """Reads in the texture coordinates"""

        for texture_coordinate_index in range(
            self.reader.getVertexTextureCoordinateCount(self.mesh_index)
        ):
            u, v = self.reader.getVertexTextureCoordinate(
                self.mesh_index, texture_coordinate_index
            )
            self.mesh.topology.texture_coordinates.append(UV(u=u, v=v))

    def add_positions(self) -> None:
        """Reads in the vertex positions"""

            x, y, z = self.reader.getVertexPosition(self.mesh_index, vertex_index)
            self.mesh.topology.positions.append(Point3(x=x, y=y, z=z))

    def read_target_deltas(self, blend_shape_target_index: int) -> Dict[int, Point3]:
        """
        Reads in the target deltas

        @rtype: Dict[int, Point3]
        @returns: Mapping of vertex indices to positions
        """

        result: Dict[int, Point3] = {}

        vertices = self.reader.getBlendShapeTargetVertexIndices(
            self.mesh_index, blend_shape_target_index
        )

        blend_shape_target_delta_count = self.reader.getBlendShapeTargetDeltaCount(
        )
        for delta_index in range(blend_shape_target_delta_count):
            x, y, z = self.reader.getBlendShapeTargetDelta(
                self.mesh_index, blend_shape_target_index, delta_index
            )
            result[vertices[delta_index]] = Point3(x=x, y=y, z=z)

    def read_blend_shapes(self, mesh: Mesh, mesh_index: int) -> None:
        """
        Reads in the blend shapes

        @type mesh: Mesh
        @param mesh: The mesh model

        @param mesh_index: The mesh index
        """

        blend_shape_target_count = self.reader.getBlendShapeTargetCount(mesh_index)
        for blend_shape_target_index in range(blend_shape_target_count):
            if (blend_shape_target_index + 1) % BLEND_SHAPE_PRINT_RANGE == 0:
                logging.info(
                    f"\t{blend_shape_target_index + 1} / {blend_shape_target_count}"
                )

            mesh.blend_shapes.append(
                BlendShape(
                    channel=self.reader.getBlendShapeChannelIndex(
                        mesh_index, blend_shape_target_index
                    ),
                    deltas=self.read_target_deltas(blend_shape_target_index),
                )
            )

        if blend_shape_target_count % BLEND_SHAPE_PRINT_RANGE != 0:
            logging.info(f"\t{blend_shape_target_count} / {blend_shape_target_count}")

from enum import Enum
from typing import Callable

from PySide2.QtWidgets import QCheckBox, QHBoxLayout, QLabel, QWidget

from ..ui.elements import Elements

class Checkbox(Enum):
    unchecked = 0
    checked = 2

class BuildOptionsWidget(QWidget):
    """
    Widget containing build options checkboxes

    @type elements: Elements
    @param elements: The object containing references to the UI elements

    @type layout: QHBoxLayout
    @param layout: The layout containing the build options checkboxes
    """

        self, elements: Elements, on_generic_changed: Callable[[int], None]
    ) -> None:

        self.elements = elements

        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.on_generic_changed = on_generic_changed

        build_options_label = QLabel("Build options:")
        self.layout.addWidget(build_options_label)

        self.elements.joints_cb = self.create_checkbox("joints", self.on_joints_changed)
        self.elements.blend_shapes_cb = self.create_checkbox(
            "blend shapes", self.on_generic_changed
        )
        self.elements.skin_cb = self.create_checkbox("skin", self.on_generic_changed)
        self.elements.rig_logic_cb = self.create_checkbox("rig logic")

        self.setLayout(self.layout)

    def create_checkbox(
        self,
        label: str,
        on_changed: Callable[[int], None] = None,
        checked: bool = False,
        enabled: bool = False,
    ) -> QCheckBox:
        """
        Adds a checkbox with given parameters and connects them to the on_changed method

        @type label: str
        @param label: The label 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)
        if on_changed:
            checkbox.stateChanged.connect(on_changed)
        self.layout.addWidget(checkbox)
        return checkbox

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

        @type state: int
        @param state: The changed state of the checkbox
        """

        if state == Checkbox.checked.value:
            self.elements.process_btn.setEnabled(True)
            if self.elements.mesh_tree_list.get_selected_meshes():
                self.elements.skin_cb.setEnabled(True)
        else:
            self.elements.skin_cb.setEnabled(False)
            if not self.elements.mesh_tree_list.get_selected_meshes():
                self.elements.process_btn.setEnabled(False)
        self.on_generic_changed(state)

from maya.cmds import confirmDialog
from PySide2 import QtCore, QtWidgets

from ..builder.scene import Scene
from ..const.ui import (
    MARGIN_BOTTOM,
    MARGIN_LEFT,
    MARGIN_RIGHT,
    MARGIN_TOP,
    SPACING,
    WINDOW_OBJECT,
    WINDOW_SIZE_HEIGHT_MAX,
    WINDOW_SIZE_HEIGHT_MIN,
    WINDOW_SIZE_WIDTH_MAX,
    WINDOW_SIZE_WIDTH_MIN,
    WINDOW_TITLE,
)
from ..reader.dna import DNA
from ..ui.elements_creator import ElementsCreator
from .widgets import QHLine

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

class DnaViewerWindow(QtWidgets.QMainWindow):
    """
    UI Window

    @type elements_creator: ElementsCreator
    @param elements_creator: The class used for creating UI elements

    @type build_scene_successful: bool
    @param build_scene_successful: A flag representing if building of the scene was a success

    @type character_config: Character
    @param character_config: The character configuration that will be processed
    """

    window: QtWidgets.QMainWindow = None

    def __init__(self, parent: QtWidgets.QWidget = None) -> None:
        super().__init__(parent)
        self.elements = Elements()
        self.elements_creator = ElementsCreator(self, self.elements)

        self.setup_window()
        self.create_ui()
        self.build_scene_successful = False
        self.character_config: Optional[Character] = None

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

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

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

        self.elements.main_widget = self.create_main_widget()
        self.setCentralWidget(self.elements.main_widget)
        self.set_size()

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

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

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

        widget = QtWidgets.QWidget()
        layout = QtWidgets.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)

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

        self.character_config = self.get_character_config()

        self.elements.main_widget.setEnabled(False)
        self.build_scene()
        self.elements.main_widget.setEnabled(True)

    def get_character_config(self) -> Character:
        """
        Gets the character config from the UI elements

        @returns: character configuration object
        """

        gui_path = self.elements.get_gui_path()
        gui_options = Gui(gui_path=gui_path) if gui_path else None

        analog_gui_path = self.elements.get_analog_gui_path()
        analog_gui_options = (
            AnalogGui(gui_path=analog_gui_path) if analog_gui_path else None
        )

        aas_path = self.elements.get_aas_path()
        aas = (
            AdditionalAssemblyScript(
                path=aas_path,
                module_name=Path(aas_path).stem,
            )
            if aas_path
            else None
        )
        riglogic = (
            RigLogic() if Elements.is_checked(self.elements.rig_logic_cb) else None
        )
        config = Character(
            dna=self.elements.dna,
            create_character_node=False,
            create_display_layers=False,
            meshes=self.elements.mesh_tree_list.get_selected_meshes(),
            gui_options=gui_options,
            analog_gui_options=analog_gui_options,
            aas=aas,
            rig_logic_config=riglogic,
        )

        config.options = self.elements.get_build_options()

        return config

    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.elements.progress_bar.setFormat(text)
        if value is not None:
            self.elements.progress_bar.setValue(value)

    def build_scene(self) -> None:
        """Start building the scene"""

        try:
            self.set_progress(text="Processing in progress...", value=0)

            dna_file_path = Elements.get_file_path(self.elements.select_dna_path)

            if dna_file_path:
                DNA.load_dna(dna_file_path)

            self.set_progress(value=33)

            builder = Scene()
            builder.config.with_character(self.character_config)
            builder.config.with_linear_unit(unit=LinearUnit.cm).with_angle_unit(
                unit=AngleUnit.degree
            )
            self.set_progress(value=66)

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

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

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

        @throws RuntimeError

        @returns: main window instance
        """

        for obj in QtWidgets.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.window.show()

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

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

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QCheckBox, QProgressBar, QPushButton, QWidget

from ..config.character import BuildOptions
from ..ui.file_chooser import FileChooser

class Elements:
    """
    Used for holding references to elements that get added to widgets

    @type main_widget: QWidget
    @param main_widget: The main widget that holds everything

    @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 normals_cb: QCheckBox
    @param normals_cb: The checkbox that represents if normals 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 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 assembly 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
    """

    main_widget: QWidget = None
    select_dna_path: FileChooser = None
    load_dna_btn: QPushButton = None
    mesh_tree_list: QWidget = None
    joints_cb: QCheckBox = None
    normals_cb: QCheckBox = None
    blend_shapes_cb: QCheckBox = None
    skin_cb: QCheckBox = None
    rig_logic_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 create_new_scene() -> None:
        """Creates a new scene"""

        cmds.file(new=True, force=True)

    def get_file_path(input: FileChooser) -> Optional[str]:
        """
        Gets the file path contained in the provided FileChooser widget

        @type input: FileChooser
        @param input: The FileChooser widget we want to get the file path from

        @rtype: Optional[str]
        @returns: The value extracted from the FileChooser
        """

        path: str = input.get_file_path()
        if path and Path(path.strip()).exists():
            return path

    def get_dna_path(self) -> Optional[str]:
        """
        Gets the DNA file path

        @returns: The DNA file path
        """

        return Elements.get_file_path(self.select_dna_path)

    def get_gui_path(self) -> Optional[str]:
        """
        Gets the gui file path

        @returns: The gui file path
        """

        return Elements.get_file_path(self.select_gui_path)

    def get_analog_gui_path(self) -> Optional[str]:
        """
        Gets the analog gui file path

        @returns: The analog gui file path
        """

        return Elements.get_file_path(self.select_analog_gui_path)

    def get_aas_path(self) -> Optional[str]:
        """
        Gets the additional assembly script file path

        @returns: The additional assembly script file path
        """

        return Elements.get_file_path(self.select_aas_path)

    def get_build_options(self) -> BuildOptions:
        """
        Gets and returns a build options configuration from the corresponding UI elements

        @rtype: BuildOptions
        @returns: The object containing build option configuration
        """

        return BuildOptions(
            add_joints=Elements.is_checked(self.joints_cb),
            add_normals=Elements.is_checked(self.normals_cb),
            add_blend_shapes=Elements.is_checked(self.blend_shapes_cb),
            add_skin=Elements.is_checked(self.skin_cb),
        )

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

import webbrowser

from PySide2 import QtCore
from PySide2.QtWidgets import (
    QCheckBox,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

    HELP_URL,
    MARGIN_BODY_LEFT,
    MARGIN_BODY_RIGHT,
    MARGIN_BODY_TOP,
    MARGIN_HEADER_BOTTOM,
    MARGIN_HEADER_LEFT,
    MARGIN_HEADER_RIGHT,
    MARGIN_HEADER_TOP,
    SPACING,
)
from ..reader.dna import load_dna
from ..ui.build_options_widget import BuildOptionsWidget
from ..ui.mesh_tree_list import MeshTreeList
from ..version import __version__

class ElementsCreator:
    """
    Used for creating elements that get added to the main window

    @type window: QMainWindow
    @param window: The main UI window

    @param elements: The container with references to the UI elements

    @type body: QVBoxLayout
    @param body: The body layout

    @type header: QHBoxLayout
    @param header: The header layout
    """

    def __init__(self, window: QMainWindow, elements: Elements) -> None:
        self.window = window
        self.body: QVBoxLayout = None
        self.header: QHBoxLayout = None

    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(
            MARGIN_BODY_LEFT, MARGIN_BODY_TOP, MARGIN_BODY_RIGHT, MARGIN_BOTTOM
        )
        self.body.setSpacing(SPACING)
        self.create_body_widgets()
        return self.body

    def create_body_widgets(self) -> None:
        """Creates and adds all needed widgets to the body"""

        self.create_dna_selector()
        self.elements.mesh_tree_list = self.create_mesh_selector()
        self.create_build_options()
        self.elements.select_gui_path = self.create_gui_selector()
        self.elements.select_analog_gui_path = self.create_analog_gui_selector()
        self.elements.select_aas_path = self.create_aas_selector()
        self.elements.process_btn = self.create_process_btn(self.window)
        self.elements.progress_bar = self.create_progress_bar(self.window)

    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()
        self.create_header_widgets()
        return self.header

    def create_header_widgets(self) -> None:
        """Creates and adds all needed widgets to the header"""

        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)

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

        """

        btn = QPushButton(self.window)
        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.window,
                "About",
                "Sorry, this application does not have documentation yet.",
            )

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

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

        widget = QWidget()
        self.elements.select_dna_path = self.create_dna_chooser()
        self.elements.load_dna_btn = self.create_load_dna_button(
            self.elements.select_dna_path
        )

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

        layout = QVBoxLayout()
        layout.addWidget(self.elements.select_dna_path)
        layout.addWidget(self.elements.load_dna_btn)
        layout.setContentsMargins(0, 0, 0, 0)
        widget.setLayout(layout)

        self.body.addWidget(widget)

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

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

        enabled = Elements.get_file_path(input) is not None
        self.elements.load_dna_btn.setEnabled(enabled)
        self.elements.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:", "Select a DNA file", "DNA files (*.dna)"
        )

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

        """

        QtCore.QCoreApplication.processEvents()
        dna_file_path = Elements.get_file_path(input)

        if dna_file_path:
            self.elements.dna = load_dna(dna_file_path)
            lod_count = self.elements.dna.descriptor.lod_count
            meshes = self.elements.dna.definition.meshes
            self.elements.mesh_tree_list.fill_mesh_list(lod_count, meshes)
            self.elements.joints_cb.setEnabled(True)
            self.elements.process_btn.setEnabled(False)

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

    def create_file_chooser(self, label: str, caption: str, filter: str) -> FileChooser:
        """
        Creates a file chooser widget that is used for selecting file paths

        @param label: 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,
            self.window,
            dialog_caption=caption,
            dialog_filter=filter,
            on_changed=self.on_generic_changed,
        )

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

        @returns: Gui selector widget
        """

            "Gui path:", "Select the gui file", "gui files (*.ma)"
        )

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

        @returns: Additional assembly script selector widget
        """

            "After assembly script path:", "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:", "Select the analog gui file", "analog gui files (*.ma)"
        )

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

        widget = BuildOptionsWidget(
            self.elements, on_generic_changed=self.on_generic_changed
        )

    def create_process_btn(self, window: QMainWindow) -> 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(window.process)

        self.body.addWidget(btn)
        return btn

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

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

        progress = QProgressBar(window)
        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
        """
        Method that gets called when the checkbox is changed

        """

        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.elements.dna and self.is_enabled_and_checked(
            self.elements.blend_shapes_cb
        ):
            if (
                len(self.elements.mesh_tree_list.get_selected_meshes())
                == self.elements.dna.get_mesh_count()
            ):
                all_total_meshes = True

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

        self.elements.rig_logic_cb.setEnabled(enabled)

from typing import Callable, Optional

    QFileDialog,
    QLabel,
    QLineEdit,
    QWidget,
)

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

    @type fc_text_field: QLineEdit
    @param fc_text_field: The input text field for storing the result path
    """

        self,
        label_text: 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)

        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)

        fc_btn = QPushButton(button_text)

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

        return str(self.fc_text_field.text())

    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
            )

    QGridLayout,
    QLabel,
    QTreeWidget,
    QTreeWidgetItem,
    QTreeWidgetItemIterator,
    QWidget,
)

from ..model.definition import NamesAndIndices

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, elements: Elements) -> None:

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

        layout = QGridLayout()
        layout.addWidget(self.mesh_tree, 0, 0, 4, 1)

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

        self.setLayout(layout_holder)

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

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

    def fill_mesh_list(self, lod_count: int, meshes: NamesAndIndices) -> None:
        """
        Fills the mesh list with the meshes, and groups them by lods

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

        @type meshes: NamesAndIndices
        @param meshes: 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 = meshes.indices_for_lod[i]

            for mesh_index in meshes_in_lod:
                child = QTreeWidgetItem(parent)
                child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
                child.setText(0, f"{meshes.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.elements.dna.get_mesh_id_from_mesh_name(mesh_name)
            if mesh_index is not None:
                meshes.append(mesh_index)

            iterator += 1

        return meshes

    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.elements.skin_cb.setEnabled(self.elements.joints_cb.checkState())
            self.elements.blend_shapes_cb.setEnabled(True)
            self.elements.rig_logic_cb.setEnabled(False)

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

from typing import Union

from PySide2.QtWidgets import QFrame, QMessageBox, QSizePolicy, QSpacerItem, QWidget

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

    def __init__(self) -> None:
        self.setFrameShape(QFrame.HLine)
        self.setFrameShadow(QFrame.Sunken)

class QVSpacer(QSpacerItem):
    """A widget for creating a vertical line"""

        super().__init__(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)

class MessageDialog(QMessageBox):
    """A widget for creating a message dialog"""

        self,
        title: str,
        message: str,
        icon: QMessageBox.Icon = QMessageBox.Information,
        parent: Union[QWidget, None] = None,
    ) -> None:

        self.setIcon(icon)
        self.setWindowTitle(title)
        self.setText(message)
        self.show()

from typing import Any

from ..util.import_util import Import

    """
    A utility class used for running python scripts
    """

    def run_additional_assembly_script(config: Character) -> None:
        """
        Runs the additional assembly script using the provided character configuration.

        @type config: Character
        @param config: The character configuration object with the needed settings for running the additional assembly script
        """

        if config.aas:
            logging.info("running after assembly script...")
            AdditionalAssemblyScript.run_script(
                config.aas.module_name,
                config.aas.path,
                config.aas.method,
                config.aas.parameter,
            )

    def run_script(
        module_name: str, path: str, method: str, parameter: Any = None
    ) -> None:
        """
        Runs the additional assembly script using the provided parameters.

        @type module_name: str
        @param module_name: The module name that gets loaded

        @type path: str
        @param path: The path where the additional assembly script is located

        @type method: str
        @param method: The name of the method that gets called

        @type parameter: Any
        @param parameter: The parameters that get passed in the method (defaults to None)
        """

        try:
            script = Import.source_py_file(module_name, path)
            script_method = getattr(script, method)
            if parameter:
                script_method(parameter)
            else:
                script_method()
            raise DNAViewerError(f"Can't run aas script. Reason: {e}") from e

from typing import Any, Dict, List

from ..builder.scene import Scene, SceneResult
from ..const.naming import BLEND_SHAPE_NAME_POSTFIX

def assemble_rig(
    dna: DNA,
    analog_gui_path: str,
    gui_path: str = None,
    gui: Gui = None,
    aas_path: str = None,
    aas_fn: str = "run_after_assemble",
    aas_params: Dict[str, Any] = None,
    with_attributes_on_root_joint: bool = False,
    with_key_frames: bool = False,
    add_mesh_name_to_blend_shape_channel_name: bool = True,
) -> SceneResult:
    """
    Used for assembling the rig with the following parameters

    @param dna: Instance of DNA

    @param gui_path: The file location of the gui

    @type gui: Gui
    @param gui: The gui configuration, this can be used instead of gui_path for finer control

    @type analog_gui_path: str
    @param analog_gui_path: The file location of the analog gui

    @type aas_path: str
    @param aas_path: The file location of the additional assembly script

    @type aas_fn: str
    @param aas_fn: The method name called from the additional assembly script (default is "run_after_assemble")

    @type aas_params: str
    @param aas_params: The parameters dictionary that will get passed to the provided method name called from the additional assembly script (default is None)

    @type with_attributes_on_root_joint: bool
    @param with_attributes_on_root_joint: A flag used for adding attributes on the root joint (default is False)

    @type with_key_frames: bool
    @param with_key_frames: A flag used for adding key frames (default is False)

    @rtype: SceneResult
    @returns: A SceneResult object representing the info about the finished build process
    """

    meshes: List[int] = list(range(dna.get_mesh_count()))

    rig_logic = RigLogic()
    if add_mesh_name_to_blend_shape_channel_name:
        rig_logic.with_blend_shape_naming(
            f"<objName>{BLEND_SHAPE_NAME_POSTFIX}.<objName>__<attrName>"
        )

    character_config = (
        Character()
        .with_dna(dna)
        .with_character_node(False)
        .with_meshes(meshes)
        .with_analog_gui_path(analog_gui_path)
        .with_additional_assembly_script(
                method=aas_fn,
                parameter=aas_params if aas_params else {},
            )
        )
        .with_joints()
        .with_normals()
        .with_blend_shapes()
        .with_skin()
        .with_rig_logic(rig_logic)
        .with_add_meshes_to_display_layers(False)
    )

    if gui_path:
        character_config.with_gui_path(gui_path)

    if gui:
        character_config.with_gui(gui)

    if with_attributes_on_root_joint:
        character_config.with_ctrl_attributes_on_root_joint().with_animated_map_attributes_on_root_joint()

    if with_key_frames:
        character_config.with_key_frames()

    character_config.options.add_mesh_name_to_blend_shape_channel_name = (
        add_mesh_name_to_blend_shape_channel_name
    )

    scene_builder = Scene()
    scene_builder.config.with_character(character_config)
    scene_builder.config.with_linear_unit(unit=LinearUnit.cm).with_angle_unit(
        unit=AngleUnit.degree
    )
    return scene_builder.build()

from ..builder.analog_gui import AnalogGui
from ..builder.gui import Gui
from ..builder.joint import Joint as JointBuilder
from ..builder.mesh import Mesh
from ..const.naming import (
    ANALOG_GUI_HOLDER,
    FACIAL_ROOT_JOINT,
    FRM_MULTIPLIERS_NAME,
    GEOMETRY_HOLDER_PREFIX,
    GUI_HOLDER,
    LOD_HOLDER_PREFIX,
    LOD_HOLDER_PREFIX_UPPER,
    RIG_HOLDER_PREFIX,
)
from ..util.additional_assembly_script import AdditionalAssemblyScript
from ..util.rig_logic import RigLogic
from ..util.shader import Shader as ShaderUtil

class CharacterCreator:
    """
    A class used for creating the character in the maya scene

    @param config: The character configuration containing build options

    @param dna: The DNA object read from the DNA file

    @type character_name: str
    @param character_name: The name of the character

    @type meshes: Dict[int, List[MObject]]
    @param meshes: A mapping of lod number to a list of meshes created for that lod
    """

    def __init__(self, config: Character, dna: DNA) -> None:
        self.character_name = self.dna.get_character_name()
        self.meshes: Dict[int, List[MObject]] = {}

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

        cmds.editDisplayLayerMembers(f"{LOD_HOLDER_PREFIX_UPPER}{lod}", 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,
            self.config.modifiers.linear_modifier,
            self.config.modifiers.angle_modifier,
        )
        builder.process()

    def add_joints_to_character(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.options.add_joints:
            logging.info("adding joints to character...")
            joints = self.add_joints()

            if self.config.create_character_node:
                cmds.parent(joints[0].name, self.character_name)

    def create_character_node(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.create_character_node:
            logging.info("building character node...")
            if not cmds.objExists(self.character_name):
                cmds.createNode("transform", n=self.character_name)

    def create_geometry_node(self) -> None:
        """
        Creates a Maya transform which will hold the geometry of the character. If the character configuration
        options have create_character_node set to False, this step will be skipped.
        """

            logging.info("adding geometry node")
            name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}"
            if not cmds.objExists(name):
                cmds.createNode("transform", n=name)
                cmds.parent(name, self.character_name)

    def create_rig_node(self) -> None:
        """
        Creates a Maya transform which will hold the rig of the character. If the character configuration options
        have create_character_node set to False, this step will be skipped.
        """

            logging.info("adding rig node")
            char_name = f"{RIG_HOLDER_PREFIX}{self.character_name}"
            if not cmds.objExists(char_name):
                cmds.createNode("transform", n=char_name)
                cmds.parent(char_name, self.character_name)

    def create_lod_node(self, lod: int, obj_name: str) -> None:
        """
        Creates a Maya transform which will hold the meshes of the character for a given lod.

        @type lod: str
        @param lod: The lod number.

        @type obj_name: str @param obj_name: The full path name of the object in the scene, if it is not found a new
        lod holder will be created.
        """

        if not cmds.objExists(obj_name):
            parent_name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}"
            name = f"{LOD_HOLDER_PREFIX}{lod}"
            cmds.createNode("transform", n=name, p=parent_name)

    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.

        @param lod: The name of the mesh that should be added to a display layer.
        """

        parent_node = (
            f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}|{LOD_HOLDER_PREFIX}{lod}"
        )
        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.

        @returns: The full path of the mesh object in the scene
        """

        return str(Maya.get_element(f"|{mesh_name}").fullPathName())

    def create_ctrl_attributes(self) -> None:
        """
        Creates 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(".")
            cmds.addAttr(
                ctrl_and_attr_names[0],
                longName=ctrl_and_attr_names[1],
                keyable=True,
                attributeType="float",
                minValue=0.0,
                maxValue=1.0,
            )

    def create_frm_attributes(self) -> None:
        """
        Creates and sets the animated map attributes.
        """

        frm_names = self.dna.get_animated_map_names()
        for name in frm_names:
                FRM_MULTIPLIERS_NAME,
                longName=name.replace(".", "_"),
            )

    def create_ctrl_attributes_on_joint(self) -> None:
        """
        Creates control attributes on the root joint from the raw control names.
        """

        if (
            self.config.options.add_ctrl_attributes_on_root_joint
            and self.config.options.add_joints
        ):
            logging.info("adding ctrl attributes on root joint...")
            names = self.dna.get_raw_control_names()
            self.create_attribute_on_joint(names=names)

    def create_animated_map_attributes(self) -> None:
        """
        Creates animated map attributes on the root joint from the animated map names.
        """

        if (
            self.config.options.add_animated_map_attributes_on_root_joint
        ):
            logging.info("adding animated map attributes on root joint...")
            names = self.dna.get_animated_map_names()

    def create_attribute_on_joint(self, names: List[str]) -> None:
        """
        Create attributes from a provided list of names on the facial root joint.

        @type names: List[str]
        @param names: List of names that are added as attributes to the facial root joint.
        """

        for name in names:
                FACIAL_ROOT_JOINT,
            )

    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.options.add_key_frames and self.config.options.add_joints:
            logging.info("setting keyframe on the root joint...")
            cmds.currentTime(0)
            cmds.select(FACIAL_ROOT_JOINT, replace=True)
            cmds.setKeyframe(inTangentType="linear", outTangentType="linear")

    def create_character_meshes(self) -> None:
        """
        Builds the meshes of the character. If specified in the character options they get parented to a created
        character node transform, otherwise the meshes get put to the root level of the scene.
        """

        logging.info("building character meshes...")
        meshes: Dict[int, List[MObject]] = {}
        for lod, meshes_per_lod in enumerate(
            self.dna.get_meshes_by_lods(self.config.meshes)
        ):
            if self.config.create_character_node and meshes_per_lod:
                logging.info(f"building LOD for {lod}")
                obj_name = f"{GEOMETRY_HOLDER_PREFIX}{self.character_name}|{LOD_HOLDER_PREFIX}{lod}"
                self.create_lod_node(lod=lod, obj_name=obj_name)

            meshes[lod] = self.create_meshes(
                lod=lod,
                meshes_per_lod=meshes_per_lod,
            )

    def create_meshes(self, lod: int, meshes_per_lod: List[int]) -> List[MObject]:
        """
        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[MObject] = []
        for mesh_index in meshes_per_lod:
            builder = Mesh(
                character_config=self.config,
                dna=self.dna,
                mesh_index=mesh_index,
            )

            mesh_name = self.dna.get_mesh_name(mesh_index=mesh_index)
            meshes.append(mesh_name)

            if self.config.create_display_layers:
                self.add_mesh_to_display_layer(mesh_name, lod)
                self.attach_mesh_to_lod(mesh_name, lod)
            ShaderUtil.default_lambert_shader(
                mesh_name, self.character_name, self.config.create_character_node
            )

    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_options:
            logging.info("adding gui...")
            builder = Gui(self.config.gui_options)
            self.import_gui(
                gui_path=self.config.gui_options.gui_path, group_name=GUI_HOLDER
            )
            self.create_ctrl_attributes()
            self.create_frm_attributes()

    def add_analog_gui(
        self,
        add_to_character_node: bool = False,
    ) -> None:
        """
        Adds an analog gui according to the specified analog gui options. If none is specified no analog gui will be
        added.

        @type add_to_character_node: bool @param add_to_character_node: A flag that specifies if the imported analog
        gui should be parented to the character node transform (default value is None).
        """

        if self.config.analog_gui_options and self.config.options.add_joints:
            logging.info("adding analog gui...")
            builder = AnalogGui(self.config.analog_gui_options)
                gui_path=self.config.analog_gui_options.gui_path,
                group_name=ANALOG_GUI_HOLDER,
                add_to_character_node=add_to_character_node,
            )

    def import_gui(
        self, gui_path: str, group_name: str, add_to_character_node: bool = False
    ) -> None:
        """
        Imports a gui using the provided parameters.

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

        @type add_to_character_node: bool @param add_to_character_node: A flag representing if the gui holder should
        be attached to the character node or be on the root of the scene (default value is False)
        """

        cmds.file(gui_path, i=True, groupReference=True, groupName=group_name)

        if add_to_character_node:
            cmds.parent(group_name, f"{RIG_HOLDER_PREFIX}{self.character_name}")

    def add_rig_logic_node(self) -> None:
        """
        Creates and adds a rig logic node specified in the character configuration.
        """

        RigLogic.add_rig_logic(config=self.config, character_name=self.character_name)

    def run_additional_assembly_script(self) -> None:
        """
        Runs an additional assembly script if specified in the character configuration.
        """

        AdditionalAssemblyScript.run_additional_assembly_script(config=self.config)

from math import pi

class Conversion:
    """
    A utility class containing methods for converting and calculating units
    """

    def calculate_linear_modifier(
        unit: LinearUnit, dna_unit: LinearUnit = None
    ) -> float:
        """
        A method used for calculating the value (linear modifier) which the transforms will be multiplied with.
        If the chosen unit is not the same as the one from the DNA file a multiplier will be calculated. Otherwise it returns 1.

        @type unit: LinearUnit
        @param unit: Can be m (meter) and cm (centimeter).

        @type dna_unit: LinearUnit
        @param dna_unit: The linear unit specified in the DNA file (default value is None).

        @rtype: float
        @returns: A multiplier needed for conversion between different units.
        """

        if dna_unit is None:
            return 1

        if unit != dna_unit:
            if unit == LinearUnit.m:
                return 0.01
            if unit == LinearUnit.cm:
                return 100
        return 1

    def calculate_angle_modifier(unit: AngleUnit, dna_unit: AngleUnit = None) -> float:
        """
        A method used for calculating the value (angle modifier) which the angles will be multiplied with. If the
        chosen unit is not the same as the one from the DNA file a multiplier will be calculated. Otherwise,
        it returns 1.

        @type unit: AngleUnit
        @param unit: Can be degree and radian.

        @type dna_unit: AngleUnit
        @param dna_unit: The angle unit specified in the DNA file (default value is None).

        """

            return 1

            if unit == AngleUnit.degree:
                return 180 / pi
            if unit == AngleUnit.radian:
                return pi / 180
        return 1

    def get_linear_unit_from_int(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(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}")

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

class Import:
    """
    A utility class containing methods for reading outside python files.
    """

    def source_py_file(name: str, path: str) -> Optional[ModuleType]:
        """
        Used for loading a python file, used for additional assembly script.

        @param name: The name of the module.

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

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

class Maya:
    """A utility class used for interfacing with maya transforms"""

    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 = OBJECT) -> 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 = OBJECT
    ) -> 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 ..builder.scene import Scene as SceneBuilder
from ..config.character import BuildOptions, Character

def get_mesh_names(dna: DNA) -> List[str]:
    """
    Gets the list of mesh names contained in the DNA file.

    @rtype: List[str]
    @returns: The list of mesh names contained in the DNA file
    """

    return dna.get_mesh_names()

def get_mesh_lods(dna: DNA) -> List[List[int]]:
    """
    Gets the list of list of mesh indices grouped by the lod number.

    @param dna: Instance of DNA.

    @rtype: List[List[int]]
    @returns: The list of list of mesh indices grouped by the lod number
    """

    result: List[List[int]] = []

    for lod in range(dna.get_lod_count()):
        mesh_indices = []
        for mesh_index in dna.get_mesh_indices_for_lod(lod):
            mesh_indices.append(mesh_index)
        result.append(mesh_indices)

    return result

def create_build_options(
    add_joints: bool = False,
    add_blend_shapes: bool = False,
    add_skin: bool = False,
    add_ctrl_attributes_on_root_joint: bool = False,
    add_animated_map_attributes_on_root_joint: bool = False,
    add_mesh_name_to_blend_shape_channel_name: bool = False,
    add_key_frames: bool = False,
) -> BuildOptions:
    """
    Creates the build options object used in the character building process.

    @param add_joints: Represents if joints should be added

    @param add_blend_shapes: Represents if blend shapes should be added

    @param add_skin: Represents if skin should be added

    @param add_ctrl_attributes_on_root_joint: Represents if gui control attributes should be added on the root joint

    @param add_animated_map_attributes_on_root_joint: Represents if animated map attributes should be added on the root joint

    @rtype: BuildOptions
    @returns: The created build options object
    """

    return BuildOptions(
        add_joints=add_joints,
        add_blend_shapes=add_blend_shapes,
        add_skin=add_skin,
        add_ctrl_attributes_on_root_joint=add_ctrl_attributes_on_root_joint,
        add_animated_map_attributes_on_root_joint=add_animated_map_attributes_on_root_joint,
        add_mesh_name_to_blend_shape_channel_name=add_mesh_name_to_blend_shape_channel_name,
        add_key_frames=add_key_frames,
    )

def build_meshes(
    dna: DNA,
    options: BuildOptions = None,
    group_by_lod: bool = False,
    mesh_list: List[int] = None,
    lod_list: List[int] = None,
    create_new_scene: bool = False,
) -> List[str]:
    """
    Starts the mesh building process with the provided parameters.

    @param options: The build options used in the build process

    @type group_by_lod: bool
    @param group_by_lod: Represents if the created meshes should be added to a holding transform representing the lod

    @type mesh_list: List[int]
    @param mesh_list: List of mesh indices that should be added.

    @type lod_list: List[int]
    @param lod_list: List of lods from which all containing meshes should be added.

    @param create_new_scene: Represents if a new scene should be created in the build process.

    @returns: The list of full paths of the created meshes in the maya scene
    """

    options = options or BuildOptions()

    meshes: List[int] = []
    meshes_by_lod = get_mesh_lods(dna)

    if lod_list:
        for lod in lod_list:
            meshes.extend(meshes_by_lod[lod])

    if mesh_list:
        meshes.extend(mesh_list)

    if mesh_list is None and lod_list is None:
        meshes = [mesh_index for meshes in meshes_by_lod for mesh_index in meshes]

        .with_dna(dna=dna)
        .with_character_node(value=group_by_lod)
        .with_meshes(meshes=meshes)
    )
    character_config.options = options

    config = (
        SceneConfig()
        .with_character(character=character_config)
        .with_create_new_scene(create_new_scene)
        .with_linear_unit(unit=LinearUnit.cm)
        .with_angle_unit(unit=AngleUnit.degree)
    )
    scene_build_result = SceneBuilder(config).build()

    result = []
    for character_result in scene_build_result.character_results:
        for nodes_by_lod in character_result.nodes.values():
            for node in nodes_by_lod:
                result.append(node)

def get_mesh_indices_containing_string(
    mesh_name_part: str, lod: int, dna: DNA
) -> List[int]:
    """
    Gets the mesh indices containing a search string.

    @type mesh_name_part: str
    @param mesh_name_part: The string that is searched for in the meshes

    @type lod: int
    @param lod: The lod in which the mesh names will be searched for the result

    @rtype: List[int]
    @returns: The list of mesh indices which contain the mesh_name_part in their name
    """

    if not 0 <= lod < dna.get_lod_count():
        raise DNAViewerError(f"Lod {lod} does not exist")

    result = []
    for mesh_index in dna.get_mesh_indices_for_lod(lod):
        if mesh_name_part.lower() in dna.get_mesh_name(mesh_index).lower():
            result.append(mesh_index)

    if not result:
        raise DNAViewerError(f"No results for {mesh_name_part}")

def get_mesh_index(mesh_name: str, lod: int, dna: DNA) -> Optional[int]:
    """
    Gets a mesh index with the given search string, returns the first one if multiple are found.

    @type mesh_name: str
    @param mesh_name: The string that is searched for in the meshes

    """

    try:
        results = get_mesh_indices_containing_string(mesh_name, lod, dna)
        if results:
                "Multiple meshes found, first result is returned, you should probably try a more specific mesh name"
            )
        return results[0]
    except DNAViewerError as e:
        logging.error(e)
    return None

from maya.api.OpenMaya import MDagModifier, MFnDagNode, MFnMesh, MPoint

from ..config.mesh import Mesh
    BLEND_SHAPE_GROUP_PREFIX,
    BLEND_SHAPE_NAME_POSTFIX,
    BLEND_SHAPE_NAMING,
    DERIVED_MESH_NAME,
    MESH_NAME,
)
from ..model.mesh import Mesh as MayaMeshModel

class MeshBlendShape:
    """
    A utility class used for interacting with blend shapes
    """

    def create_all_derived_meshes(
        config: Mesh,
        dna: DNA,
        data: MayaMeshModel,
        fn_mesh: MFnMesh,
        dag_modifier: MDagModifier,
        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 config: Mesh
        @param config: Mesh configuration from the DNA.

        @type data: MayaMeshModel
        @param data: An object that stores values that get passed around different methods.

        @type fn_mesh: MFnMesh
        @param fn_mesh: Used for creating and manipulating maya mesh objects.

        @type dag_modifier: MDagModifier
        @param dag_modifier: Used for manipulating maya objects.

        @type add_mesh_name_to_blend_shape_channel_name: bool
        @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mash name of blend shape channel is added to name when creating it
        """

        logging.info("building derived meshes...")

        group: str = cmds.group(
            empty=True,
            name=f"{BLEND_SHAPE_GROUP_PREFIX}{dna.get_mesh_name(config.mesh_index)}",
        )

        data.derived_mesh_names = []
        blend_shapes = dna.get_blend_shapes(config.mesh_index)
        for blend_shape_target_index, blend_shape in enumerate(blend_shapes):
                logging.info(f"\t{blend_shape_target_index + 1} / {len(blend_shapes)}")

            MeshBlendShape._create_derived_mesh(
                config,
                dna,
                data,
                blend_shape_target_index,
                blend_shape.channel,
                group,
                fn_mesh,
                dag_modifier,
            )

        if len(blend_shapes) % BLEND_SHAPE_PRINT_RANGE != 0:
            logging.info(f"\t{len(blend_shapes)} / {len(blend_shapes)}")

        cmds.setAttr(f"{group}.visibility", 0)

    def _create_derived_mesh(
        dna: DNA,
        blend_shape_target_index: int,
        blend_shape_channel: int,
        group: str,
    ) -> 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 = MeshNeutral.get_vertex_positions_from_dna_vertex_positions(
            config=config, data=data
        )

        zipped_deltas = dna.get_blend_shape_target_deltas_with_vertex_id(
            config.mesh_index, blend_shape_target_index
        )
        for zipped_delta in zipped_deltas:
            delta: Point3 = zipped_delta[1]
            new_vert_layout[zipped_delta[0]] += MPoint(
                config.linear_modifier * delta.x,
                config.linear_modifier * delta.y,
                config.linear_modifier * delta.z,
            )

        new_mesh = fn_mesh.create(
            new_vert_layout, data.polygon_faces, data.polygon_connects
        )
        derived_name = dna.get_blend_shape_name(blend_shape_channel)
        name = (
            f"{dna.geometry.meshes[config.mesh_index].name}__{derived_name}"
            if add_mesh_name_to_blend_shape_channel_name
            else derived_name
        )
        dag_modifier.renameNode(new_mesh, name)
        dag_modifier.doIt()

        dag = MFnDagNode(Maya.get_element(group))
        dag.addChild(new_mesh)

        data.derived_mesh_names.append(name)

    def create_blend_shape_node(
        mesh_name: str, derived_mesh_names: List[str], rename: bool = False
    ) -> None:
        """
        Creates a blend shape node.

        @param mesh_name: The name of the mesh.

        @type derived_mesh_names: List[str]
        @param derived_mesh_names: List of the names that will end up as blend shapes added to the mesh.

        @type rename: bool
        @param rename: A flag representing if the name should be changed to a blend shape naming convention.
        """

        nodes = []
        for derived_mesh_name in derived_mesh_names:
            if rename:
                name = BLEND_SHAPE_NAMING.replace(MESH_NAME, mesh_name).replace(
                    DERIVED_MESH_NAME, derived_mesh_name
                )
            else:
                name = derived_mesh_name

            nodes.append(name)

        cmds.select(nodes, replace=True)

        cmds.select(mesh_name, add=True)
        cmds.blendShape(name=f"{mesh_name}{BLEND_SHAPE_NAME_POSTFIX}")
        cmds.delete(f"{BLEND_SHAPE_GROUP_PREFIX}{mesh_name}")

from ..util.mesh import get_mesh_indices_containing_string, get_mesh_lods

def print_meshes(dna: DNA) -> None:
    """
    Prints the mesh names grouped by lod.

    """

    mesh_indices_by_lods = get_mesh_lods(dna)

    for lod, mesh_indices in enumerate(mesh_indices_by_lods):
        logging.info(f"LOD_{lod}")
        for mesh_index in mesh_indices:
            logging.info(f"\t{mesh_index}->{dna.get_mesh_name(mesh_index)}")

def print_mesh_indices_containing_string(
) -> None:
    """
    Prints the mesh indices that contain a search string.

    @param mesh_name_part: The search string.

    @param lod: The lod where the search will be done.

    """

    try:
        results = get_mesh_indices_containing_string(mesh_name_part, lod, dna)
        for mesh_index in results:
            logging.info(f"\t{dna.get_mesh_name(mesh_index)}->{mesh_index}")
        raise DNAViewerError(e) from e
    except Exception as e:
        logging.error(f"Unhandled exception, {e}")

from maya.api.OpenMaya import MDagModifier, MFnMesh, MObject, MPoint

class MeshNeutral:
    """
    A utility class used for creating and interacting with meshes
    """

    def get_vertex_positions_from_dna_vertex_positions(
        config: MeshConfig, data: MeshModel
    ) -> List[MPoint]:
        """
        Gets a list of points that represent the vertex positions.

        @type config: MeshConfig

        @type data: MeshModel

        @rtype: List[MPoint]
        @returns: List of maya point objects.
        """

        vertex_positions = []
        for position in data.dna_vertex_positions:
            vertex_positions.append(
                MPoint(
                    config.linear_modifier * position.x,
                    config.linear_modifier * position.y,
                    config.linear_modifier * position.z,
                )
            )
        return vertex_positions

    def prepare_mesh(config: MeshConfig, dna: DNA, data: MeshModel) -> None:
        """

        """

        logging.info("==============================")
        logging.info(f"building mesh with mesh_index: {config.mesh_index}")

        data.dna_vertex_positions = dna.get_vertex_positions_for_mesh_index(
            config.mesh_index
        )
        data.dna_vertex_layout_positions = (
            dna.get_vertex_layout_positions_for_mesh_index(config.mesh_index)
        )

        (
            data.polygon_faces,
            data.polygon_connects,
        ) = dna.get_polygon_faces_and_connects(config.mesh_index)

    def create_mesh_object(
    ) -> Tuple[MFnMesh, MObject]:
        """

        @rtype: Tuple[MFnMesh, MObject]
        @returns: Maya objects representing maya mesh functions and the created maya mesh object.
        """

        fn_mesh = MFnMesh()

        mesh_object = fn_mesh.create(
            MeshNeutral.get_vertex_positions_from_dna_vertex_positions(config, data),
        )

        return fn_mesh, mesh_object

    def rename_mesh(config: MeshConfig, dna: DNA, mesh_object: MObject) -> MDagModifier:
        """
        Renames the initial mesh object that was created to the name from the configuration.

        @type mesh_object: MObject

        @rtype: Tuple[MDagModifier]
        @returns: Maya object representing the dag modifier.
        """

        mesh_name = dna.get_mesh_name(config.mesh_index)
        logging.info(f"naming mesh to: {mesh_name}")

        dag_modifier = MDagModifier()
        dag_modifier.renameNode(mesh_object, mesh_name)
        return dag_modifier

    def get_texture_data(
        mesh_index: int, dna: DNA
    ) -> Tuple[List[float], List[float], List[int]]:
        """
        Gets the data needed for the creation of textures.

        @param dna: Instance of DNA.

        @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 = dna.get_vertex_texture_coordinates_for_mesh(mesh_index)
        dna_faces = dna.get_faces(mesh_index)

        coordinate_indices = []
        for layout_id in range(len(dna.get_layouts_for_mesh_index(mesh_index))):
            coordinate_indices.append(
                dna.get_texture_coordinate_index(mesh_index, layout_id)
            )

        texture_coordinate_us = []
        texture_coordinate_vs = []
        texture_coordinate_indices = []

        index_counter = 0

                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_texture_coordinates(
        config: MeshConfig, dna: DNA, data: MeshModel, fn_mesh: MFnMesh
    ) -> None:
        """
        Method for adding texture coordinates.

        @params fn_mesh: Object used for manipulating maya mesh objects.
        """

        logging.info("adding texture coordinates...")

        (
            texture_coordinate_us,
            texture_coordinate_vs,
            texture_coordinate_indices,
        ) = MeshNeutral.get_texture_data(config.mesh_index, dna)

        fn_mesh.setUVs(texture_coordinate_us, texture_coordinate_vs)
        fn_mesh.assignUVs(data.polygon_faces, texture_coordinate_indices)

        cmds.select(mesh_name, replace=True)
        cmds.polyMergeUV(mesh_name, distance=0.01, constructionHistory=False)

from maya import cmds, mel
from maya.api.OpenMaya import MFnMesh, MSpace, MVector

class MeshNormals:
    """
    A utility class used for adding normals to a mesh
    """

    def add_normals(
        config: MeshConfig,
        dna: DNA,
        data: MeshModel,
        space: Optional[MSpace] = MSpace.kObject,
    ) -> None:
        """
        Adds normals to the mesh node.

        @type space: Optional[space]
        @params space: The maya space used for setting the face vertex normals (defaults to MSpace.kObject)
        """

        data.vertex_normals = dna.get_vertex_normals_for_mesh(config.mesh_index)
        data.dna_vertex_layout_normals = []
        for layout in dna.get_layouts_for_mesh_index(config.mesh_index):
            data.dna_vertex_layout_normals.append(
                dna.get_normal_for_mesh_index_and_layout(config.mesh_index, layout)
            )

        normal_array = []
        face_array = []
        vertex_array = []

        dna_faces = dna.get_faces(config.mesh_index)
        for face_id, vertices_layout_index_array in enumerate(dna_faces):
            for vertex_layout_index in vertices_layout_index_array:
                vertex_normal_raw = data.dna_vertex_layout_normals[vertex_layout_index]
                vertex_normal = MVector(
                    vertex_normal_raw.x, vertex_normal_raw.y, vertex_normal_raw.z
                )
                normal_array.append(vertex_normal)
                face_array.append(face_id)
                vertex_id = data.dna_vertex_layout_positions[vertex_layout_index]
                vertex_array.append(vertex_id)

        fn_mesh.setFaceVertexNormals(normal_array, face_array, vertex_array, space)

        mesh_node = MFnMesh(Maya.get_element(dna.get_mesh_name(config.mesh_index)))
        mesh_node.unlockVertexNormals(range(len(data.dna_vertex_positions)))
        cmds.polySoftEdge(mesh_node.name(), a=180, ch=False)
        mel.eval("BakeAllNonDefHistory;")

from ..const.naming import SKIN_CLUSTER_AFFIX
from ..const.printing import SKIN_WEIGHT_PRINT_RANGE

class MeshSkin:
    """
    A utility class used for interacting and adding skin clusters to a mesh
    """

    def prepare_joints(dna: DNA, mesh_index: int) -> Tuple[List[int], List[str]]:
        """
        Gets the joint indices and names needed for the given mesh.

        @param mesh_index: The index of the mesh.

        @type joints: List[Joint]
        @param joints: List of all joints from which some of them are selected as a result.

        @rtype: Tuple[List[int], List[str]]
        @returns: The tuple containing the list of joint indices and the list of joint names.
        """

        joints = dna.read_all_neutral_joints()
        joints_temp: List[int] = []
        joint_indices = dna.get_all_skin_weights_joint_indices_for_mesh(mesh_index)

        joint_ids = []
        if any(joint_indices):
            for row in joint_indices:
                for column in row:
                    joints_temp.append(column)

            joint_ids = list(set(joints_temp))
            joint_ids.sort()
        else:
            lod = dna.get_lowest_lod_containing_meshes([mesh_index])
            if lod:
                joint_ids = dna.get_joint_indices_for_lod(lod)

        joint_names = []
        for joint_id in joint_ids:
            joint_names.append(joints[joint_id].name)
        return joint_ids, joint_names

    def add_skin_cluster(
        dna: DNA, mesh_index: int, 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.

        @param joints: List of joints used for adding the skin cluster.
        """

        logging.info("adding skin cluster...")
        maximum_influences = dna.get_maximum_influence_per_vertex(mesh_index)

        cmds.select(joint_names[0], replace=True)

        skin_cluster = cmds.skinCluster(
            toSelectedBones=True,
            name=f"{mesh_name}_{SKIN_CLUSTER_AFFIX}",
            maximumInfluences=maximum_influences,
            skinMethod=0,
            obeyMaxInfluences=True,
        )
        if joint_names:
            cmds.skinCluster(
                skin_cluster, edit=True, addInfluence=joint_names[1:], weight=0
            )

    def set_skin_weights(
        dna: DNA, mesh_index: int, 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("setting skin weights...")
        skin_weights = dna.get_skin_weight_matrix_for_mesh(mesh_index)

        # import skin weights
        temp_str = f"{mesh_name}_{SKIN_CLUSTER_AFFIX}.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)}")

import dna

class Reader:
    """
    A utility class used for reading DNA files
    """

    def create_stream_reader(dna_path: str) -> dna.BinaryStreamReader:
        """
        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.BinaryStreamReader
        @returns: The stream reader needed for reading values from the DNA file
        """

        stream = dna.FileStream(
            dna_path, dna.FileStream.AccessMode_Read, dna.FileStream.OpenMode_Binary
        )

        reader = dna.BinaryStreamReader(stream, dna.DataLayer_All)
        reader.read()
        if not dna.Status.isOk():
            status = dna.Status.get()
            raise RuntimeError(f"Error loading DNA: {status.message}")
        return reader

from ..reader.geometry import Geometry

def get_geometry_reader(mesh_index: int, dna_path: str = None) -> Geometry:
    """
    Gets a Geometry reader for a given mesh index and a dna_file path.

    @param dna_path: The path of the DNA file

    @rtype: Geometry
    @returns: A geometry reader stored in the Reference class object for the given mesh index and DNA
    """

    return Reference.get_geometry_reader(mesh_index=mesh_index, dna_path=dna_path)

def set_geometry_reader(dna_path: str, geometry_reader: Geometry) -> None:
    """
    Adds a new geometry reader to the the references mapped to the DNA file path.

    @type geometry_reader: Geometry
    @param geometry_reader: The geometry stream reader
    """

    return Reference.set_geometry_reader(
        dna_path=dna_path, geometry_reader=geometry_reader
    )

class Reference:
    """
    A class used for storing and retrieving geometry readers.
    """

    geometry_readers: Dict[str, List[Geometry]] = {}

    def get_geometry_reader(mesh_index: int, dna_path: str = None) -> Geometry:
        """
        Gets a Geometry reader for a given mesh index and a dna_file path.

        @rtype: Geometry
        @returns: A stored geometry reader for the given mesh index and DNA
        """

        if dna_path is None:  # defaults to first one
            geometry_readers_for_dna = next(iter(Reference.geometry_readers.values()))
            return geometry_readers_for_dna[mesh_index]
        return Reference.geometry_readers[dna_path][mesh_index]

    def set_geometry_reader(dna_path: str, geometry_reader: Geometry) -> None:
        """
        Adds a mapping value of a geometry reader to the DNA file path.

        @type geometry_reader: Geometry
        @param geometry_reader: The geometry reader that should be added to the mapping.
        """

        if dna_path not in Reference.geometry_readers:
            Reference.geometry_readers[dna_path] = []

        Reference.geometry_readers[dna_path].append(geometry_reader)

from maya import mel
from maya.cmds import loadPlugin

from ..builder.rig_logic import RigLogic as RigLogicBuilder
from ..const.naming import RIG_LOGIC_PREFIX

    """
    A utility class for adding rig logic to the character
    """

    def add_rig_logic(config: Character, character_name: str) -> None:
        """
        Adds a rig logic node if the option is specified in the character configuration.

        @param config: The character configuration.

        @type character_name: str
        @param character_name: The name of the character.
        """

        if config.rig_logic_config:
            logging.info("adding rig logic...")
            try:
                loadPlugin("embeddedRL4.mll")
                config.rig_logic_config.with_name(
                    f"{RIG_LOGIC_PREFIX}{character_name}"
                ).with_dna_file_path(config.dna.path)
                mel_command = RigLogicBuilder(config.rig_logic_config).build_command()
                logging.info(f"mel command: {mel_command}")
                mel.eval(mel_command)
            except Exception as e:
                logging.error(
                    "The procedure needed for assembling the rig logic was not found, the plugin needed for this might not be loaded."
                )
                    f"Something went wrong, skipping adding the rig logic... Reason: {e}"
                ) from e

from os import makedirs

from ..config.scene import SaveOptions
from ..config.units import Unit

class SceneBuilder:
    """
    A utility class containing methods needed for building the maya scene
    """

    LOD_PREFIX = "LOD_"

    def add_display_layers(display_layers_needed: List[int]) -> None:
        """
        Creates a display layer for each value of the given list if it does not exist

        @type display_layers_needed: List[int]
        @param display_layers_needed: A list of lod numbers that need a display layer created for them
        """

        for lod in display_layers_needed:
            if not cmds.objExists(f"{SceneBuilder.LOD_PREFIX}{lod}"):
                cmds.createDisplayLayer(name=f"{SceneBuilder.LOD_PREFIX}{lod}")

    def save_scene(save_options: SaveOptions) -> None:
        """
        Saves the scene if save options are provided.

        @type save_options: SaveOptions
        @param save_options: The part configuration concerning how the scene should be saved
        """

        path = save_options.get_path()
        logging.info(f"Saving scene to: {path}")
        makedirs(Path(path).parent, exist_ok=True)
        cmds.file(rename=path)
        if save_options.extension == "ma":
            cmds.file(save=True, type="mayaAscii")
        elif save_options.extension == "mb":
            cmds.file(save=True, type="mayaBinary")
        else:
            cmds.file(save=True)

    def set_units(dna: DNA, units: Unit) -> None:
        """
        Sets the linear and angle units of the scene if provided as a parameter, otherwise defaults the values provided in the DNA file.

        @type units: Unit
        @param units: The configuration that contains the linear and the angle unit.
        """

        linear_unit = units.linear_unit if units.linear_unit else dna.get_linear_unit()
        angle_unit = units.angle_unit if units.angle_unit else dna.get_angle_unit()

        cmds.currentUnit(linear=linear_unit.name, angle=angle_unit.name)

    def new_scene() -> None:
        """
        Creates a new maya scene.
        """

        cmds.file(force=True, new=True)

class Shader:
    def default_lambert_shader(
        mesh_name: str, character_name: str = None, create_character_node: bool = False
    ) -> None:
        try:
            if create_character_node:
                names = cmds.ls(f"*|{mesh_name}", l=True)
                name = [
                    item for item in names if item.startswith(f"|{character_name}")
                ][0]
                cmds.select(name, r=True)
            else:
                cmds.select(mesh_name, r=True)

            mel.eval("sets -e -forceElement initialShadingGroup")

                f"Couldn't set lambert shader for mesh {mesh_name}. Reason: {e}"
            )
            raise DNAViewerError(e) from e

from typing import List, Tuple, Union

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

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 create(skin_cluster: MFnSkinCluster, mesh_name: str) -> "MayaSkinWeights":
        """
        Creates a new instance of the class object.

        @type skin_cluster: MFnSkinCluster
        @param skin_cluster: Functionalities of maya skin clusters

        @param mesh_name: The name of the mesh
        """

        skin_weights = MayaSkinWeights()

        skin_weights.no_of_influences = cmds.skinCluster(
            skin_cluster.name(), q=True, mi=True
        )

        skin_weights.skinning_method = cmds.skinCluster(
            skin_cluster.name(), q=True, sm=True
        )

        skin_weights.joints = MayaSkinWeights.get_skin_cluster_influence(skin_cluster)

        skin_weights.vertices_info = MayaSkinWeights.get_skin_weights_for_mesh_name(
            skin_cluster, mesh_name
        )

        return skin_weights

    def get_skin_cluster_influence(
        skin_cluster: MFnSkinCluster,
    ) -> List[str]:
        """
        Gets a list of joint names that are influences to the skin cluster.

        @param skin_cluster: The functionalities of a maya skin cluster object

        @rtype: List[str]
        @returns: The list if names of the joints that influence the skin cluster
        """

        influences: List[str] = cmds.skinCluster(skin_cluster.name(), q=True, inf=True)
        if influences and not isinstance(influences[0], str):
            influences = [obj.name() for obj in influences]
        return influences

    def get_skin_weights_for_mesh_name(
        mesh_name: str,
    ) -> List[List[Union[int, float]]]:
        """
        Gets the skin weights concerning the given 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

class SkinWeightsMaya:
    """
    A class used for setting and interacting with skin weights in maya.
    """

    def get_skin_weights_data(self, mesh_name: str) -> Tuple[MFnMesh, MFnSkinCluster]:
        """
        Gets the maya objects that manipulate the mesh node and the skin cluster for a given mesh name.

        @rtype: Tuple[MFnMesh, MFnSkinCluster]
        @returns: The maya object that manipulate the mesh node and the skin cluster for a given mesh name.
        """

        skin_cluster = None
        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))
        if not skin_cluster:
            raise DNAViewerError(f"Unable to find skin for given mesh: {mesh_name}")
        return mesh_node, skin_cluster

    def get_skin_weights_from_scene(self, 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 = self.get_skin_weights_data(mesh_name)

        skin_weights = MayaSkinWeights.create(skin_cluster, mesh_name)

    def get_file_joint_mappings(
        self, 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

        @param skin_cluster: An object for working with functions concerning a skin cluster in maya

        @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(
        self, 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 = self.get_skin_weights_data(mesh_name)

        file_joint_mapping = self.get_file_joint_mappings(skin_weights, skin_cluster)

        self.import_skin_weights(
            skin_cluster, mesh_node, skin_weights, file_joint_mapping
        )

        logging.info("Set skin weights ended.")

    def import_skin_weights(
        self,
        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):
                    f"{vtx_str}{str(file_joint_mapping[int(vtx_info[i])])}]",
                    vtx_info[i + 1],
                )