2025-01-13 23:36:50 +08:00
<< << << < Updated upstream
== == == =
#!/usr/bin/env python
# -*- coding: utf-8 -*-
>> >> >> > Stashed changes
2025-01-06 23:33:41 +08:00
import logging
import os
import webbrowser
from typing import Callable , List
from maya import cmds
from maya . cmds import confirmDialog
2025-01-13 20:41:08 +08:00
from PySide2 . QtCore import Qt , QSize , QRect , QPoint , QCoreApplication
2025-01-06 23:33:41 +08:00
from PySide2 . QtWidgets import (
QApplication ,
QCheckBox ,
QGridLayout ,
QHBoxLayout ,
QLabel ,
2025-01-13 20:41:08 +08:00
QLayout ,
2025-01-06 23:33:41 +08:00
QMainWindow ,
QMessageBox ,
QProgressBar ,
QPushButton ,
QTabWidget ,
QTreeWidget ,
QTreeWidgetItem ,
QTreeWidgetItemIterator ,
QVBoxLayout ,
QWidget ,
2025-01-13 20:41:08 +08:00
QScrollArea
2025-01-06 23:33:41 +08:00
)
2025-01-13 20:41:08 +08:00
from PySide2 . QtGui import QIcon , QPixmap
from PySide2 import QtCore , QtGui
2025-01-06 23:33:41 +08:00
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 ( )
2025-01-13 23:36:50 +08:00
<< << << < Updated upstream
2025-01-13 20:41:08 +08:00
TOOL_NAME = " Delos "
2025-01-06 23:33:41 +08:00
WINDOW_TITLE = " DNA Viewer "
2025-01-13 20:41:08 +08:00
WINDOW_OBJECT = " dnaviewer "
TOOL_HELP_URL = f " http://10.72.61.59:3000/ArtGroup/ { TOOL_NAME } /wiki "
2025-01-13 23:36:50 +08:00
== == == =
TOOL_NAME = " DNA Viewer "
WINDOW_TITLE = " DNA Viewer "
WINDOW_OBJECT = " dnaviewer "
TOOL_HELP_URL = f " https://gitea.cgnico.com/CGNICO/MetaFusion/wiki "
>> >> >> > Stashed changes
2025-01-06 23:33:41 +08:00
SPACING = 6
WINDOW_SIZE_WIDTH_MIN = 800
WINDOW_SIZE_WIDTH_MAX = 1200
2025-01-13 20:41:08 +08:00
WINDOW_SIZE_HEIGHT_MIN = 1000
2025-01-06 23:33:41 +08:00
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
2025-01-13 20:41:08 +08:00
TOOL_PATH = os . path . join ( os . path . dirname ( os . path . dirname ( os . path . dirname ( os . path . dirname ( __file__ ) ) ) ) ) . replace ( " \\ " , " / " )
2025-01-12 23:04:04 +08:00
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 ( " \\ " , " / " )
2025-01-13 20:41:08 +08:00
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 ( " \\ " , " / " )
2025-01-12 23:04:04 +08:00
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
class FlowLayout ( QLayout ) :
def __init__ ( self , parent = None ) :
super ( ) . __init__ ( parent )
self . itemList = [ ]
self . spacing_x = 5
self . spacing_y = 5
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
def addItem ( self , item ) :
self . itemList . append ( item )
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
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 : 1 px 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
2025-01-13 23:36:50 +08:00
<< << << < Updated upstream
2025-01-13 20:41:08 +08:00
# If no corresponding image is found, use default preview image
2025-01-13 23:36:50 +08:00
== == == =
>> >> >> > Stashed changes
2025-01-13 20:41:08 +08:00
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 : 2 px solid #202020;
border - radius : 6 px ;
}
QPushButton : hover {
background - color : #404040;
border : 2 px solid #303030;
}
""" )
btn . clicked . connect ( lambda : self . dna_selected . emit ( info [ ' dna_path ' ] ) )
return btn
class MeshTreeList ( QWidget ) :
2025-01-06 23:33:41 +08:00
def __init__ ( self , main_window : " DnaViewerWindow " ) - > None :
super ( ) . __init__ ( )
self . main_window = main_window
2025-01-13 20:41:08 +08:00
# Layout
self . main_layout = QVBoxLayout ( )
self . main_layout . setContentsMargins (
2025-01-06 23:33:41 +08:00
MARGIN_BODY_LEFT ,
2025-01-13 20:41:08 +08:00
MARGIN_BODY_TOP ,
2025-01-06 23:33:41 +08:00
MARGIN_BODY_RIGHT ,
2025-01-13 20:41:08 +08:00
MARGIN_BOTTOM
2025-01-06 23:33:41 +08:00
)
2025-01-13 20:41:08 +08:00
self . setLayout ( self . main_layout )
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
# 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 " )
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
# Setup scroll area
self . scroll_area . setWidgetResizable ( True )
self . scroll_area . setMinimumHeight ( 150 )
self . scroll_area . setStyleSheet ( """
QScrollArea {
border : 1 px 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
2025-01-06 23:33:41 +08:00
self . btn_select_all . setEnabled ( False )
self . btn_deselect_all . setEnabled ( False )
2025-01-13 20:41:08 +08:00
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 )
2025-01-06 23:33:41 +08:00
self . btn_deselect_all . clicked . connect ( self . deselect_all )
2025-01-13 20:41:08 +08:00
# ==================================================== 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 ) ) } " )
2025-01-06 23:33:41 +08:00
2025-01-12 22:29:32 +08:00
2025-01-13 20:41:08 +08:00
def create_mesh_tree ( self ) - > QWidget :
2025-01-06 23:33:41 +08:00
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
2025-01-12 22:29:32 +08:00
def __init__ ( self , parent : QWidget = None ) - > None :
2025-01-06 23:33:41 +08:00
super ( ) . __init__ ( parent )
2025-01-12 22:29:32 +08:00
self . body : QVBoxLayout = None
self . header : QHBoxLayout = None
self . build_options : QWidget = None
self . extra_build_options : QWidget = None
2025-01-06 23:33:41 +08:00
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 )
2025-01-13 20:41:08 +08:00
header = self . create_header ( )
2025-01-06 23:33:41 +08:00
layout . addLayout ( header )
layout . addWidget ( QHLine ( ) )
2025-01-13 20:41:08 +08:00
body = self . create_body ( )
2025-01-06 23:33:41 +08:00
layout . addLayout ( body )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
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. \n Save 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 :
2025-01-12 22:29:32 +08:00
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 ( ) ,
2025-01-13 20:41:08 +08:00
gui_path = GUI_PATH ,
analog_gui_path = ANALOG_GUI_PATH ,
aas_path = ADDITIONAL_ASSEMBLE_SCRIPT_PATH ,
2025-01-12 22:29:32 +08:00
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 )
2025-01-06 23:33:41 +08:00
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 :
2025-01-12 22:29:32 +08:00
return self . is_checked ( self . ctrl_attributes_on_root_joint_cb )
2025-01-06 23:33:41 +08:00
def add_animated_map_attributes_on_root_joint ( self ) - > bool :
2025-01-12 22:29:32 +08:00
return self . is_checked ( self . animated_map_attributes_on_root_joint_cb )
2025-01-06 23:33:41 +08:00
def add_mesh_name_to_blend_shape_channel_name ( self ) - > bool :
2025-01-12 22:29:32 +08:00
return self . is_checked ( self . mesh_name_to_blend_shape_channel_name_cb )
2025-01-06 23:33:41 +08:00
def add_key_frames ( self ) - > bool :
2025-01-12 22:29:32 +08:00
return self . is_checked ( self . key_frames_cb )
2025-01-06 23:33:41 +08:00
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 :
2025-01-13 20:41:08 +08:00
""" Create body layout """
2025-01-06 23:33:41 +08:00
self . body = QVBoxLayout ( )
self . body . setContentsMargins (
MARGIN_BODY_LEFT ,
MARGIN_BODY_TOP ,
MARGIN_BODY_RIGHT ,
MARGIN_BOTTOM ,
)
self . body . setSpacing ( SPACING )
2025-01-13 20:41:08 +08:00
# 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
2025-01-06 23:33:41 +08:00
self . create_dna_selector ( )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
self . mesh_tree_list = self . create_mesh_selector ( )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
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 " )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
widget = QWidget ( )
layout = QHBoxLayout ( widget )
layout . addWidget ( tab )
self . body . addWidget ( widget )
2025-01-13 20:41:08 +08:00
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 ( )
2025-01-06 23:33:41 +08:00
self . process_btn = self . create_process_btn ( )
self . progress_bar = self . create_progress_bar ( )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
return self . body
2025-01-13 20:41:08 +08:00
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 )
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
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 )
2025-01-06 23:33:41 +08:00
2025-01-13 20:41:08 +08:00
def create_header ( self ) - > QHBoxLayout :
2025-01-06 23:33:41 +08:00
self . header = QHBoxLayout ( )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
label = QLabel ( " v " + __version__ )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
btn = self . create_help_btn ( )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
self . header . addWidget ( label )
self . header . addStretch ( 1 )
self . header . addWidget ( btn )
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
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 :
2025-01-13 20:41:08 +08:00
if TOOL_HELP_URL :
webbrowser . open ( TOOL_HELP_URL )
2025-01-06 23:33:41 +08:00
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 )
2025-01-12 22:29:32 +08:00
2025-01-06 23:33:41 +08:00
self . select_dna_path . fc_text_field . textChanged . connect (
lambda : self . on_dna_selected ( self . select_dna_path )
)
2025-01-12 22:29:32 +08:00
layout = QVBoxLayout ( )
layout . addWidget ( self . select_dna_path )
layout . addWidget ( self . load_dna_btn )
2025-01-06 23:33:41 +08:00
layout . setContentsMargins (
MARGIN_HEADER_LEFT ,
MARGIN_HEADER_TOP ,
MARGIN_HEADER_RIGHT ,
MARGIN_HEADER_BOTTOM ,
)
widget . setLayout ( layout )
2025-01-12 22:29:32 +08:00
2025-01-06 23:33:41 +08:00
self . body . addWidget ( widget )
2025-01-12 22:29:32 +08:00
return widget
2025-01-06 23:33:41 +08:00
2025-01-12 22:00:02 +08:00
def on_dna_selected ( self , input : FileChooser ) - > None :
2025-01-12 22:29:32 +08:00
enabled = input . get_file_path ( ) is not None
2025-01-06 23:33:41 +08:00
self . load_dna_btn . setEnabled ( enabled )
self . process_btn . setEnabled ( False )
def create_dna_chooser ( self ) - > FileChooser :
return self . create_file_chooser (
2025-01-13 20:41:08 +08:00
" DNA File: " ,
2025-01-06 23:33:41 +08:00
" 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 :
2025-01-13 20:41:08 +08:00
return self . create_file_chooser (
2025-01-06 23:33:41 +08:00
" Gui path: " ,
2025-01-13 20:41:08 +08:00
" GUI file to load. Required by RigLogic " ,
" Select the gui file " ,
" gui files (*.ma) " ,
2025-01-06 23:33:41 +08:00
)
2025-01-13 20:41:08 +08:00
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) " ,
2025-01-12 23:10:01 +08:00
)
2025-01-13 20:41:08 +08:00
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) " ,
2025-01-06 23:33:41 +08:00
)
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 " ,
2025-01-12 22:29:32 +08:00
" Add joints to rig. Requires: DNA to be loaded " ,
2025-01-06 23:33:41 +08:00
layout ,
self . on_joints_changed ,
)
self . blend_shapes_cb = self . create_checkbox (
" blend shapes " ,
2025-01-12 22:29:32 +08:00
" Add blend shapes to rig. Requires: DNA to be loaded and at least one mesh to be check " ,
2025-01-06 23:33:41 +08:00
layout ,
self . on_generic_changed ,
)
self . skin_cb = self . create_checkbox (
" skin cluster " ,
2025-01-12 22:29:32 +08:00
" Add skin cluster to rig. Requires: DNA to be loaded and at least one mesh and joints to be checked " ,
2025-01-06 23:33:41 +08:00
layout ,
self . on_generic_changed ,
)
self . rig_logic_cb = self . create_checkbox (
" rig logic " ,
2025-01-12 22:29:32 +08:00
" 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 " ,
2025-01-06 23:33:41 +08:00
layout ,
)
layout . addStretch ( )
2025-01-13 20:41:08 +08:00
widget . setMaximumHeight ( 150 )
2025-01-06 23:33:41 +08:00
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 ,
)
2025-01-12 22:29:32 +08:00
self . ctrl_attributes_on_root_joint_cb = self . create_checkbox (
2025-01-06 23:33:41 +08:00
" ctrl attributes on root joint " ,
" ctrl attributes on root joint " ,
layout ,
enabled = True ,
checked = True ,
)
2025-01-12 22:29:32 +08:00
self . animated_map_attributes_on_root_joint_cb = self . create_checkbox (
2025-01-06 23:33:41 +08:00
" animated map attributes on root joint " ,
" animated map attributes on root joint " ,
layout ,
enabled = True ,
checked = True ,
)
2025-01-12 22:29:32 +08:00
self . mesh_name_to_blend_shape_channel_name_cb = self . create_checkbox (
2025-01-06 23:33:41 +08:00
" mesh name to blend shape channel name " ,
" mesh name to blend shape channel name " ,
layout ,
enabled = True ,
checked = True ,
)
2025-01-12 22:29:32 +08:00
self . key_frames_cb = self . create_checkbox (
2025-01-06 23:33:41 +08:00
" key frames " ,
" Add keyframes to rig " ,
layout ,
enabled = True ,
checked = True ,
)
layout . addStretch ( )
2025-01-13 20:41:08 +08:00
widget . setMaximumHeight ( 150 )
2025-01-06 23:33:41 +08:00
return widget
def enable_additional_build_options ( self , enable : bool ) - > None :
2025-01-12 22:29:32 +08:00
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 )
2025-01-06 23:33:41 +08:00
def create_checkbox (
self ,
label : str ,
hint : str ,
layout : QHBoxLayout ,
on_changed : Callable [ [ int ] , None ] = None ,
checked : bool = False ,
enabled : bool = False ,
) - > QCheckBox :
2025-01-13 20:41:08 +08:00
2025-01-06 23:33:41 +08:00
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 ) :
2025-01-13 20:41:08 +08:00
if len ( self . mesh_tree_list . get_selected_meshes ( ) ) == self . dna . get_mesh_count ( ) :
2025-01-06 23:33:41 +08:00
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 )
)
2025-01-13 23:36:50 +08:00
self . rig_logic_cb . setEnabled ( enabled )