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)