import logging
import os
import webbrowser
from typing import Callable, List
from maya import cmds
from maya.cmds import confirmDialog
from PySide2.QtCore import QCoreApplication, Qt
from PySide2.QtWidgets import (
    QApplication,
    QCheckBox,
    QGridLayout,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QTabWidget,
    QTreeWidget,
    QTreeWidgetItem,
    QTreeWidgetItemIterator,
    QVBoxLayout,
    QWidget,
)
from .. import DNA, build_rig
from ..builder.config import RigConfig
from ..dnalib.layer import Layer
from ..version import __version__
from .widgets import FileChooser, QHLine
[docs]def show() -> None:
    DnaViewerWindow.show_window() 
WINDOW_OBJECT = "dnaviewer"
WINDOW_TITLE = "DNA Viewer"
HELP_URL = "https://epicgames.github.io/MetaHuman-DNA-Calibration/"
SPACING = 6
WINDOW_SIZE_WIDTH_MIN = 800
WINDOW_SIZE_WIDTH_MAX = 1200
WINDOW_SIZE_HEIGHT_MIN = 800
WINDOW_SIZE_HEIGHT_MAX = 1000
MARGIN_LEFT = 8
MARGIN_TOP = 8
MARGIN_RIGHT = 8
MARGIN_BOTTOM = 8
MARGIN_HEADER_LEFT = 0
MARGIN_HEADER_TOP = 0
MARGIN_HEADER_RIGHT = 0
MARGIN_HEADER_BOTTOM = 0
MARGIN_BODY_LEFT = 0
MARGIN_BODY_TOP = 0
MARGIN_BODY_RIGHT = 0
[docs]class MeshTreeList(QWidget):
    """
    A custom widget that lists out meshes with checkboxes next to them, so these meshes can be selected to be processed. The meshes are grouped by LOD
    @type mesh_tree: QWidget
    @param mesh_tree: The widget that contains the meshes to be selected in a tree list
    """
    def __init__(self, main_window: "DnaViewerWindow") -> None:
        super().__init__()
        self.main_window = main_window
        label = QLabel("Meshes:")
        self.mesh_tree = self.create_mesh_tree()
        layout = QGridLayout()
        layout.addWidget(self.mesh_tree, 0, 0, 4, 1)
        layout.setContentsMargins(
            MARGIN_BODY_LEFT,
            MARGIN_BODY_TOP,
            MARGIN_BODY_RIGHT,
            MARGIN_BOTTOM,
        )
        layout_holder = QVBoxLayout()
        layout_holder.addWidget(label)
        layout_holder.addLayout(layout)
        layout_holder.setContentsMargins(
            MARGIN_BODY_LEFT,
            MARGIN_BODY_TOP,
            MARGIN_BODY_RIGHT,
            MARGIN_BOTTOM,
        )
        self.btn_select_all = QPushButton("Select all meshes")
        self.btn_select_all.setEnabled(False)
        self.btn_select_all.clicked.connect(self.select_all)
        layout_holder.addWidget(self.btn_select_all)
        self.btn_deselect_all = QPushButton("Deselect all meshes")
        self.btn_deselect_all.setEnabled(False)
        self.btn_deselect_all.clicked.connect(self.deselect_all)
        layout_holder.addWidget(self.btn_deselect_all)
        self.setLayout(layout_holder)
[docs]    def create_mesh_tree(self) -> QWidget:
        """
        Creates the mesh tree list widget
        @rtype: QWidget
        @returns: The created widget
        """
        mesh_tree = QTreeWidget()
        mesh_tree.setHeaderHidden(True)
        mesh_tree.itemChanged.connect(self.tree_item_changed)
        mesh_tree.setStyleSheet("background-color: #505050")
        mesh_tree.setToolTip("Select mesh or meshes to add to rig")
        return mesh_tree 
[docs]    def fill_mesh_list(
        self, lod_count: int, names: List[str], indices_names: List[List[int]]
    ) -> None:
        """
        Fills the mesh list with the meshes, and groups them by lods
        @type lod_count: int
        @param lod_count: The LOD count
        @type names: List[str]
        @param names: The names and indices of all the meshes
        @type indices_names: List[List[int]
        @param indices_names: The names and indices of all the meshes
        """
        self.mesh_tree.clear()
        for i in range(lod_count):
            parent = QTreeWidgetItem(self.mesh_tree)
            parent.setText(0, f"LOD {i}")
            parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
            meshes_in_lod = indices_names[i]
            for mesh_index in meshes_in_lod:
                child = QTreeWidgetItem(parent)
                child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
                child.setText(0, f"{names[mesh_index]}")
                child.setCheckState(0, Qt.Unchecked)
            self.mesh_tree.setItemExpanded(parent, True) 
[docs]    def get_selected_meshes(self) -> List[int]:
        """
        Gets the selected meshes from the tree widget
        @rtype: List[int]
        @returns: The list of mesh indices that are selected
        """
        meshes = []
        iterator = QTreeWidgetItemIterator(
            self.mesh_tree, QTreeWidgetItemIterator.Checked
        )
        while iterator.value():
            item = iterator.value()
            mesh_name = item.text(0)
            mesh_index = self.main_window.dna.get_mesh_id_from_mesh_name(mesh_name)
            if mesh_index is not None:
                meshes.append(mesh_index)
            iterator += 1
        return meshes 
[docs]    def select_all(self) -> None:
        """
        Selects all meshes in the tree widget
        """
        self.iterate_over_items(Qt.Checked) 
[docs]    def deselect_all(self) -> None:
        """
        Deselects all meshes in the tree widget
        """
        self.iterate_over_items(Qt.Unchecked) 
[docs]    def iterate_over_items(self, state: Qt.CheckState) -> None:
        """
        Deselects all meshes in the tree widget
        """
        item = self.mesh_tree.invisibleRootItem()
        for index in range(item.childCount()):
            child = item.child(index)
            child.setCheckState(0, state) 
[docs]    def tree_item_changed(self) -> None:
        """The method that gets called when a tree item gets its value changed"""
        meshes = self.get_selected_meshes()
        if meshes:
            self.main_window.skin_cb.setEnabled(self.main_window.joints_cb.checkState())
            self.main_window.blend_shapes_cb.setEnabled(True)
            self.main_window.process_btn.setEnabled(True)
            self.main_window.rig_logic_cb.setEnabled(False)
            if len(meshes) == self.main_window.dna.get_mesh_count():
                self.main_window.rig_logic_cb.setEnabled(
                    self.main_window.joints_cb.checkState()
                    and self.main_window.blend_shapes_cb.checkState()
                    and self.main_window.skin_cb.checkState()
                    and self.main_window.select_gui_path.get_file_path() is not None
                    and self.main_window.select_analog_gui_path.get_file_path()
                    is not None
                    and self.main_window.select_aas_path.get_file_path() is not None
                )
        else:
            self.main_window.skin_cb.setEnabled(False)
            self.main_window.blend_shapes_cb.setEnabled(False)
            self.main_window.process_btn.setEnabled(
                self.main_window.joints_cb.checkState()
            )  
[docs]class DnaViewerWindow(QMainWindow):
    """
    UI Window
    Attributes
    ----------
    @type select_dna_path: FileChooser
    @param select_dna_path: The FileChooser widget for getting the DNA path
    @type load_dna_btn: QPushButton
    @param load_dna_btn: The button that starts loading in the DNA
    @type mesh_tree_list: QWidget
    @param mesh_tree_list: The widget that contains the meshes to be selected in a tree list
    @type joints_cb: QCheckBox
    @param joints_cb: The checkbox that represents if joints should be added
    @type blend_shapes_cb: QCheckBox
    @param blend_shapes_cb: The checkbox that represents if blend shapes should be added
    @type skin_cb: QCheckBox
    @param skin_cb: The checkbox that represents if skin should be added
    @type rig_logic_cb: QCheckBox
    @param rig_logic_cb: The checkbox that represents if rig logic should be added
    @type ctrl_attributes_on_root_joint_cb: QCheckBox
    @param ctrl_attributes_on_root_joint_cb: The checkbox that represents if control attributes on joint should be added
    @type animated_map_attributes_on_root_joint_cb: QCheckBox
    @param animated_map_attributes_on_root_joint_cb: The checkbox that represents if animated maps attributes on root joint should be added
    @type mesh_name_to_blend_shape_channel_name_cb: QCheckBox
    @param mesh_name_to_blend_shape_channel_name_cb: The checkbox that represents if mesh names to blend shapes channel name should be added
    @type key_frames_cb: QCheckBox
    @param key_frames_cb: The checkbox that represents if key frames should be added
    @type select_gui_path: FileChooser
    @param select_gui_path: The FileChooser widget for getting the gui path
    @type select_analog_gui_path: FileChooser
    @param select_analog_gui_path: The FileChooser widget for getting the analog gui path
    @type select_aas_path: FileChooser
    @param select_aas_path: The FileChooser widget for getting the additional assemble script path
    @type process_btn: QPushButton
    @param process_btn: The button that starts creating the scene and character
    @type progress_bar: QProgressBar
    @param progress_bar: The progress bar that shows the building progress
    """
    _instance: "DnaViewerWindow" = None
    main_widget: QWidget = None
    select_dna_path: FileChooser = None
    load_dna_btn: QPushButton = None
    mesh_tree_list: QWidget = None
    joints_cb: QCheckBox = None
    blend_shapes_cb: QCheckBox = None
    skin_cb: QCheckBox = None
    rig_logic_cb: QCheckBox = None
    ctrl_attributes_on_root_joint_cb: QCheckBox = None
    animated_map_attributes_on_root_joint_cb: QCheckBox = None
    mesh_name_to_blend_shape_channel_name_cb: QCheckBox = None
    key_frames_cb: QCheckBox = None
    select_gui_path: FileChooser = None
    select_analog_gui_path: FileChooser = None
    select_aas_path: FileChooser = None
    process_btn: QPushButton = None
    progress_bar: QProgressBar = None
    dna: DNA = None
    def __init__(self, parent: QWidget = None) -> None:
        super().__init__(parent)
        self.body: QVBoxLayout = None
        self.header: QHBoxLayout = None
        self.build_options: QWidget = None
        self.extra_build_options: QWidget = None
        self.setup_window()
        self.create_ui()
[docs]    def setup_window(self) -> None:
        """A method for setting up the window"""
        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowTitleHint
            | Qt.WindowMaximizeButtonHint
            | Qt.WindowMinimizeButtonHint
            | Qt.WindowCloseButtonHint
        )
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setObjectName(WINDOW_OBJECT)
        self.setWindowTitle(WINDOW_TITLE)
        self.setWindowFlags(Qt.Window)
        self.setFocusPolicy(Qt.StrongFocus) 
[docs]    def create_ui(self) -> None:
        """Fills the window with UI elements"""
        self.main_widget = self.create_main_widget()
        self.setCentralWidget(self.main_widget)
        self.set_size()
        self.setStyleSheet(self.load_css()) 
[docs]    def load_css(self) -> str:
        css = os.path.join(os.path.dirname(__file__), "app.css")
        with open(css, encoding="utf-8") as file:
            return file.read() 
[docs]    def create_main_widget(self) -> QWidget:
        """
        Creates the widget containing the UI elements
        @rtype: QtWidgets.QWidget
        @returns: the main widget
        """
        header = self.create_header()
        body = self.create_body()
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.addLayout(header)
        layout.addWidget(QHLine())
        layout.addLayout(body)
        layout.setContentsMargins(MARGIN_LEFT, MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM)
        layout.setSpacing(SPACING)
        return widget 
[docs]    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) 
[docs]    def show_message_dialog(self) -> bool:
        dlg = QMessageBox()
        dlg.setIcon(QMessageBox.Warning)
        dlg.setWindowTitle("Warning")
        dlg.setText(
            "Unsaved changes exists.\nSave changes and create new scene, discard changes, and create new scene or cancel procesing."
        )
        dlg.setStandardButtons(
            QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
        )
        button = dlg.exec_()
        if button == QMessageBox.Save:
            cmds.SaveScene()
            return not cmds.file(q=True, modified=True)
        if button == QMessageBox.Cancel:
            return False
        return True 
[docs]    def process(self) -> None:
        """Start the build process of creation of scene from provided configuration from the UI"""
        process = True
        if cmds.file(q=True, modified=True):
            process = self.show_message_dialog()
        if process:
            self.set_progress(text="Processing in progress...", value=0)
            config = RigConfig(
                meshes=self.mesh_tree_list.get_selected_meshes(),
                gui_path=self.select_gui_path.get_file_path(),
                analog_gui_path=self.select_analog_gui_path.get_file_path(),
                aas_path=self.select_aas_path.get_file_path(),
                add_rig_logic=self.add_rig_logic(),
                add_joints=self.add_joints(),
                add_blend_shapes=self.add_blend_shapes(),
                add_skin_cluster=self.add_skin_cluster(),
                add_ctrl_attributes_on_root_joint=self.add_ctrl_attributes_on_root_joint(),
                add_animated_map_attributes_on_root_joint=self.add_animated_map_attributes_on_root_joint(),
                add_mesh_name_to_blend_shape_channel_name=self.add_mesh_name_to_blend_shape_channel_name(),
                add_key_frames=self.add_key_frames(),
            )
            self.main_widget.setEnabled(False)
            try:
                self.set_progress(value=33)
                self.dna = DNA(self.select_dna_path.get_file_path())
                self.set_progress(value=66)
                build_rig(dna=self.dna, config=config)
                self.set_progress(text="Processing completed", value=100)
            except Exception as e:
                self.set_progress(text="Processing failed", value=100)
                logging.error(e)
                confirmDialog(message=e, button=["ok"], icon="critical")
            self.main_widget.setEnabled(True) 
[docs]    def set_progress(self, text: str = None, value: int = None) -> None:
        """Setting text and/or value to progress bar"""
        if text is not None:
            self.progress_bar.setFormat(text)
        if value is not None:
            self.progress_bar.setValue(value) 
[docs]    @staticmethod
    def show_window() -> None:
        if DnaViewerWindow._instance is None:
            DnaViewerWindow._instance = DnaViewerWindow(
                parent=DnaViewerWindow.maya_main_window()
            )
        DnaViewerWindow.activate_window() 
[docs]    @staticmethod
    def maya_main_window() -> QWidget:
        """
        Gets the MayaWindow instance
        @throws RuntimeError
        @rtype: QtWidgets.QWidget
        @returns: main window instance
        """
        for obj in QApplication.topLevelWidgets():
            if obj.objectName() == "MayaWindow":
                return obj
        raise RuntimeError("Could not find MayaWindow instance") 
[docs]    @staticmethod
    def activate_window() -> None:
        """Shows window if minimized"""
        try:
            DnaViewerWindow._instance.show()
            if DnaViewerWindow._instance.windowState() & Qt.WindowMinimized:
                DnaViewerWindow._instance.setWindowState(Qt.WindowActive)
            DnaViewerWindow._instance.raise_()
            DnaViewerWindow._instance.activateWindow()
        except RuntimeError as e:
            logging.info(e)
            if str(e).rstrip().endswith("already deleted."):
                DnaViewerWindow._instance = None
                DnaViewerWindow.show_window() 
[docs]    def add_joints(self) -> bool:
        return self.is_checked(self.joints_cb) 
[docs]    def add_blend_shapes(self) -> bool:
        return self.is_checked(self.blend_shapes_cb) 
[docs]    def add_skin_cluster(self) -> bool:
        return self.is_checked(self.skin_cb) 
[docs]    def add_rig_logic(self) -> bool:
        return self.is_checked(self.rig_logic_cb) 
[docs]    def add_ctrl_attributes_on_root_joint(self) -> bool:
        return self.is_checked(self.ctrl_attributes_on_root_joint_cb) 
[docs]    def add_animated_map_attributes_on_root_joint(self) -> bool:
        return self.is_checked(self.animated_map_attributes_on_root_joint_cb) 
[docs]    def add_mesh_name_to_blend_shape_channel_name(self) -> bool:
        return self.is_checked(self.mesh_name_to_blend_shape_channel_name_cb) 
[docs]    def add_key_frames(self) -> bool:
        return self.is_checked(self.key_frames_cb) 
[docs]    def is_checked(self, checkbox: QCheckBox) -> bool:
        """
        Returns if the provided checkbox is checked and enabled
        @type checkbox: QCheckBox
        @param checkbox: The checkbox thats value needs to be checked and enabled
        @rtype: bool
        @returns: The flag representing if the checkbox is checked and enabled
        """
        return (
            checkbox is not None
            and bool(checkbox.isEnabled())
            and checkbox.checkState() == Qt.CheckState.Checked
        ) 
[docs]    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_dna_selector()
        self.mesh_tree_list = self.create_mesh_selector()
        self.build_options = self.create_build_options()
        self.extra_build_options = self.create_extra_build_options()
        tab = QTabWidget(self)
        tab.addTab(self.build_options, "Build options")
        tab.addTab(self.extra_build_options, "Extra options")
        widget = QWidget()
        layout = QHBoxLayout(widget)
        layout.addWidget(tab)
        self.body.addWidget(widget)
        self.select_gui_path = self.create_gui_selector()
        self.select_analog_gui_path = self.create_analog_gui_selector()
        self.select_aas_path = self.create_aas_selector()
        self.process_btn = self.create_process_btn()
        self.progress_bar = self.create_progress_bar()
        return self.body 
[docs]    def create_help_btn(self) -> QWidget:
        """
        Creates the help button widget
        @rtype: QHBoxLayout
        @returns: The created horizontal box layout with the widgets added
        """
        btn = QPushButton(self)
        btn.setText(" ? ")
        btn.setToolTip("Help")
        btn.clicked.connect(self.on_help)
        return btn 
[docs]    def on_help(self) -> None:
        """The method that gets called when the help button is clicked"""
        if HELP_URL:
            webbrowser.open(HELP_URL)
        else:
            QMessageBox.about(
                self,
                "About",
                "Sorry, this application does not have documentation yet.",
            ) 
[docs]    def create_dna_selector(self) -> QWidget:
        """
        Creates and adds the DNA selector widget
        @rtype: QWidget
        @returns: The created DNA selector widget
        """
        widget = QWidget()
        self.select_dna_path = self.create_dna_chooser()
        self.load_dna_btn = self.create_load_dna_button(self.select_dna_path)
        self.select_dna_path.fc_text_field.textChanged.connect(
            lambda: self.on_dna_selected(self.select_dna_path)
        )
        layout = QVBoxLayout()
        layout.addWidget(self.select_dna_path)
        layout.addWidget(self.load_dna_btn)
        layout.setContentsMargins(
            MARGIN_HEADER_LEFT,
            MARGIN_HEADER_TOP,
            MARGIN_HEADER_RIGHT,
            MARGIN_HEADER_BOTTOM,
        )
        widget.setLayout(layout)
        self.body.addWidget(widget)
        return widget 
[docs]    def on_dna_selected(self, input: FileChooser) -> None:
        """
        The method that gets called when a DNA file gets selected
        @type input: FileChooser
        @param input: The file chooser object corresponding to the DNA selector widget
        """
        enabled = input.get_file_path() is not None
        self.load_dna_btn.setEnabled(enabled)
        self.process_btn.setEnabled(False) 
[docs]    def create_dna_chooser(self) -> FileChooser:
        """
        Creates and adds the DNA chooser widget
        @rtype: FileChooser
        @returns: Dna chooser widget
        """
        return self.create_file_chooser(
            "Path:",
            "DNA file to load. Required by all gui elements",
            "Select a DNA file",
            "DNA files (*.dna)",
            self.on_dna_changed,
        ) 
[docs]    def on_dna_changed(self, state: int) -> None:  # pylint: disable=unused-argument
        """
        Method that gets called when the checkbox is changed
        @type state: int
        @param state: The changed state of the checkbox
        """
        enabled = False
        if self.dna:
            if self.dna.path == self.select_dna_path.get_file_path():
                enabled = True
        self.load_dna_btn.setEnabled(enabled)
        self.mesh_tree_list.btn_select_all.setEnabled(enabled)
        self.mesh_tree_list.btn_deselect_all.setEnabled(enabled)
        self.process_btn.setEnabled(enabled) 
[docs]    def on_load_dna_clicked(self, input: FileChooser) -> None:
        """
        The method that gets called when a DNA file gets selected
        @type input: FileChooser
        @param input: The file chooser object corresponding to the DNA selector widget
        """
        self.main_widget.setEnabled(False)
        QCoreApplication.processEvents()
        try:
            dna_file_path = input.get_file_path()
            if dna_file_path:
                self.dna = DNA(dna_file_path, [Layer.definition])
                lod_count = self.dna.get_lod_count()
                names = self.get_mesh_names()
                indices_names = self.get_lod_indices_names()
                self.mesh_tree_list.fill_mesh_list(lod_count, names, indices_names)
                self.joints_cb.setEnabled(True)
                self.enable_additional_build_options(True)
                self.process_btn.setEnabled(False)
                self.mesh_tree_list.btn_select_all.setEnabled(True)
                self.mesh_tree_list.btn_deselect_all.setEnabled(True)
        except Exception as e:
            dlg = QMessageBox()
            dlg.setIcon(QMessageBox.Warning)
            dlg.setWindowTitle("Error")
            dlg.setText(str(e))
            dlg.setStandardButtons(QMessageBox.Ok)
            dlg.exec_()
        self.main_widget.setEnabled(True) 
[docs]    def get_mesh_names(self) -> List[str]:
        """Reads in the meshes of the definition"""
        names: List[str] = []
        for index in range(self.dna.get_mesh_count()):
            names.append(self.dna.get_mesh_name(index))
        return names 
[docs]    def get_lod_indices_names(self) -> List[List[int]]:
        """Reads in the meshes of the definition"""
        lod_indices: List[List[int]] = []
        for index in range(self.dna.get_lod_count()):
            lod_indices.append(self.dna.get_mesh_indices_for_lod(index))
        return lod_indices 
[docs]    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)
        self.body.addWidget(widget)
        return widget 
[docs]    def create_file_chooser(
        self,
        label: str,
        hint: str,
        caption: str,
        filter: str,
        on_changed: Callable[[int], None] = None,
    ) -> FileChooser:
        """
        Creates a file chooser widget that is used for selecting file paths
        @type label: str
        @param label: The label in the FileDialog that pops up
        @type hint: str
        @param hint: The label in the FileDialog that pops up
        @type caption: str
        @param caption: The caption in the FileDialog that pops up
        @type filter: str
        @param filter: The file filter that is used in the FileDialog
        @rtype: FileChooser
        @returns: The created file chooser object
        """
        widget = FileChooser(
            label,
            hint,
            self,
            dialog_caption=caption,
            dialog_filter=filter,
            on_changed=on_changed or self.on_generic_changed,
        )
        self.body.addWidget(widget)
        return widget 
[docs]    def create_gui_selector(self) -> FileChooser:
        """
        Creates the gui selector widget
        @rtype: FileChooser
        @returns: Gui selector widget
        """
        return self.create_file_chooser(
            "Gui path:",
            "GUI file to load. Required by RigLogic",
            "Select the gui file",
            "gui files (*.ma)",
        ) 
[docs]    def create_aas_selector(self) -> FileChooser:
        """
        Creates and adds the additional assemble script selector widget
        @rtype: FileChooser
        @returns: Additional assemble script selector widget
        """
        return self.create_file_chooser(
            "Additional assemble script path:",
            "Additional assemble script to use. Required by RigLogic",
            "Select the aas file",
            "python script (*.py)",
        ) 
[docs]    def create_analog_gui_selector(self) -> FileChooser:
        """
        Creates and adds the analog gui selector widget
        @rtype: FileChooser
        @returns: Analog gui selector widget
        """
        return self.create_file_chooser(
            "Analog gui path:",
            "Analog GUI file to load. Required by RigLogic",
            "Select the analog gui file",
            "analog gui files (*.ma)",
        ) 
[docs]    def create_build_options(self) -> QWidget:
        """Creates and adds the widget containing the build options checkboxes"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(
            MARGIN_BODY_LEFT,
            MARGIN_BODY_TOP,
            MARGIN_BODY_RIGHT,
            MARGIN_BOTTOM,
        )
        self.joints_cb = self.create_checkbox(
            "joints",
            "Add joints to rig. Requires: DNA to be loaded",
            layout,
            self.on_joints_changed,
        )
        self.blend_shapes_cb = self.create_checkbox(
            "blend shapes",
            "Add blend shapes to rig. Requires: DNA to be loaded and at least one mesh to be check",
            layout,
            self.on_generic_changed,
        )
        self.skin_cb = self.create_checkbox(
            "skin cluster",
            "Add skin cluster to rig. Requires: DNA to be loaded and at least one mesh and joints to be checked",
            layout,
            self.on_generic_changed,
        )
        self.rig_logic_cb = self.create_checkbox(
            "rig logic",
            "Add RigLogic to rig. Requires: DNA to be loaded, all meshes to be checked, joints, skin, blend shapes to be checked, also gui, analog gui and additional assemble script must be set",
            layout,
        )
        layout.addStretch()
        return widget 
[docs]    def enable_additional_build_options(self, enable: bool) -> None:
        self.ctrl_attributes_on_root_joint_cb.setEnabled(enable)
        self.animated_map_attributes_on_root_joint_cb.setEnabled(enable)
        self.mesh_name_to_blend_shape_channel_name_cb.setEnabled(enable)
        self.key_frames_cb.setEnabled(enable) 
[docs]    def create_checkbox(
        self,
        label: str,
        hint: str,
        layout: QHBoxLayout,
        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 hint: str
        @param hint: The hint of the checkbox
        @type on_changed: Callable[[int], None]
        @param on_changed: The method that will get called when the checkbox changes states
        @type checked: bool
        @param checked: The value representing if the checkbox is checked after creation
        @type enabled: bool
        @param enabled: The value representing if the checkbox is enabled after creation
        @rtype: QCheckBox
        @returns: the created checkbox object
        """
        checkbox = QCheckBox(label, self)
        checkbox.setChecked(checked)
        checkbox.setEnabled(enabled)
        checkbox.setToolTip(hint)
        if on_changed:
            checkbox.stateChanged.connect(on_changed)
        layout.addWidget(checkbox)
        return checkbox 
[docs]    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 self.joints_cb.isChecked():
            self.process_btn.setEnabled(True)
            if self.mesh_tree_list.get_selected_meshes():
                self.skin_cb.setEnabled(True)
        else:
            self.skin_cb.setEnabled(False)
            if not self.mesh_tree_list.get_selected_meshes():
                self.process_btn.setEnabled(False)
        self.on_generic_changed(state) 
[docs]    def create_process_btn(self) -> QPushButton:
        """
        Creates and adds a process button
        @type window: QMainWindow
        @param window: The instance of the window object
        @rtype: QPushButton
        @returns: The created process button
        """
        btn = QPushButton("Process")
        btn.setEnabled(False)
        btn.clicked.connect(self.process)
        self.body.addWidget(btn)
        return btn 
[docs]    def create_progress_bar(self) -> QProgressBar:
        """
        Creates and adds progress bar
        @type window: QMainWindow
        @param window: The instance of the window object
        @rtype: QProgressBar
        @returns: The created progress bar
        """
        progress = QProgressBar(self)
        progress.setRange(0, 100)
        progress.setValue(0)
        progress.setTextVisible(True)
        progress.setFormat("")
        self.body.addWidget(progress)
        return progress 
[docs]    def on_generic_changed(self, state: int) -> None:  # pylint: disable=unused-argument
        """
        Method that gets called when the checkbox is changed
        @type state: int
        @param state: The changed state of the checkbox
        """
        self.set_riglogic_cb_enabled() 
[docs]    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())
        ) 
[docs]    def set_riglogic_cb_enabled(self) -> None:
        """Method that sets enable state of riglogic check box"""
        all_total_meshes = False
        if self.dna and self.is_enabled_and_checked(self.blend_shapes_cb):
            if (
                len(self.mesh_tree_list.get_selected_meshes())
                == self.dna.get_mesh_count()
            ):
                all_total_meshes = True
        enabled = (
            self.is_enabled_and_checked(self.joints_cb)
            and self.is_enabled_and_checked(self.blend_shapes_cb)
            and all_total_meshes
            and self.is_enabled_and_checked(self.skin_cb)
            and self.select_gui_path.get_file_path() is not None
            and self.select_analog_gui_path.get_file_path() is not None
            and self.select_aas_path.get_file_path() is not None
        )
        self.rig_logic_cb.setEnabled(enabled)