Source code for dna_viewer.ui.app

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_header(self) -> QHBoxLayout: """ Creates and adds to the header widget @rtype: QHBoxLayout @returns: The created horizontal box layout with the widgets added """ self.header = QHBoxLayout() label = QLabel("v" + __version__) btn = self.create_help_btn() self.header.addWidget(label) self.header.addStretch(1) self.header.addWidget(btn) self.header.setContentsMargins( MARGIN_HEADER_LEFT, MARGIN_HEADER_TOP, MARGIN_HEADER_RIGHT, MARGIN_HEADER_BOTTOM, ) self.header.setSpacing(SPACING) return self.header
[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 create_load_dna_button(self, dna_input: FileChooser) -> QWidget: """ Creates and adds the load DNA button widget @type input: FileChooser @param input: The file chooser object corresponding to the DNA selector widget @rtype: QWidget @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
[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 create_extra_build_options(self) -> QWidget: """Creates and adds the widget containing the extra build options checkboxes""" widget = QWidget() layout = QVBoxLayout(widget) layout.setContentsMargins( MARGIN_BODY_LEFT, MARGIN_BODY_TOP, MARGIN_BODY_RIGHT, MARGIN_BOTTOM, ) self.ctrl_attributes_on_root_joint_cb = self.create_checkbox( "ctrl attributes on root joint", "ctrl attributes on root joint", layout, enabled=True, checked=True, ) self.animated_map_attributes_on_root_joint_cb = self.create_checkbox( "animated map attributes on root joint", "animated map attributes on root joint", layout, enabled=True, checked=True, ) self.mesh_name_to_blend_shape_channel_name_cb = self.create_checkbox( "mesh name to blend shape channel name", "mesh name to blend shape channel name", layout, enabled=True, checked=True, ) self.key_frames_cb = self.create_checkbox( "key frames", "Add keyframes to rig", layout, enabled=True, checked=True, ) 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)