<<<<<<< Updated upstream ======= #!/usr/bin/env python # -*- coding: utf-8 -*- >>>>>>> Stashed changes import logging import os import webbrowser from typing import Callable, List from maya import cmds from maya.cmds import confirmDialog from PySide2.QtCore import Qt, QSize, QRect, QPoint, QCoreApplication from PySide2.QtWidgets import ( QApplication, QCheckBox, QGridLayout, QHBoxLayout, QLabel, QLayout, QMainWindow, QMessageBox, QProgressBar, QPushButton, QTabWidget, QTreeWidget, QTreeWidgetItem, QTreeWidgetItemIterator, QVBoxLayout, QWidget, QScrollArea ) from PySide2.QtGui import QIcon, QPixmap from PySide2 import QtCore, QtGui from .. import DNA, build_rig from ..builder.config import RigConfig from ..dnalib.layer import Layer from ..version import __version__ from .widgets import FileChooser, QHLine def show() -> None: DnaViewerWindow.show_window() <<<<<<< Updated upstream TOOL_NAME = "Delos" WINDOW_TITLE = "DNA Viewer" WINDOW_OBJECT = "dnaviewer" TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" ======= TOOL_NAME = "DNA Viewer" WINDOW_TITLE = "DNA Viewer" WINDOW_OBJECT = "dnaviewer" TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/MetaFusion/wiki" >>>>>>> Stashed changes SPACING = 6 WINDOW_SIZE_WIDTH_MIN = 800 WINDOW_SIZE_WIDTH_MAX = 1200 WINDOW_SIZE_HEIGHT_MIN = 1000 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 TOOL_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))).replace("\\", "/") SCRIPTS_PATH = os.path.join(TOOL_PATH, "scripts").replace("\\", "/") DATA_PATH = os.path.join(TOOL_PATH, "data").replace("\\", "/") DNA_PATH = os.path.join(DATA_PATH, "dna").replace("\\", "/") IMG_PATH = os.path.join(DATA_PATH, "img").replace("\\", "/") SOURCE_PATH = os.path.join(DATA_PATH, "source").replace("\\", "/") GUI_PATH = os.path.join(SOURCE_PATH, "gui.ma").replace("\\", "/") ANALOG_GUI_PATH = os.path.join(SOURCE_PATH, "analog_gui.ma").replace("\\", "/") ADDITIONAL_ASSEMBLE_SCRIPT_PATH = os.path.join(SOURCE_PATH, "additional_assemble_script.py").replace("\\", "/") class FlowLayout(QLayout): def __init__(self, parent=None): super().__init__(parent) self.itemList = [] self.spacing_x = 5 self.spacing_y = 5 def addItem(self, item): self.itemList.append(item) def count(self): return len(self.itemList) def itemAt(self, index): if index >= 0 and index < len(self.itemList): return self.itemList[index] return None def takeAt(self, index): if index >= 0 and index < len(self.itemList): return self.itemList.pop(index) return None def expandingDirections(self): return Qt.Orientations(Qt.Orientation(0)) def hasHeightForWidth(self): return True def heightForWidth(self, width): height = self.doLayout(QRect(0, 0, width, 0), True) return height def setGeometry(self, rect): super(FlowLayout, self).setGeometry(rect) self.doLayout(rect, False) def sizeHint(self): return self.minimumSize() def minimumSize(self): size = QSize() for item in self.itemList: size = size.expandedTo(item.minimumSize()) size += QSize(2 * self.margin(), 2 * self.margin()) return size def doLayout(self, rect, testOnly): x = rect.x() y = rect.y() lineHeight = 0 for item in self.itemList: wid = item.widget() spaceX = self.spacing_x spaceY = self.spacing_y nextX = x + item.sizeHint().width() + spaceX if nextX - spaceX > rect.right() and lineHeight > 0: x = rect.x() y = y + lineHeight + spaceY nextX = x + item.sizeHint().width() + spaceX lineHeight = 0 if not testOnly: item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) x = nextX lineHeight = max(lineHeight, item.sizeHint().height()) return y + lineHeight - rect.y() class DNABrowser(QWidget): dna_selected = QtCore.Signal(str) # Signal: when DNA is selected def __init__(self, dna_path, img_path, parent=None): super().__init__(parent) self.dna_path = dna_path self.img_path = img_path self.setup_ui() self.scan_dna_files() self.update_grid() def setup_ui(self): self.main_layout = QVBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) self.flow_widget = QWidget() self.flow_layout = FlowLayout(self.flow_widget) self.flow_layout.setSpacing(10) self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) self.scroll_area.setWidget(self.flow_widget) self.scroll_area.setFixedHeight(350) self.scroll_area.setStyleSheet(""" QScrollArea { border: 1px solid #404040; background-color: #303030; } """) self.main_layout.addWidget(self.scroll_area) def scan_dna_files(self): """Scan DNA folder and create index""" self.dna_files = {} if not os.path.exists(self.dna_path): cmds.warning(f"DNA path not found: {self.dna_path}") return # Default preview image path default_preview = os.path.join(self.img_path, "Preview.png").replace("\\", "/") for file in os.listdir(self.dna_path): if file.endswith('.dna'): name = os.path.splitext(file)[0] dna_file = os.path.join(self.dna_path, file).replace("\\", "/") # Find corresponding image, if not found use default image img_file = None for ext in ['.jpg', '.png', '.jpeg']: img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/") if os.path.exists(img_path): img_file = img_path break <<<<<<< Updated upstream # If no corresponding image is found, use default preview image ======= >>>>>>> Stashed changes if not img_file and os.path.exists(default_preview): img_file = default_preview self.dna_files[name] = { 'dna_path': dna_file, 'img_path': img_file } def update_grid(self): """Update DNA grid""" for i in reversed(range(self.flow_layout.count())): self.flow_layout.itemAt(i).widget().deleteLater() for name, info in self.dna_files.items(): dna_btn = self.create_dna_button(name, info) self.flow_layout.addWidget(dna_btn) def create_dna_button(self, name, info): """Create DNA button""" btn = QPushButton() btn.setFixedSize(180, 120) layout = QVBoxLayout(btn) layout.setContentsMargins(4, 4, 4, 4) # Icon label icon_label = QLabel() icon_label.setAlignment(Qt.AlignCenter) pixmap = QtGui.QPixmap(info['img_path']) scaled_pixmap = pixmap.scaled(90, 90, Qt.KeepAspectRatio, Qt.SmoothTransformation) icon_label.setPixmap(scaled_pixmap) # Text label text_label = QLabel(name) text_label.setAlignment(Qt.AlignCenter) text_label.setStyleSheet("color: #FFFFFF;") layout.addWidget(icon_label) layout.addWidget(text_label) btn.setStyleSheet(""" QPushButton { background-color: #303030; border: 2px solid #202020; border-radius: 6px; } QPushButton:hover { background-color: #404040; border: 2px solid #303030; } """) btn.clicked.connect(lambda: self.dna_selected.emit(info['dna_path'])) return btn class MeshTreeList(QWidget): def __init__(self, main_window: "DnaViewerWindow") -> None: super().__init__() self.main_window = main_window # Layout self.main_layout = QVBoxLayout() self.main_layout.setContentsMargins( MARGIN_BODY_LEFT, MARGIN_BODY_TOP, MARGIN_BODY_RIGHT, MARGIN_BOTTOM ) self.setLayout(self.main_layout) # Widgets self.title_label = QLabel("Meshes:") self.scroll_area = QScrollArea() self.tree_container = QWidget() self.mesh_tree = self.create_mesh_tree() self.btn_select_all = QPushButton("Select all meshes") self.btn_deselect_all = QPushButton("Deselect all meshes") # Setup scroll area self.scroll_area.setWidgetResizable(True) self.scroll_area.setMinimumHeight(150) self.scroll_area.setStyleSheet(""" QScrollArea { border: 1px solid #404040; background-color: #303030; } """) # Setup tree container self.tree_layout = QVBoxLayout(self.tree_container) self.tree_layout.setContentsMargins(0, 0, 0, 0) self.tree_layout.addWidget(self.mesh_tree) self.scroll_area.setWidget(self.tree_container) # Setup buttons self.btn_select_all.setEnabled(False) self.btn_deselect_all.setEnabled(False) self.button_layout = QHBoxLayout() self.button_layout.addWidget(self.btn_select_all) self.button_layout.addWidget(self.btn_deselect_all) # Layout assembly self.main_layout.addWidget(self.title_label) self.main_layout.addWidget(self.scroll_area) self.main_layout.addLayout(self.button_layout) # Connections self.btn_select_all.clicked.connect(self.select_all) self.btn_deselect_all.clicked.connect(self.deselect_all) # ==================================================== DNA Browser ==================================================== def scan_dna_files(self): self.dna_files = {} if not os.path.exists(self.dna_path): cmds.warning(f"DNA path not found: {self.dna_path}") return if not os.path.exists(self.img_path): cmds.warning(f"Image path not found: {self.img_path}") return for file in os.listdir(self.dna_path): if file.endswith('.dna'): name = os.path.splitext(file)[0] dna_file = os.path.join(self.dna_path, file).replace("\\", "/") img_file = None for ext in ['.jpg', '.png', '.jpeg']: img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/") if os.path.exists(img_path): img_file = img_path break self.dna_files[name] = { 'dna_path': dna_file, 'img_path': img_file } print(f"DNA file: {name}") print(f" DNA path: {dna_file}") print(f" Image path: {img_file}") print(f" Image exists: {bool(img_file and os.path.exists(img_file))}") def create_mesh_tree(self) -> QWidget: mesh_tree = QTreeWidget() mesh_tree.setHeaderHidden(True) mesh_tree.itemChanged.connect(self.tree_item_changed) mesh_tree.setStyleSheet("background-color: #505050") mesh_tree.setToolTip("Select mesh or meshes to add to rig") return mesh_tree def fill_mesh_list( self, lod_count: int, names: List[str], indices_names: List[List[int]] ) -> None: self.mesh_tree.clear() for i in range(lod_count): parent = QTreeWidgetItem(self.mesh_tree) parent.setText(0, f"LOD {i}") parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) meshes_in_lod = indices_names[i] for mesh_index in meshes_in_lod: child = QTreeWidgetItem(parent) child.setFlags(child.flags() | Qt.ItemIsUserCheckable) child.setText(0, f"{names[mesh_index]}") child.setCheckState(0, Qt.Unchecked) self.mesh_tree.setItemExpanded(parent, True) def get_selected_meshes(self) -> List[int]: 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 def select_all(self) -> None: self.iterate_over_items(Qt.Checked) def deselect_all(self) -> None: self.iterate_over_items(Qt.Unchecked) def iterate_over_items(self, state: Qt.CheckState) -> None: item = self.mesh_tree.invisibleRootItem() for index in range(item.childCount()): child = item.child(index) child.setCheckState(0, state) def tree_item_changed(self) -> None: """The method that gets called when a tree item gets its value changed""" meshes = self.get_selected_meshes() if meshes: self.main_window.skin_cb.setEnabled(self.main_window.joints_cb.checkState()) self.main_window.blend_shapes_cb.setEnabled(True) self.main_window.process_btn.setEnabled(True) self.main_window.rig_logic_cb.setEnabled(False) if len(meshes) == self.main_window.dna.get_mesh_count(): self.main_window.rig_logic_cb.setEnabled( self.main_window.joints_cb.checkState() and self.main_window.blend_shapes_cb.checkState() and self.main_window.skin_cb.checkState() and self.main_window.select_gui_path.get_file_path() is not None and self.main_window.select_analog_gui_path.get_file_path() is not None and self.main_window.select_aas_path.get_file_path() is not None ) else: self.main_window.skin_cb.setEnabled(False) self.main_window.blend_shapes_cb.setEnabled(False) self.main_window.process_btn.setEnabled( self.main_window.joints_cb.checkState() ) class DnaViewerWindow(QMainWindow): _instance: "DnaViewerWindow" = None main_widget: QWidget = None select_dna_path: FileChooser = None load_dna_btn: QPushButton = None mesh_tree_list: QWidget = None joints_cb: QCheckBox = None blend_shapes_cb: QCheckBox = None skin_cb: QCheckBox = None rig_logic_cb: QCheckBox = None ctrl_attributes_on_root_joint_cb: QCheckBox = None animated_map_attributes_on_root_joint_cb: QCheckBox = None mesh_name_to_blend_shape_channel_name_cb: QCheckBox = None key_frames_cb: QCheckBox = None select_gui_path: FileChooser = None select_analog_gui_path: FileChooser = None select_aas_path: FileChooser = None process_btn: QPushButton = None progress_bar: QProgressBar = None dna: DNA = None def __init__(self, parent: QWidget = None) -> None: super().__init__(parent) self.body: QVBoxLayout = None self.header: QHBoxLayout = None self.build_options: QWidget = None self.extra_build_options: QWidget = None self.setup_window() self.create_ui() def setup_window(self) -> None: self.setWindowFlags( self.windowFlags() | Qt.WindowTitleHint | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint ) self.setAttribute(Qt.WA_DeleteOnClose) self.setObjectName(WINDOW_OBJECT) self.setWindowTitle(WINDOW_TITLE) self.setWindowFlags(Qt.Window) self.setFocusPolicy(Qt.StrongFocus) def create_ui(self) -> None: self.main_widget = self.create_main_widget() self.setCentralWidget(self.main_widget) self.set_size() self.setStyleSheet(self.load_css()) def load_css(self) -> str: css = os.path.join(os.path.dirname(__file__), "app.css") with open(css, encoding="utf-8") as file: return file.read() def create_main_widget(self) -> QWidget: widget = QWidget() layout = QVBoxLayout(widget) header = self.create_header() layout.addLayout(header) layout.addWidget(QHLine()) body = self.create_body() layout.addLayout(body) layout.setContentsMargins(MARGIN_LEFT, MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM) layout.setSpacing(SPACING) return widget def set_size(self) -> None: self.setMaximumSize(WINDOW_SIZE_WIDTH_MAX, WINDOW_SIZE_HEIGHT_MAX) self.setMinimumSize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN) self.resize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN) def show_message_dialog(self) -> bool: dlg = QMessageBox() dlg.setIcon(QMessageBox.Warning) dlg.setWindowTitle("Warning") dlg.setText( "Unsaved changes exists.\nSave changes and create new scene, discard changes, and create new scene or cancel procesing." ) dlg.setStandardButtons( QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel ) button = dlg.exec_() if button == QMessageBox.Save: cmds.SaveScene() return not cmds.file(q=True, modified=True) if button == QMessageBox.Cancel: return False return True def process(self) -> None: 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=GUI_PATH, analog_gui_path=ANALOG_GUI_PATH, aas_path=ADDITIONAL_ASSEMBLE_SCRIPT_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) def set_progress(self, text: str = None, value: int = None) -> None: if text is not None: self.progress_bar.setFormat(text) if value is not None: self.progress_bar.setValue(value) @staticmethod def show_window() -> None: if DnaViewerWindow._instance is None: DnaViewerWindow._instance = DnaViewerWindow( parent=DnaViewerWindow.maya_main_window() ) DnaViewerWindow.activate_window() @staticmethod def maya_main_window() -> QWidget: for obj in QApplication.topLevelWidgets(): if obj.objectName() == "MayaWindow": return obj raise RuntimeError("Could not find MayaWindow instance") @staticmethod def activate_window() -> None: try: DnaViewerWindow._instance.show() if DnaViewerWindow._instance.windowState() & Qt.WindowMinimized: DnaViewerWindow._instance.setWindowState(Qt.WindowActive) DnaViewerWindow._instance.raise_() DnaViewerWindow._instance.activateWindow() except RuntimeError as e: logging.info(e) if str(e).rstrip().endswith("already deleted."): DnaViewerWindow._instance = None DnaViewerWindow.show_window() def add_joints(self) -> bool: return self.is_checked(self.joints_cb) def add_blend_shapes(self) -> bool: return self.is_checked(self.blend_shapes_cb) def add_skin_cluster(self) -> bool: return self.is_checked(self.skin_cb) def add_rig_logic(self) -> bool: return self.is_checked(self.rig_logic_cb) def add_ctrl_attributes_on_root_joint(self) -> bool: return self.is_checked(self.ctrl_attributes_on_root_joint_cb) def add_animated_map_attributes_on_root_joint(self) -> bool: return self.is_checked(self.animated_map_attributes_on_root_joint_cb) def add_mesh_name_to_blend_shape_channel_name(self) -> bool: return self.is_checked(self.mesh_name_to_blend_shape_channel_name_cb) def add_key_frames(self) -> bool: return self.is_checked(self.key_frames_cb) def is_checked(self, checkbox: QCheckBox) -> bool: return ( checkbox is not None and bool(checkbox.isEnabled()) and checkbox.checkState() == Qt.CheckState.Checked ) def create_body(self) -> QVBoxLayout: """Create body layout""" self.body = QVBoxLayout() self.body.setContentsMargins( MARGIN_BODY_LEFT, MARGIN_BODY_TOP, MARGIN_BODY_RIGHT, MARGIN_BOTTOM, ) self.body.setSpacing(SPACING) # Add DNA browser self.dna_browser = DNABrowser(DNA_PATH, IMG_PATH, self) self.dna_browser.dna_selected.connect(self.on_dna_browser_selected) self.body.addWidget(self.dna_browser) # DNA selector 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 = FileChooser("", "", self) self.select_gui_path.fc_text_field.setText(GUI_PATH) self.select_gui_path.hide() self.select_analog_gui_path = FileChooser("", "", self) self.select_analog_gui_path.fc_text_field.setText(ANALOG_GUI_PATH) self.select_analog_gui_path.hide() self.select_aas_path = FileChooser("", "", self) self.select_aas_path.fc_text_field.setText(ADDITIONAL_ASSEMBLE_SCRIPT_PATH) self.select_aas_path.hide() self.process_btn = self.create_process_btn() self.progress_bar = self.create_progress_bar() return self.body def on_dna_browser_selected(self, dna_path: str) -> None: """When DNA browser selects a DNA file, update the input field""" if self.select_dna_path: self.select_dna_path.fc_text_field.setText(dna_path) self.on_dna_selected(self.select_dna_path) def on_dna_path_changed(self, text: str) -> None: """When DNA file input field content changes""" if os.path.exists(text) and text.endswith('.dna'): self.on_dna_selected(self.select_dna_path) def create_header(self) -> QHBoxLayout: self.header = QHBoxLayout() label = QLabel("v" + __version__) btn = self.create_help_btn() self.header.addWidget(label) self.header.addStretch(1) self.header.addWidget(btn) self.header.setContentsMargins( MARGIN_HEADER_LEFT, MARGIN_HEADER_TOP, MARGIN_HEADER_RIGHT, MARGIN_HEADER_BOTTOM, ) self.header.setSpacing(SPACING) return self.header def create_help_btn(self) -> QWidget: btn = QPushButton(self) btn.setText(" ? ") btn.setToolTip("Help") btn.clicked.connect(self.on_help) return btn def on_help(self) -> None: if TOOL_HELP_URL: webbrowser.open(TOOL_HELP_URL) else: QMessageBox.about( self, "About", "Sorry, this application does not have documentation yet.", ) def create_dna_selector(self) -> QWidget: 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 def on_dna_selected(self, input: FileChooser) -> None: enabled = input.get_file_path() is not None self.load_dna_btn.setEnabled(enabled) self.process_btn.setEnabled(False) def create_dna_chooser(self) -> FileChooser: return self.create_file_chooser( "DNA File:", "DNA file to load. Required by all gui elements", "Select a DNA file", "DNA files (*.dna)", self.on_dna_changed, ) def on_dna_changed(self, state: int) -> None: # pylint: disable=unused-argument 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) def create_load_dna_button(self, dna_input: FileChooser) -> QWidget: btn = QPushButton("Load DNA") btn.setEnabled(False) btn.clicked.connect(lambda: self.on_load_dna_clicked(dna_input)) return btn def on_load_dna_clicked(self, input: FileChooser) -> None: self.main_widget.setEnabled(False) QCoreApplication.processEvents() try: dna_file_path = input.get_file_path() if dna_file_path: self.dna = DNA(dna_file_path, [Layer.definition]) lod_count = self.dna.get_lod_count() names = self.get_mesh_names() indices_names = self.get_lod_indices_names() self.mesh_tree_list.fill_mesh_list(lod_count, names, indices_names) self.joints_cb.setEnabled(True) self.enable_additional_build_options(True) self.process_btn.setEnabled(False) self.mesh_tree_list.btn_select_all.setEnabled(True) self.mesh_tree_list.btn_deselect_all.setEnabled(True) 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) def get_mesh_names(self) -> List[str]: names: List[str] = [] for index in range(self.dna.get_mesh_count()): names.append(self.dna.get_mesh_name(index)) return names def get_lod_indices_names(self) -> List[List[int]]: lod_indices: List[List[int]] = [] for index in range(self.dna.get_lod_count()): lod_indices.append(self.dna.get_mesh_indices_for_lod(index)) return lod_indices def create_mesh_selector(self) -> MeshTreeList: widget = MeshTreeList(self) self.body.addWidget(widget) return widget def create_file_chooser( self, label: str, hint: str, caption: str, filter: str, on_changed: Callable[[int], None] = None, ) -> FileChooser: 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 def create_gui_selector(self) -> FileChooser: return self.create_file_chooser( "Gui path:", "GUI file to load. Required by RigLogic", "Select the gui file", "gui files (*.ma)", ) def create_aas_selector(self) -> FileChooser: return self.create_file_chooser( "Additional assemble script path:", "Additional assemble script to use. Required by RigLogic", "Select the aas file", "python script (*.py)", ) def create_analog_gui_selector(self) -> FileChooser: 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)", ) def create_build_options(self) -> QWidget: 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() widget.setMaximumHeight(150) return widget def create_extra_build_options(self) -> QWidget: 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() widget.setMaximumHeight(150) return widget def enable_additional_build_options(self, enable: bool) -> None: self.ctrl_attributes_on_root_joint_cb.setEnabled(enable) self.animated_map_attributes_on_root_joint_cb.setEnabled(enable) self.mesh_name_to_blend_shape_channel_name_cb.setEnabled(enable) self.key_frames_cb.setEnabled(enable) def create_checkbox( self, label: str, hint: str, layout: QHBoxLayout, on_changed: Callable[[int], None] = None, checked: bool = False, enabled: bool = False, ) -> QCheckBox: checkbox = QCheckBox(label, self) checkbox.setChecked(checked) checkbox.setEnabled(enabled) checkbox.setToolTip(hint) if on_changed: checkbox.stateChanged.connect(on_changed) layout.addWidget(checkbox) return checkbox def on_joints_changed(self, state: int) -> None: 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) def create_process_btn(self) -> QPushButton: btn = QPushButton("Process") btn.setEnabled(False) btn.clicked.connect(self.process) self.body.addWidget(btn) return btn def create_progress_bar(self) -> QProgressBar: progress = QProgressBar(self) progress.setRange(0, 100) progress.setValue(0) progress.setTextVisible(True) progress.setFormat("") self.body.addWidget(progress) return progress def on_generic_changed(self, state: int) -> None: # pylint: disable=unused-argument self.set_riglogic_cb_enabled() def is_enabled_and_checked(self, check_box: QCheckBox) -> bool: return ( check_box is not None and bool(check_box.isEnabled()) and bool(check_box.isChecked()) ) def set_riglogic_cb_enabled(self) -> None: 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) ) self.rig_logic_cb.setEnabled(enabled)